lazarus-ccr/components/richmemo/gtk2/gtk2richmemo.pas
skalogryz 19aca57b5e richmemo: adding RMLCLTRUNK define
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7278 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2020-01-13 14:25:57 +00:00

1762 lines
56 KiB
ObjectPascal

{
gtk2richmemo.pas
Author: Dmitry 'skalogryz' Boyarintsev
*****************************************************************************
* *
* This file is part of the Lazarus Component Library (LCL) *
* *
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
* for details about the copyright. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* *
*****************************************************************************
}
unit Gtk2RichMemo;
{$mode objfpc}{$H+}
interface
uses
// Bindings
gtk2, glib2, gdk2, pango,
// RTL/FCL
Types, Classes, SysUtils,
// LCL
LCLType, Controls, Graphics, LazUTF8, StdCtrls, LCLProc,
// Gtk2 widget
Gtk2Int, Gtk2Def,
GTK2WinApiWindow, Gtk2Globals, Gtk2Proc,
gdk2pixbuf, Gtk2WSStdCtrls,
// RichMemo
RichMemo, WSRichMemo, RichMemoUtils;
const
TagNameNumeric = 'numeric';
TagNameSubOrSuper = 'suborsuper';
TagNameLink = 'link';
BulletChar = #$E2#$80#$A2;
TabChar = #$09;
{ TGtk2WSCustomRichMemo }
type
TGtk2WSCustomRichMemo = class(TWSCustomRichMemo)
protected
class procedure SetCallbacks(const AGtkWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo);
class procedure GetWidgetBuffer(const AWinControl: TWinControl; var TextWidget: PGtkWidget; var Buffer: PGtkTextBuffer);
class function GetAttrAtIter(view: PGtkTextView; const start: TGtkTextIter): PGtkTextAttributes;
class function GetAttrAtPos(const AWinControl: TWinControl; TextStart: Integer; APara: Boolean = false): PGtkTextAttributes;
class procedure GetAttributesAt(const AWinControl: TWinControl; TextStart: Integer; APara: Boolean;
var attr: PGtkTextAttributes; var fp: TFontParams);
class procedure ApplyTag(abuffer: PGtkTextBuffer; tag: PGtkTextTag; TextStart, TextLen: Integer; ToParagraphs: Boolean = False);
class procedure FormatSubSuperScript(buffer: PGtkTextBuffer; vs: TVScriptPos; fontSizePts: Double; TextStart, TextLen: Integer);
published
class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle; override;
class procedure DestroyHandle(const AWinControl: TWinControl); override;
class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
class function GetStrings(const ACustomMemo: TCustomMemo): TStrings; override;
class function GetStyleRange(const AWinControl: TWinControl; TextStart: Integer;
var RangeStart, RangeLen: Integer): Boolean; override;
class function GetTextAttributes(const AWinControl: TWinControl; TextStart: Integer;
var Params: TIntFontParams): Boolean; override;
class procedure SetTextAttributes(const AWinControl: TWinControl; TextStart, TextLen: Integer;
const Params: TIntFontParams); override;
class function GetParaAlignment(const AWinControl: TWinControl; TextStart: Integer;
var AAlign: TIntParaAlignment): Boolean; override;
class procedure SetParaAlignment(const AWinControl: TWinControl; TextStart, TextLen: Integer;
const AAlign: TIntParaAlignment); override;
class function GetParaMetric(const AWinControl: TWinControl; TextStart: Integer;
var AMetric: TIntParaMetric): Boolean; override;
class procedure SetParaMetric(const AWinControl: TWinControl; TextStart, TextLen: Integer;
const AMetric: TIntParaMetric); override;
class procedure SetParaNumbering(const AWinControl: TWinControl; TextStart,
TextLen: Integer; const ANumber: TIntParaNumbering); override;
class procedure SetParaTabs(const AWinControl: TWinControl; TextStart, TextLen: Integer;
const AStopList: TTabStopList); override;
class function GetParaTabs(const AWinControl: TWinControl; TextStart: integer;
var AStopList: TTabStopList): Boolean; override;
class function GetParaRange(const AWinControl: TWinControl; TextStart: Integer; var rng: TParaRange): Boolean; override;
class procedure SetTextUIParams(const AWinControl: TWinControl; TextStart, TextLen: Integer;
const ui: TTextUIParam); override;
class function GetTextUIParams(const AWinControl: TWinControl; TextStart: Integer;
var ui: TTextUIParam): Boolean; override;
class procedure InDelText(const AWinControl: TWinControl; const TextUTF8: String; DstStart, DstLen: Integer); override;
class function CharAtPos(const AWinControl: TWinControl; x,y: Integer): Integer; override;
class function Search(const AWinControl: TWinControl; const ANiddle: string; const SearchOpts: TIntSearchOpt): Integer; override;
class function ImageFromFile(const ARichMemo: TCustomRichMemo; APos: Integer;
const FileNameUTF8: string;
const AImgSize: TSize
): Boolean;
class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
class procedure SetZoomFactor(const AWinControl: TWinControl; AZoomFactor: Double); override;
// inline handler
class function InlineInsert(const AWinControl: TWinControl; ATextStart, ATextLength: Integer;
const ASize: TSize; AHandler: TRichMemoInline; var wsObj: TRichMemoInlineWSObject): Boolean; override;
class procedure InlineInvalidate(const AWinControl: TWinControl;
AHandler: TRichMemoInline; wsObj: TRichMemoInlineWSObject); override;
end;
{ TGtk2InlineObject }
TGtk2InlineObject = class(TRichMemoInlineWSObject)
public
anch : PGtkTextChildAnchor;
wgt : PGtkWidget;
il : TRichMemoInline;
size : TSize;
cnv : TCanvas;
constructor Create;
destructor Destroy; override;
end;
type
{ TGtk2RichMemoStrings }
TGtk2RichMemoStrings = class(TGtk2MemoStrings)
protected
FGtkText : PGtkTextView;
FGtkBuf: PGtkTextBuffer;
FOwner: TWinControl;
function GetTextStr: string; override;
function GetCount: integer; override;
function Get(Index : Integer) : string; override;
public
constructor Create(TextView : PGtkTextView; TheOwner: TWinControl);
destructor Destroy; override;
procedure Assign(Source : TPersistent); override;
procedure AddStrings(TheStrings: TStrings); override;
procedure Clear; override;
procedure Delete(Index : integer); override;
procedure Insert(Index : integer; const S: string); override;
procedure SetTextStr(const Value: string); override;
property Owner: TWinControl read FOwner;
end;
const
SubSuperFontKoef = 0.583; // the koef for the font size of sub/super scripts matching Adover Illustrator and OpenOffice
SuperRiseKoef = 0.58;
SubRiseKoef = -0.08;
implementation
type
TRichMemoData = record
link : Boolean;
link_li : integer;
link_le : integer;
link_act: TGdkEventType;
link_btn: guint;
end;
PRichMemoData = ^TRichMemoData;
var
linkCursor: PGdkCursor = nil;// gdk_cursor_new(GDK_DRAFT_LARGE)
// todo: why "shr" on each of this flag test?
function gtktextattr_underline(const a : TGtkTextAppearance) : Boolean;
begin
Result:=((a.flag0 and bm_TGtkTextAppearance_underline) shr bp_TGtkTextAppearance_underline) > 0;
end;
function gtktextattr_strikethrough(const a : TGtkTextAppearance) : Boolean;
begin
Result:=((a.flag0 and bm_TGtkTextAppearance_strikethrough) shr bp_TGtkTextAppearance_strikethrough) > 0;
end;
function gtktextattr_bkcolor(const a : TGtkTextAppearance) : Boolean;
begin
Result:=((a.flag0 and bm_TGtkTextAppearance_draw_bg ) shr bp_TGtkTextAppearance_draw_bg) > 0;
end;
function GtkTextAttrToFontParams(const textAttr: TGtkTextAttributes; var FontParams: TIntFontParams; const FontKoef : Double = 1.0): Boolean;
var
w : integer;
st : TPangoStyle;
pf : PPangoFontDescription;
sz : double;
const
ScreenDPI = 96; // todo: might change, should be received dynamically
PageDPI = 72; // not expected to be changed
begin
InitFontParams(FontParams);
pf := textAttr.font;
Result := Assigned(pf);
if not Result then Exit;
if Assigned(pf) then begin
FontParams.Name := pango_font_description_get_family(pf);
FontParams.Size := pango_font_description_get_size(pf);
sz:=FontParams.Size / PANGO_SCALE;
if pango_font_description_get_size_is_absolute(pf) then
sz:=sz/(ScreenDPI/PageDPI);
FontParams.Size:=round(sz*FontKoef);
w := pango_font_description_get_weight(pf);
if w > PANGO_WEIGHT_NORMAL then Include(FontParams.Style, fsBold);
st := pango_font_description_get_style(pf);
if st and PANGO_STYLE_ITALIC > 0 then Include(FontParams.Style, fsItalic);
end;
FontParams.Color := TGDKColorToTColor(textAttr.appearance.fg_color);
if gtktextattr_underline(textAttr.appearance) then Include(FontParams.Style, fsUnderline);
if gtktextattr_strikethrough(textAttr.appearance) then Include(FontParams.Style, fsStrikeOut);
FontParams.HasBkClr := gtktextattr_bkcolor(textAttr.appearance);
if FontParams.HasBkClr then
FontParams.BkColor := TGDKColorToTColor(textAttr.appearance.bg_color);
end;
type
TGtk2WSCustomMemoInt = class(TGtk2WSCustomMemo);
TCustomRichMemoInt = class(TCustomRichMemo);
procedure Gtk2WS_MemoSelChanged_Before(Textbuffer: PGtkTextBuffer;
StartIter: PGtkTextIter; mark: PGtkTextMark; WidgetInfo: PWidgetInfo); cdecl;
var
tag : PGtkTextTag;
begin
tag := gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table(TextBuffer)
, TagNameNumeric);
// navigate "through" numbering characters
if gtk_text_iter_has_tag( StartIter, tag) then begin
// if tried to move at the "endo
if gtk_text_iter_begins_tag(StartIter, tag) then begin
gtk_text_iter_forward_to_tag_toggle(StartIter, nil);
gtk_text_buffer_move_mark(TextBuffer, mark, StartIter);
end else begin
gtk_text_iter_forward_char(StartIter);
if gtk_text_iter_ends_tag(StartIter, tag) then begin
gtk_text_iter_backward_to_tag_toggle(StartIter, nil);
gtk_text_iter_backward_char(StartIter);
gtk_text_buffer_move_mark(TextBuffer, mark, StartIter);
end;
end;
end;
end;
procedure Gtk2WS_MemoSelChanged (Textbuffer: PGtkTextBuffer;
StartIter: PGtkTextIter; mark: PGtkTextMark; WidgetInfo: PWidgetInfo); cdecl;
begin
if TControl(WidgetInfo^.LCLObject) is TCustomRichMemo then
begin
TCustomRichMemoInt(WidgetInfo^.LCLObject).DoSelectionChange;
end;
end;
procedure Gtk2WS_RichMemoInsert(Textbuffer: PGtkTextBuffer;
StartIter: PGtkTextIter; text: PChar; len: gint; WidgetInfo: PWidgetInfo); cdecl;
var
rm : TCustomRichMemo;
iter : PGtkTextIter;
tag : PGtkTextTag;
w : PGtkWidget;
b : PGtkTextBuffer;
attr : PGtkTextAttributes;
begin
if TControl(WidgetInfo^.LCLObject) is TCustomRichMemo then
begin
rm := TCustomRichMemo(WidgetInfo^.LCLObject);
// re-zooming any newly entered (pasted, manually inserted text)
if (rm.ZoomFactor<>1) then begin
TGtk2WSCustomRichMemo.GetWidgetBuffer(rm, w, b);
iter:=gtk_text_iter_copy(StartIter);
gtk_text_iter_backward_chars(iter, len);
attr := gtk_text_view_get_default_attributes(PGtkTextView(w));
gtk_text_iter_get_attributes(iter, attr);
if attr^.font_scale<>rm.ZoomFactor then begin
tag := gtk_text_buffer_create_tag(b, nil,
'scale', [ gdouble(rm.ZoomFactor),
'scale-set', gboolean(gTRUE),
nil]);
gtk_text_buffer_apply_tag(b, tag, iter, StartIter);
end;
gtk_text_attributes_unref(attr);
end;
end;
end;
procedure Gtk2WS_Backspace(view: PGtkTextView; WidgetInfo: PWidgetInfo); cdecl;
var
buf : PGtkTextBuffer;
mark : PGtkTextMark;
iend : TGtkTextIter;
istart : TGtkTextIter;
tag : PGtkTextTag;
begin
// this handler checks, if the "numbering" should be erarsed
buf:=gtk_text_view_get_buffer(view);
if not Assigned(buf) then Exit;
mark := gtk_text_buffer_get_mark(buf, 'insert');
if not Assigned(mark) then Exit;
tag := gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table(buf)
, TagNameNumeric);
if not Assigned(tag) then Exit;
// first, check if cursor is right "after" the "numbering characters"
gtk_text_buffer_get_iter_at_mark(buf, @iend, mark);
if gtk_text_iter_ends_tag(@iend, tag) then begin
// cursor position is at the beginning of the line - erase all
// characters that belong to the numbering.
istart:=iend;
gtk_text_iter_backward_to_tag_toggle(@istart, tag);
gtk_text_buffer_delete(buf, @istart, @iend);
// prevent default backspace
g_signal_stop_emission_by_name(view, 'backspace');
end;
end;
function GatherNumeirc(buf: PGtkTextBuffer; const istart: TGtkTextIter; var num: TIntParaNumbering): Boolean;
var
iend : TGtkTextIter;
ch : Pgchar;
s : string;
i : Integer;
err : Integer;
begin
iend:=istart;
gtk_text_iter_forward_to_tag_toggle(@iend, nil);
ch:=gtk_text_iter_get_text(@istart, @iend);
Result:=Assigned(ch);
if not Result then Exit;
s:=ch;
g_free(ch);
Result:=length(s)>1;
if not Result then Exit;
if Pos(BulletChar, s[1]) = 1 then num.Style:=pnBullet
else if s[1] in ['0'..'9'] then begin
num.Style:=pnNumber;
i:=1;
while (i<=length(s)) and (s[i] in ['0'..'9']) do inc(i);
Val( Copy(s, 1, i-1), num.NumberStart, err);
end else begin
num.Style:=pnCustomChar;
end;
//ch.
//gtk_text_buffer_insert_range
end;
function Gtk2_RichMemoKeyPress(view: PGtkTextView; Event: PGdkEventKey;
WidgetInfo: PWidgetInfo): gboolean; cdecl;
var
buf : PGtkTextBuffer;
mark : PGtkTextMark;
istart : TGtkTextIter;
tag : PGtkTextTag;
begin
if Event^.keyval= GDK_KEY_Return then begin
//writeln('return !');
buf:=gtk_text_view_get_buffer(view);
if not Assigned(buf) then Exit;
mark := gtk_text_buffer_get_mark(buf, 'insert');
if not Assigned(mark) then Exit;
tag := gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table(buf)
, TagNameNumeric);
if not Assigned(tag) then Exit;
gtk_text_buffer_get_iter_at_mark(buf, @istart, mark);
gtk_text_iter_set_line_offset(@istart, 0);
if gtk_text_iter_begins_tag(@istart, tag) then begin
//writeln('apply!');
//writeln( 'ofs: ', gtk_text_iter_get_offset(@istart));
end;
end;
Result:=false;
end;
function Gtk2_RichMemoMotion(view: PGtkTextView; Event: PGdkEventKey;
WidgetInfo: PWidgetInfo): gboolean; cdecl;
var
mt : PGdkEventMotion;
i : TGtkTextIter;
tag : PGtkTextTag;
buf : PGtkTextBuffer;
w : PGdkWindow;
bx,by: gint;
begin
buf:=gtk_text_view_get_buffer(view);
if not Assigned(buf) then Exit;
tag := gtk_text_tag_table_lookup(
gtk_text_buffer_get_tag_table(buf), TagNameLink);
if not Assigned(tag) then Exit; // which is odd.
mt:=PGdkEventMotion(Event);
gtk_text_view_window_to_buffer_coords(view, GTK_TEXT_WINDOW_TEXT,
round(mt^.x), round(mt^.y), @bx, @by);
gtk_text_view_get_iter_at_location(view, @i, bx,by);
gdk_cursor_new(GDK_DRAFT_LARGE);
w:= gtk_text_view_get_window(view, GTK_TEXT_WINDOW_TEXT);
if gtk_text_iter_has_tag(@i,tag) then begin
if not Assigned(linkCursor) then
linkCursor:=gdk_cursor_new(GDK_HAND1);
gdk_window_set_cursor(w, linkCursor);
end else
gdk_window_set_cursor(w, nil);
Result:=false;
end;
function Gtk2_RichMemoMouseButton(view: PGtkTextView; Event: PGdkEventKey;
WidgetInfo: PWidgetInfo): gboolean; cdecl;
var
mt : PGdkEventButton;
i : TGtkTextIter;
tag : PGtkTextTag;
buf : PGtkTextBuffer;
bx,by: gint;
mi : TLinkMouseInfo;
li,le: integer;
act : TLinkAction;
data : PRichMemoData;
begin
buf:=gtk_text_view_get_buffer(view);
data:=PRichMemoData(WidgetInfo^.UserData);
if not Assigned(buf) then Exit;
tag := gtk_text_tag_table_lookup(
gtk_text_buffer_get_tag_table(buf), TagNameLink);
if not Assigned(tag) then Exit; // which is odd.
mt:=PGdkEventButton(Event);
gtk_text_view_window_to_buffer_coords(view, GTK_TEXT_WINDOW_TEXT,
round(mt^.x), round(mt^.y), @bx, @by);
gtk_text_view_get_iter_at_location(view, @i, bx,by);
if gtk_text_iter_has_tag(@i,tag) then begin
if TControl(WidgetInfo^.LCLObject) is TCustomRichMemo then
begin
gtk_text_iter_backward_to_tag_toggle(@i, tag);
li:=gtk_text_iter_get_offset(@i);
gtk_text_iter_forward_to_tag_toggle(@i, tag);
le:=gtk_text_iter_get_offset(@i)-li;
case mt^._type of
GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_3BUTTON_PRESS: begin
data^.link:=true;
data^.link_li:=li;
data^.link_le:=le;
data^.link_act:=mt^._type;
data^.link_btn:=mt^.button;
end;
GDK_BUTTON_RELEASE: begin
act:=laClick;
if (data^.link) and (data^.link_btn=mt^.button) and (data^.link_li=li) and (le=data^.link_le) then
begin
data^.link:=false;
case mt^.button of
2: mi.button:=mbMiddle;
3: mi.button:=mbRight;
else
mi.button:=mbLeft;
end;
TCustomRichMemoInt(WidgetInfo^.LCLObject).DoLinkAction(act, mi, li, le);
end;
end;
end;
end;
end;
Result:=false;
end;
{ TGtk2InlineObject }
constructor TGtk2InlineObject.Create;
begin
inherited Create;
end;
destructor TGtk2InlineObject.Destroy;
begin
cnv.Free;
inherited Destroy;
end;
{ TGtk2RichMemoStrings }
function TGtk2RichMemoStrings.GetTextStr: string;
var
StartIter, EndIter: TGtkTextIter;
AText: PgChar;
begin
Result := '';
gtk_text_buffer_get_start_iter(FGtkBuf, @StartIter);
gtk_text_buffer_get_end_iter(FGtkBuf, @EndIter);
AText := gtk_text_iter_get_text(@StartIter, @EndIter);
Result := StrPas(AText);
if AText <> nil then
g_free(AText);
end;
function TGtk2RichMemoStrings.GetCount: integer;
begin
Result := gtk_text_buffer_get_line_count(FGtkBuf);
if Get(Result-1) = '' then Dec(Result);
end;
function TGtk2RichMemoStrings.Get(Index: Integer): string;
var
StartIter, EndIter: TGtkTextIter;
AText: PgChar;
begin
gtk_text_buffer_get_iter_at_line(FGtkBuf, @StartIter, Index);
if Index = gtk_text_buffer_get_line_count(FGtkBuf) then
gtk_text_buffer_get_end_iter(FGtkBuf, @EndIter)
else begin
gtk_text_buffer_get_iter_at_line(FGtkBuf, @EndIter, Index);
gtk_text_iter_forward_to_line_end(@EndIter);
end;
// if a row is blank gtk_text_iter_forward_to_line_end will goto the row ahead
// this is not desired. so if it jumped ahead a row then the row we want is blank
if gtk_text_iter_get_line(@StartIter) = gtk_text_iter_get_line(@EndIter) then
begin
AText := gtk_text_iter_get_text(@StartIter, @EndIter);
Result := StrPas(AText);
g_free(AText);
end
else
Result := '';
end;
constructor TGtk2RichMemoStrings.Create(TextView: PGtkTextView;
TheOwner: TWinControl);
begin
inherited Create(TextView, TheOwner);
if TextView = nil then RaiseGDBException(
'TGtk2RichMemoStrings.Create Unspecified Text widget');
FGtkText:= TextView;
FGtkBuf := gtk_text_view_get_buffer(FGtkText);
if TheOwner = nil then RaiseGDBException(
'TGtk2RichMemoStrings.Create Unspecified owner');
FOwner:=TheOwner;
end;
destructor TGtk2RichMemoStrings.Destroy;
begin
inherited Destroy;
end;
procedure TGtk2RichMemoStrings.Assign(Source: TPersistent);
var
S: TStrings;
begin
if Source is TStrings then
begin
S:=TStrings(Source);
// to prevent Clear and then SetText we need to use our own Assign
QuoteChar := S.QuoteChar;
Delimiter := S.Delimiter;
NameValueSeparator := S.NameValueSeparator;
TextLineBreakStyle := S.TextLineBreakStyle;
Text := S.Text;
end
else
inherited Assign(Source);
end;
procedure TGtk2RichMemoStrings.AddStrings(TheStrings: TStrings);
begin
SetTextStr(GetTextStr + TStrings(TheStrings).Text);
end;
procedure TGtk2RichMemoStrings.Clear;
begin
SetText('');
end;
procedure TGtk2RichMemoStrings.Delete(Index: integer);
var
StartIter,
EndIter: TGtkTextIter;
begin
gtk_text_buffer_get_iter_at_line(FGtkBuf, @StartIter, Index);
if Index = Count-1 then begin
gtk_text_iter_backward_char(@StartIter);
gtk_text_buffer_get_end_iter(FGtkBuf, @EndIter)
end
else
gtk_text_buffer_get_iter_at_line(FGtkBuf, @EndIter, Index+1);
gtk_text_buffer_delete(FGtkBuf, @StartIter, @EndIter);
end;
procedure TGtk2RichMemoStrings.Insert(Index: integer; const S: string);
var
StartIter,
CursorIter: TGtkTextIter;
NewLine: String;
TextMark: PGtkTextMark;
begin
if Index < gtk_text_buffer_get_line_count(FGtkBuf) then begin
//insert with LineEnding
NewLine := S+LineEnding;
gtk_text_buffer_get_iter_at_line(FGtkBuf, @StartIter, Index);
end
else begin
//append with a preceding LineEnding
gtk_text_buffer_get_end_iter(FGtkBuf, @StartIter);
if gtk_text_buffer_get_line_count(FGtkBuf) = Count then
NewLine := LineEnding+S+LineEnding
else
NewLine := S+LineEnding;
end;
{if FQueueCursorMove = 0 then
begin}
TextMark := gtk_text_buffer_get_insert(FGtkBuf);
gtk_text_buffer_get_iter_at_mark(FGtkBuf, @CursorIter, TextMark);
{ if gtk_text_iter_equal(@StartIter, @CursorIter) then
QueueCursorMove(-1);
end;}
// and finally insert the new text
gtk_text_buffer_insert(FGtkBuf, @StartIter, PChar(NewLine) ,-1);
end;
procedure TGtk2RichMemoStrings.SetTextStr(const Value: string);
begin
if (Value <> Text) then
begin
LockOnChange({%H-}PGtkObject(Owner.Handle), 1);
gtk_text_buffer_set_text(FGtkBuf, PChar(Value), -1);
end;
end;
class procedure TGtk2WSCustomRichMemo.SetCallbacks(
const AGtkWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo);
var
TextBuf: PGtkTextBuffer;
view : PGtkTextView;
begin
TGtk2WSCustomMemoInt.SetCallbacks(AGtkWidget, AWidgetInfo);
view:=PGtkTextView(AWidgetInfo^.CoreWidget);
TextBuf := gtk_text_view_get_buffer(view);
SignalConnect(PGtkWidget(view), 'backspace', @Gtk2WS_Backspace, AWidgetInfo);
SignalConnect(PGtkWidget(TextBuf), 'mark-set', @Gtk2WS_MemoSelChanged_Before, AWidgetInfo);
SignalConnectAfter(PGtkWidget(TextBuf), 'mark-set', @Gtk2WS_MemoSelChanged, AWidgetInfo);
SignalConnectAfter(PGtkWidget(TextBuf), 'insert-text', @Gtk2WS_RichMemoInsert, AWidgetInfo);
SignalConnect(PGtkWidget(view), 'key-press-event', @Gtk2_RichMemoKeyPress, AWidgetInfo);
SignalConnect(PGtkWidget(view), 'motion-notify-event', @Gtk2_RichMemoMotion, AWidgetInfo);
SignalConnect(PGtkWidget(view), 'button-press-event', @Gtk2_RichMemoMouseButton, AWidgetInfo);
SignalConnect(PGtkWidget(view), 'button-release-event', @Gtk2_RichMemoMouseButton, AWidgetInfo);
end;
class procedure TGtk2WSCustomRichMemo.GetWidgetBuffer(const AWinControl: TWinControl;
var TextWidget: PGtkWidget; var Buffer: PGtkTextBuffer);
var
Widget : PGtkWidget;
list : PGList;
begin
TextWidget:=nil;
Buffer:=nil;
// todo: cache values?
Widget := PGtkWidget(PtrUInt(AWinControl.Handle));
list := gtk_container_get_children(PGtkContainer(Widget));
if not Assigned(list) then Exit;
TextWidget := PGtkWidget(list^.data);
if not Assigned(TextWidget) then Exit;
buffer := gtk_text_view_get_buffer (PGtkTextView(TextWidget));
end;
class function TGtk2WSCustomRichMemo.GetAttrAtIter(view: PGtkTextView; const start: TGtkTextIter): PGtkTextAttributes;
var
attr : PGtkTextAttributes;
begin
Result:=nil;
attr := gtk_text_view_get_default_attributes(view);
if not Assigned(attr) then Exit;
gtk_text_iter_get_attributes(@start, attr);
Result:=attr;
end;
class function TGtk2WSCustomRichMemo.GetAttrAtPos(
const AWinControl: TWinControl; TextStart: Integer; APara: Boolean ): PGtkTextAttributes;
var
TextWidget : PGtkWidget;
buffer : PGtkTextBuffer;
iter : TGtkTextIter;
begin
Result:=nil;
GetWidgetBuffer(AWinControl, TextWidget, buffer);
if not Assigned(buffer) then Exit;
gtk_text_buffer_get_iter_at_offset(buffer, @iter, TextStart);
if APara then gtk_text_iter_set_line_offset(@iter, 0);
Result := GetAttrAtIter(PGtkTextView(TextWidget), iter);
end;
class procedure TGtk2WSCustomRichMemo.ApplyTag(abuffer: PGtkTextBuffer;
tag: PGtkTextTag; TextStart, TextLen: Integer; ToParagraphs: Boolean = False);
var
istart : TGtkTextIter;
iend : TGtkTextIter;
begin
gtk_text_buffer_get_iter_at_offset (abuffer, @istart, TextStart);
gtk_text_buffer_get_iter_at_offset (abuffer, @iend, TextStart+TextLen);
if ToParagraphs then begin
gtk_text_iter_set_line_offset(@istart, 0);
gtk_text_iter_forward_to_line_end(@iend);
end;
gtk_text_buffer_apply_tag(abuffer, tag, @istart, @iend);
end;
class function TGtk2WSCustomRichMemo.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle;
var
Widget,
TempWidget: PGtkWidget;
WidgetInfo: PWidgetInfo;
buffer: PGtkTextBuffer;
SS:TPoint;
gcolor : TGdkColor;
data: PRichMemoData;
const
pu: array [Boolean] of gint = (PANGO_UNDERLINE_NONE, PANGO_UNDERLINE_SINGLE);
begin
Widget := gtk_scrolled_window_new(nil, nil);
Result := TLCLIntfHandle(PtrUInt(Widget));
if Result = 0 then Exit;
{$IFDEF DebugLCLComponents}
DebugGtkWidgets.MarkCreated(Widget,dbgsName(AWinControl));
{$ENDIF}
New(data);
FillChar(data^, sizeof(TRichMemoData), 0);
WidgetInfo := CreateWidgetInfo(Pointer(Result), AWinControl, AParams);
WidgetInfo^.DataOwner := True;
WidgetInfo^.UserData := data;
TempWidget := gtk_text_view_new();
gtk_container_add(PGtkContainer(Widget), TempWidget);
GTK_WIDGET_UNSET_FLAGS(PGtkScrolledWindow(Widget)^.hscrollbar, GTK_CAN_FOCUS);
GTK_WIDGET_UNSET_FLAGS(PGtkScrolledWindow(Widget)^.vscrollbar, GTK_CAN_FOCUS);
SS:=Gtk2TranslateScrollStyle(TCustomMemo(AWinControl).ScrollBars);
gtk_scrolled_window_set_policy(PGtkScrolledWindow(Widget),SS.X, SS.Y);
// add border for memo
gtk_scrolled_window_set_shadow_type(PGtkScrolledWindow(Widget),
BorderStyleShadowMap[TCustomMemo(AWinControl).BorderStyle]);
SetMainWidget(Widget, TempWidget);
{$ifdef RMLCLTRUNK}
GetWidgetInfo(Widget)^.CoreWidget := TempWidget;
{$else}
GetWidgetInfo(Widget, True)^.CoreWidget := TempWidget;
{$endif}
gtk_text_view_set_editable(PGtkTextView(TempWidget), not TCustomMemo(AWinControl).ReadOnly);
gtk_text_view_set_justification(PGtkTextView(TempWidget), aGtkJustification[TCustomMemo(AWinControl).Alignment]);
if TCustomMemo(AWinControl).WordWrap then
gtk_text_view_set_wrap_mode(PGtkTextView(TempWidget), GTK_WRAP_WORD)
else
gtk_text_view_set_wrap_mode(PGtkTextView(TempWidget), GTK_WRAP_NONE);
gtk_text_view_set_accepts_tab(PGtkTextView(TempWidget), TCustomMemo(AWinControl).WantTabs);
gtk_widget_show_all(Widget);
buffer := gtk_text_view_get_buffer (PGtkTextView(TempWidget));
//tag:=gtk_text_tag_new(TagNameNumeric);
gtk_text_buffer_create_tag (buffer, TagNameNumeric,
'editable', [ gboolean(gFALSE),
'editable-set', gboolean(gTRUE),
nil]);
gtk_text_buffer_create_tag (buffer, TagNameSubOrSuper, nil);
gcolor := TColortoTGDKColor(clBlue);
gtk_text_buffer_create_tag (buffer, TagNameLink, 'foreground-gdk', [@gcolor,
'foreground-set', gboolean(gTRUE),
'underline-set', gboolean(gTRUE),
'underline', gint(pu[true]),
nil] );
Set_RC_Name(AWinControl, Widget);
SetCallbacks(Widget, WidgetInfo);
end;
class procedure TGtk2WSCustomRichMemo.DestroyHandle(
const AWinControl: TWinControl);
var
w : PGtkWidget;
b : PGtkTextBuffer;
handlerid: gulong;
begin
GetWidgetBuffer(AWinControl, w, b);
// uninstall hanlder, to prevent crashes
handlerid := g_signal_handler_find(b
, G_SIGNAL_MATCH_FUNC or G_SIGNAL_MATCH_DATA
, 0, 0, nil
, @Gtk2WS_MemoSelChanged, GetWidgetInfo(w));
g_signal_handler_disconnect (b, handlerid);
// the proper way of destroying a widgetset
Gtk2WidgetSet.DestroyLCLComponent(AWinControl);
// or actually - TGtk2WSWinControl.DestroyHandle(AWinControl)
// the following call
// TWSWinControlClass(Classparent).DestroyHandle(AWinControl);
// won't work, because TGtk2CustomMemo doesn't have DestoryHandle assigned
end;
class function TGtk2WSCustomRichMemo.GetSelLength(const ACustomEdit: TCustomEdit
): integer;
var
w : PGtkWidget;
b : PGtkTextBuffer;
istart : TGtkTextIter;
iend : TGtkTextIter;
begin
GetWidgetBuffer(ACustomEdit, w, b);
if not Assigned(b) then begin
Result:=-1;
Exit;
end;
Result := 0;
if not gtk_text_buffer_get_selection_bounds(b, @istart, @iend) then Exit;
Result := Abs(gtk_text_iter_get_offset(@iend) - gtk_text_iter_get_offset(@istart));
end;
class function TGtk2WSCustomRichMemo.GetStrings(const ACustomMemo: TCustomMemo
): TStrings;
var
TextView: PGtkTextView;
begin
{$ifdef RMLCLTRUNK}
TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomMemo.Handle))^.CoreWidget);
{$else}
TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomMemo.Handle), False)^.CoreWidget);
{$endif}
Result := TGtk2RichMemoStrings.Create(TextView, ACustomMemo);
end;
class function TGtk2WSCustomRichMemo.GetStyleRange(
const AWinControl: TWinControl; TextStart: Integer; var RangeStart,
RangeLen: Integer): Boolean;
var
w : PGtkWidget;
b : PGtkTextBuffer;
istart : TGtkTextIter;
iend : TGtkTextIter;
begin
GetWidgetBuffer(AWinControl, w, b);
if not Assigned(b) then begin
Result:=false;
Exit;
end;
gtk_text_buffer_get_iter_at_offset (b, @istart, TextStart+1);
if gtk_text_iter_get_offset(@istart)<>TextStart+1 then begin
Result:=false; // TextStart is beyoned the end of text
Exit;
end;
gtk_text_iter_backward_to_tag_toggle(@istart, nil);
RangeStart:=gtk_text_iter_get_offset(@istart);
gtk_text_buffer_get_iter_at_offset (b, @iend, TextStart);
gtk_text_iter_forward_to_tag_toggle(@iend, nil);
RangeLen:=gtk_text_iter_get_offset(@iend)-RangeStart;
Result:=true;
end;
class procedure TGtk2WSCustomRichMemo.FormatSubSuperScript(buffer: PGtkTextBuffer; vs: TVScriptPos; fontSizePts: Double; TextStart, TextLen: Integer);
var
sz : gint;
istart : TGtkTextIter;
iend : TGtkTextIter;
tag : PGtkTextTag;
scrtag : PGtkTextTag;
hasscript : Boolean;
k : Double;
begin
gtk_text_buffer_get_iter_at_offset (buffer, @istart, TextStart);
scrtag := gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table(buffer), TagNameSubOrSuper );
hasscript := gtk_text_iter_has_tag( @istart, scrtag );
if not hasscript then begin
iend:=istart;
gtk_text_iter_forward_to_tag_toggle(@iend, scrtag);
hasscript:=gtk_text_iter_get_offset(@iend)<TextStart+TextLen;
end;
if vs = vpNormal then begin
// no need to do anything, since no modifications were applied;
if not hasscript then Exit;
gtk_text_buffer_get_iter_at_offset (buffer, @iend, TextStart+TextLen);
gtk_text_buffer_remove_tag(buffer, scrtag, @istart, @iend);
tag := gtk_text_buffer_create_tag (buffer, nil,
'rise', [0,
'rise-set', gboolean(gTRUE),
nil]);
gtk_text_buffer_apply_tag(buffer, tag, @istart, @iend);
end else begin
gtk_text_buffer_get_iter_at_offset (buffer, @iend, TextStart+TextLen);
if vs = vpSubScript then k := SubRiseKoef
else k := SuperRiseKoef;
sz := round(fontSizePts * k * PANGO_SCALE);
tag := gtk_text_buffer_create_tag (buffer, nil,
'rise', [sz,
'rise-set', gboolean(gTRUE),
'size-set', gboolean(gTRUE),
'size-points', gdouble(fontSizePts*SubSuperFontKoef),
nil]);
gtk_text_buffer_apply_tag(buffer, tag, @istart, @iend);
gtk_text_buffer_apply_tag(buffer, scrtag, @istart, @iend);
end;
end;
class procedure TGtk2WSCustomRichMemo.GetAttributesAt(
const AWinControl: TWinControl; TextStart: Integer; APara: Boolean;
var attr: PGtkTextAttributes; var fp: TFontParams);
var
iter : TGtkTextIter;
v : PGtkTextView;
b : PGtkTextBuffer;
tag : PGtkTextTag;
begin
InitFontParams(fp);
GetWidgetBuffer(AWinControl, PGtkWidget(v), b);
gtk_text_buffer_get_iter_at_offset(b, @iter, TextStart);
if APara then gtk_text_iter_set_line_offset(@iter, 0);
attr:=GetAttrAtIter(v, iter);
if not Assigned(attr) then Exit;
tag := gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table(b), TagNameSubOrSuper );
if gtk_text_iter_has_tag(@iter, tag) then begin
GtkTextAttrToFontParams(attr^, fp, 1 / SubSuperFontKoef );
if attr^.appearance.rise < 0 then fp.VScriptPos:=vpSubScript
else if attr^.appearance.rise > 0 then fp.VScriptPos:=vpSuperScript;
end else
GtkTextAttrToFontParams(attr^, fp);
end;
class procedure TGtk2WSCustomRichMemo.SetTextAttributes(const AWinControl: TWinControl; TextStart, TextLen: Integer; const Params: TIntFontParams);
var
TextWidget: PGtkWidget;
buffer : PGtkTextBuffer;
tag : Pointer;
gcolor : TGdkColor;
bgcolor : TGdkColor;
nm : string;
const
pu: array [Boolean] of gint = (PANGO_UNDERLINE_NONE, PANGO_UNDERLINE_SINGLE);
pb: array [Boolean] of gint = (PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_BOLD);
pi: array [Boolean] of gint = (PANGO_STYLE_NORMAL, PANGO_STYLE_ITALIC);
begin
GetWidgetBuffer(AWinControl, TextWidget, buffer);
if not Assigned(buffer) then Exit;
gcolor := TColortoTGDKColor(Params.Color);
bgcolor := TColortoTGDKColor(Params.BkColor);
nm := Params.Name;
if nm = '' then nm := #0;
tag := gtk_text_buffer_create_tag (buffer, nil,
'family-set', [gboolean(gTRUE),
'family', @nm[1],
'foreground-gdk', @gcolor,
'foreground-set', gboolean(gTRUE),
'background-gdk', @bgcolor,
'background-set', gboolean(Params.HasBkClr),
'size-set', gboolean(gTRUE),
'size-points', gdouble(Params.Size),
'underline-set', gboolean(gTRUE),
'underline', gint(pu[fsUnderline in Params.Style]),
'weight-set', gboolean(gTRUE),
'weight', gint(pb[fsBold in Params.Style]),
'style-set', gboolean(gTRUE),
'style', gint(pi[fsItalic in Params.Style]),
'strikethrough-set', gboolean(gTRUE),
'strikethrough', gboolean(fsStrikeOut in Params.Style),
nil]);
ApplyTag(buffer, tag, TextStart, TextLen);
FormatSubSuperScript(buffer, Params.VScriptPos, Params.Size, TextStart, TextLen);
end;
class function TGtk2WSCustomRichMemo.GetParaAlignment(
const AWinControl: TWinControl; TextStart: Integer; var AAlign: TIntParaAlignment
): Boolean;
var
attr : PGtkTextAttributes;
begin
attr:=GetAttrAtPos(AWinControl, TextStart, true);
Result := Assigned(attr);
if Result then begin
case attr^.justification of
GTK_JUSTIFY_LEFT: AAlign:=paLeft;
GTK_JUSTIFY_RIGHT: AAlign:=paRIGHT;
GTK_JUSTIFY_CENTER: AAlign:=paCenter;
GTK_JUSTIFY_FILL: AAlign:=paJustify;
else
AAlign:=paLeft;
end;
gtk_text_attributes_unref(attr);
end;
end;
class procedure TGtk2WSCustomRichMemo.SetParaAlignment(
const AWinControl: TWinControl; TextStart, TextLen: Integer;
const AAlign: TIntParaAlignment);
var
w : PGtkWidget;
buffer : PGtkTextBuffer;
tag : PGtkTextTag;
val : Integer;
begin
case AAlign of
paRight: val:=GTK_JUSTIFY_RIGHT;
paCenter: val:=GTK_JUSTIFY_CENTER;
paJustify: val:=GTK_JUSTIFY_FILL;
else
val := GTK_JUSTIFY_LEFT;
end;
GetWidgetBuffer(AWinControl, w, buffer);
tag := gtk_text_buffer_create_tag (buffer, nil,
'justification', [ gint(val),
'justification-set', gboolean(gTRUE),
nil]);
ApplyTag(buffer, tag, TextStart, TextLen, true);
end;
class function TGtk2WSCustomRichMemo.GetParaMetric(
const AWinControl: TWinControl; TextStart: Integer;
var AMetric: TIntParaMetric): Boolean;
var
attr : PGtkTextAttributes;
fp : TFontParams;
const
ScreenDPI = 96; // todo: might change, should be received dynamically
PageDPI = 72; // not expected to be changed
PixToPt = PageDPI / ScreenDPI;
begin
GetAttributesAt(AWinControl, TextStart, true, attr, fp);
Result := Assigned(attr);
if Result then begin
if attr^.indent<0 then begin
AMetric.FirstLine:=(attr^.left_margin)*PixToPt;
AMetric.HeadIndent:=(-attr^.indent+attr^.left_margin)*PixToPt;
end else begin
AMetric.FirstLine:=(attr^.left_margin+attr^.indent)*PixToPt;
AMetric.HeadIndent:=attr^.left_margin*PixToPt;
end;
AMetric.TailIndent:=attr^.right_margin*PixToPt;
AMetric.SpaceAfter:=attr^.pixels_above_lines*PixToPt;
AMetric.SpaceBefore:=attr^.pixels_below_lines*PixToPt;
AMetric.LineSpacing:=(attr^.pixels_inside_wrap*PixToPt+fp.Size)/(fp.Size);
gtk_text_attributes_unref(attr);
end;
end;
class procedure TGtk2WSCustomRichMemo.SetParaMetric(
const AWinControl: TWinControl; TextStart, TextLen: Integer;
const AMetric: TIntParaMetric);
var
w : PGtkWidget;
buffer : PGtkTextBuffer;
tag : PGtkTextTag;
h : double;
fl : double;
t : double;
attr : PGtkTextAttributes;
fp : TFontParams;
const
ScreenDPI = 96; // todo: might change, should be received dynamically
PageDPI = 72; // not expected to be changed
DPIFactor = ScreenDPI / PageDPI;
begin
h:=AMetric.HeadIndent;
if h<0 then h:=0;
fl:=AMetric.FirstLine;
if fl<0 then fl:=0;
if fl<h then begin
t:=h;
h:=fl;
fl:=fl-t;
end else
fl:=fl-h;
GetAttributesAt(AWinControl, TextStart, true, attr, fp);
gtk_text_attributes_unref(attr);
GetWidgetBuffer(AWinControl, w, buffer);
tag := gtk_text_buffer_create_tag (buffer, nil,
'pixels-above-lines', [ gint(round(AMetric.SpaceBefore*DPIFactor)),
'pixels-above-lines-set', gboolean(gTRUE),
'pixels-below-lines', gint(round(AMetric.SpaceAfter*DPIFactor)),
'pixels-below-lines-set', gboolean(gTRUE),
'left-margin', gint(round(h*DPIFactor)),
'left-margin-set', gboolean(gTRUE),
'right-margin', gint(round(AMetric.TailIndent*DPIFactor)),
'right-margin-set', gboolean(gTRUE),
'indent', gint(round(fl*DPIFactor)),
'indent-set', gboolean(gTRUE),
'pixels-inside-wrap', gint((round(fp.Size*(AMetric.LineSpacing-DefLineSpacing)*DPIFactor))),
'pixels-inside_wrap-set', gboolean(gTRUE),
nil]);
ApplyTag(buffer, tag, TextStart, TextLen, true);
end;
class procedure TGtk2WSCustomRichMemo.SetParaNumbering(
const AWinControl: TWinControl; TextStart, TextLen: Integer;
const ANumber: TIntParaNumbering);
var
w : PGtkWidget;
b : PGtkTextBuffer;
istart : TGtkTextIter;
iend : TGtkTextIter;
txt : String;
len : Integer;
ln : Integer;
ls : Integer;
ofs : Integer;
numidx : Integer;
tag : PGtkTextTag;
begin
inherited SetParaNumbering(AWinControl, TextStart, TextLen, ANumber);
GetWidgetBuffer(AWinControl, w, b);
if not Assigned(w) or not Assigned(b) then Exit;
tag := gtk_text_tag_table_lookup( gtk_text_buffer_get_tag_table(b)
, TagNameNumeric);
gtk_text_buffer_get_iter_at_offset (b, @istart, TextStart);
iend:=istart;
gtk_text_iter_forward_chars(@iend, TextLen);
ln:=gtk_text_iter_get_line(@istart);
ls:=gtk_text_iter_get_line(@iend);
numidx:=1;
if ANumber.Style=pnNumber then numidx:=ANumber.NumberStart;
repeat
gtk_text_iter_set_line_offset(@istart, 0);
case ANumber.Style of
pnBullet: txt := BulletChar;
pnNumber: txt := IntToStr(numidx);
pnLowLetter: txt := 'a';
pnLowRoman: txt := 'i';
pnUpLetter: txt := 'A';
pnUpRoman: txt := 'I';
pnCustomChar: txt:= UTF8Encode(ANumber.CustomChar);
end;
if not (ANumber.Style in [pnBullet, pnCustomChar]) and (ANumber.SepChar<>#0) then
txt:=txt+UTF8Encode(ANumber.SepChar);
txt:=txt+TabChar;
//gtk_text_buffer_get_iter_at_offset (b, @iend, TextStart);
//gtk_text_buffer_move
ofs:=gtk_text_iter_get_offset(@istart);
// remove existing number
if gtk_text_iter_begins_tag(@istart, tag) then begin
iend:=istart;
gtk_text_iter_forward_to_tag_toggle(@iend, nil);
gtk_text_buffer_delete(b, @istart, @iend);
gtk_text_buffer_get_iter_at_offset (b, @istart, ofs);
end;
// insert new number
gtk_text_buffer_insert(b, @istart, @txt[1], length(txt));
// restoring iterators
gtk_text_buffer_get_iter_at_offset (b, @istart, ofs);
gtk_text_iter_set_line_offset(@istart, 0);
iend := istart;
len:=UTF8Length(txt);
gtk_text_iter_forward_chars(@iend, len);
// aplying tag
gtk_text_buffer_apply_tag_by_name(b, TagNameNumeric, @istart, @iend);
// next line!
gtk_text_iter_forward_line(@istart);
inc(ln);
inc(numidx);
until ln>ls;
end;
class procedure TGtk2WSCustomRichMemo.SetParaTabs(
const AWinControl: TWinControl; TextStart, TextLen: Integer;
const AStopList: TTabStopList);
var
w : PGtkWidget;
buffer : PGtkTextBuffer;
tag : PGtkTextTag;
parr : PPangoTabArray;
i : Integer;
const
ScreenDPI = 96; // todo: might change, should be received dynamically
PageDPI = 72; // not expected to be changed
DPIFactor = ScreenDPI / PageDPI;
begin
GetWidgetBuffer(AWinControl, w, buffer);
if not Assigned(w) or not Assigned(buffer) then Exit;
GetWidgetBuffer(AWinControl, w, buffer);
if AStopList.Count=0 then
parr:=nil
else begin
parr:=pango_tab_array_new(AStopList.Count, true);
for i:=0 to AStopList.Count-1 do begin
pango_tab_array_set_tab(parr, i, PANGO_TAB_LEFT, round(AStopList.Tabs[i].Offset * DPIFactor) );
end;
end;
tag := gtk_text_buffer_create_tag (buffer, nil,
'tabs', [ parr,
'tabs-set', gboolean(AStopList.Count>0),
nil]);
ApplyTag(buffer, tag, TextStart, TextLen, true);
if Assigned(parr) then pango_tab_array_free(parr);
end;
class function TGtk2WSCustomRichMemo.GetParaTabs(
const AWinControl: TWinControl; TextStart: integer;
var AStopList: TTabStopList): Boolean;
const
ScreenDPI = 96; // todo: might change, should be received dynamically
PageDPI = 72; // not expected to be changed
PixToPt = PageDPI / ScreenDPI;
var
i : Integer;
attr : PGtkTextAttributes;
loc : gint;
al : TPangoTabAlign;
f : Double;
begin
InitTabStopList(AStopList);
attr:=GetAttrAtPos(AWinControl, TextStart, true);
Result:=Assigned(attr);
if not Result then Exit;
if not Assigned(attr^.tabs) then Exit;
AStopList.Count:=pango_tab_array_get_size(attr^.tabs);
if AStopList.Count=0 then Exit;
f := PixToPt;
if not pango_tab_array_get_positions_in_pixels(attr^.tabs) then
f:= f / PANGO_SCALE;
SetLength(AStopList.Tabs, AStopList.Count);
for i:=0 to AStopList.Count-1 do begin
pango_tab_array_get_tab(attr^.tabs, i, @al, @loc);
AStopList.Tabs[i].Offset:=loc*f;
AStopList.Tabs[i].Align:=tabLeft;
end;
gtk_text_attributes_unref(attr);
end;
class function TGtk2WSCustomRichMemo.GetParaRange(
const AWinControl: TWinControl; TextStart: Integer; var rng: TParaRange
): Boolean;
var
w : PGtkWidget;
b : PGtkTextBuffer;
istart : TGtkTextIter;
iend : TGtkTextIter;
begin
GetWidgetBuffer(AWinControl, w, b);
if not Assigned(b) then begin
Result:=false;
Exit;
end;
gtk_text_buffer_get_iter_at_offset (b, @istart, TextStart);
gtk_text_buffer_get_iter_at_offset (b, @iend, TextStart);
gtk_text_iter_set_line_offset(@istart, 0);
gtk_text_iter_forward_to_line_end(@iend);
rng.start:=gtk_text_iter_get_offset(@istart);
rng.lengthNoBr:=gtk_text_iter_get_offset(@iend)-rng.start;
// if there's a character to move, then it's end of line, if not then it won't change!
gtk_text_iter_forward_char(@iend);
rng.length:=gtk_text_iter_get_offset(@iend)-rng.start;
Result:=true;
end;
class procedure TGtk2WSCustomRichMemo.SetTextUIParams(
const AWinControl: TWinControl; TextStart, TextLen: Integer;
const ui: TTextUIParam);
var
TextWidget: PGtkWidget;
buffer : PGtkTextBuffer;
tag : Pointer;
istart : TGtkTextIter;
iend : TGtkTextIter;
begin
GetWidgetBuffer(AWinControl, TextWidget, buffer);
if not Assigned(buffer) then Exit;
gtk_text_buffer_get_iter_at_offset (buffer, @istart, TextStart);
gtk_text_buffer_get_iter_at_offset (buffer, @iend, TextStart+TextLen);
if uiLink in ui.features then begin
gtk_text_buffer_apply_tag_by_name(buffer, TagNameLink, @istart, @iend);
end else begin
tag := gtk_text_tag_table_lookup(
gtk_text_buffer_get_tag_table(buffer), TagNameLink);
if Assigned(tag) then
gtk_text_buffer_remove_tag(buffer, tag, @istart, @iend);
end;
end;
class function TGtk2WSCustomRichMemo.GetTextUIParams(
const AWinControl: TWinControl; TextStart: Integer;
var ui: TTextUIParam): Boolean;
var
TextWidget: PGtkWidget;
buffer : PGtkTextBuffer;
tag : PGtkTextTag;
istart : TGtkTextIter;
begin
ui.features:=[];
GetWidgetBuffer(AWinControl, TextWidget, buffer);
Result:=Assigned(buffer);
if not Result then Exit;
tag := gtk_text_tag_table_lookup(
gtk_text_buffer_get_tag_table(buffer), TagNameLink);
if Assigned(tag) then begin // which is odd.
gtk_text_buffer_get_iter_at_offset (buffer, @istart, TextStart);
if gtk_text_iter_has_tag(@istart, tag) then
Include(ui.features, uiLink);
end;
end;
class procedure TGtk2WSCustomRichMemo.InDelText(const AWinControl: TWinControl;
const TextUTF8: String; DstStart, DstLen: Integer);
var
w : PGtkWidget;
b : PGtkTextBuffer;
istart : TGtkTextIter;
iend : TGtkTextIter;
begin
GetWidgetBuffer(AWinControl, w, b);
if not Assigned(b) then Exit;
gtk_text_buffer_get_iter_at_offset (b, @istart, DstStart);
gtk_text_buffer_get_iter_at_offset (b, @iend, DstStart+DstLen);
gtk_text_buffer_delete(b, @istart, @iend);
if length(TextUTF8)>0 then
gtk_text_buffer_insert(b, @istart, @textUTF8[1], length(TextUTF8));
end;
class function TGtk2WSCustomRichMemo.CharAtPos(const AWinControl: TWinControl;
x, y: Integer): Integer;
var
w : PGtkWidget;
b : PGtkTextBuffer;
istart : TGtkTextIter;
gx, gy : gint;
trailing: gint;
begin
GetWidgetBuffer(AWinControl, w, b);
if not Assigned(w) then Exit;
gtk_text_view_window_to_buffer_coords(PGtkTextView(w), GTK_TEXT_WINDOW_WIDGET, x, y, @gx, @gy);
gtk_text_view_get_iter_at_position(PGtkTextView(w), @istart, @trailing, gx,gy);
Result:=gtk_text_iter_get_offset(@istart)+trailing;
end;
procedure UTF8CharsToWideString(const p: Pchar; var w: WideString);
var
slen : Integer;
cnt: Integer;
sz: SizeUInt;
begin
if not Assigned(p) then begin
w:='';
Exit;
end;
slen:=strlen(p);
if slen=0 then begin
w:='';
Exit;
end;
cnt:=UTF8Length(p, slen);
SetLength(w, cnt);
if cnt>0 then
ConvertUTF8ToUTF16( @w[1], length(w), p, slen, [toInvalidCharToSymbol], sz);
SetLength(w, sz);
end;
class function TGtk2WSCustomRichMemo.Search(const AWinControl: TWinControl;
const ANiddle: string; const SearchOpts: TIntSearchOpt): Integer;
var
TextWidget : PGtkWidget;
buffer : PGtkTextBuffer;
istart : TGtkTextIter;
iend : TGtkTextIter;
start_match : TGtkTextIter;
end_match : TGtkTextIter;
Found : Boolean;
opt : TGtkTextSearchFlags;
gstr : PChar;
txt : WideString;
sub : WIdeString;
const
GTK_TEXT_SEARCH_VISIBLE_ONLY = 1 shl 0; (* values of TGtkTextSearchFlags *)
{%H-}GTK_TEXT_SEARCH_TEXT_ONLY = 1 shl 1;
GTK_TEXT_SEARCH_CASE_INSENSITIVE = 1 shl 2;
begin
Result := -1;
GetWidgetBuffer(AWinControl, TextWidget, buffer);
if not Assigned(buffer) then Exit;
opt:=GTK_TEXT_SEARCH_VISIBLE_ONLY;
if not (soMatchCase in SearchOpts.Options) then begin
opt:=opt or GTK_TEXT_SEARCH_CASE_INSENSITIVE; // doesn't work anyway! it works in gtk3 only
gtk_text_buffer_get_iter_at_offset (buffer, @istart, SearchOpts.start);
gtk_text_buffer_get_iter_at_offset (buffer, @iend, SearchOpts.start+SearchOpts.len);
gtk_text_buffer_get_text(buffer, @istart, @iend, false);
gstr := gtk_text_buffer_get_text(Buffer, @istart, @iend, False);
if Assigned(gstr) then begin
UTF8CharsToWideString(gstr, txt);
g_free(gstr);
txt:=WideUpperCase(txt);
sub:=WideUpperCase(UTF8Decode(ANiddle));
Result:=Pos(sub,txt);
if Result>0 then
Result:=Result-1+SearchOpts.start
else
Result:=-1;
end else
Result:=-1;
end else begin
gtk_text_buffer_get_iter_at_offset(buffer, @istart, SearchOpts.start );
if not (soBackward in SearchOpts.Options) then
begin
gtk_text_buffer_get_iter_at_offset(buffer, @iend, SearchOpts.start+SearchOpts.len );
Found := gtk_text_iter_forward_search(@istart, PgChar(ANiddle), opt,
@start_match, @end_match, @iend)
end else begin
gtk_text_buffer_get_iter_at_offset(buffer, @iend, SearchOpts.start-SearchOpts.len);
Found := gtk_text_iter_backward_search(@istart, PgChar(ANiddle), opt,
@start_match, @end_match, @iend)
end;
if Found
then Result := gtk_text_iter_get_offset(@start_match)
else Result := -1;
end;
end;
class function TGtk2WSCustomRichMemo.ImageFromFile(
const ARichMemo: TCustomRichMemo; APos: Integer; const FileNameUTF8: string;
const AImgSize: TSize): Boolean;
var
t: PGtkWidget;
b: PGtkTextBuffer;
istart: TGtkTextIter;
pix: PGdkPixbuf;
err: PGError;
const
ScreenDPI = 96; // todo: might change, should be received dynamically
PageDPI = 72; // not expected to be changed
DPIFactor = ScreenDPI / PageDPI;
begin
Result:=false;
GetWidgetBuffer(ARichMemo, t, b);
if not Assigned(b) then Exit;
err:=nil;
if (AImgSize.cx=0) and (AImgSize.cy=0) then
pix := gdk_pixbuf_new_from_file(PChar(FileNameUTF8), @err)
else
pix := gdk_pixbuf_new_from_file_at_size(PChar(FileNameUTF8),
round(AImgSize.cx * DPIFactor), round(AImgSize.cy * DPIFactor), @err);
Result:=Assigned(pix);
if Result then begin
gtk_text_buffer_get_iter_at_offset(b, @istart, APos);
gtk_text_buffer_insert_pixbuf(b, @istart, pix);
end else
writeln(err^.message);
end;
class procedure TGtk2WSCustomRichMemo.SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer);
var
TextMark: PGtkTextMark;
CursorIter: TGtkTextIter;
w : PGtkWidget;
b : PGtkTextBuffer;
begin
GetWidgetBuffer(ACustomEdit, w, b);
if not Assigned(b) then Exit;
if NewStart = -1 then
begin
// always scroll so the cursor is visible
TextMark := gtk_text_buffer_get_insert(b);
gtk_text_buffer_get_iter_at_mark(b, @CursorIter, TextMark);
end
else begin
// SelStart was used and we should move to that location
gtk_text_buffer_get_iter_at_offset(b, @CursorIter, NewStart);
gtk_text_buffer_place_cursor(b, @CursorIter); // needed to move the cursor
TextMark := gtk_text_buffer_get_insert(b);
end;
gtk_text_view_scroll_to_mark(PGtkTextView(w), TextMark, 0, True, 0, 1);
end;
class procedure TGtk2WSCustomRichMemo.SetSelLength(
const ACustomEdit: TCustomEdit; NewLength: integer);
var
TextMark: PGtkTextMark;
StartIter,
EndIter: TGtkTextIter;
Offset: Integer;
w : PGtkWidget;
b : PGtkTextBuffer;
begin
GetWidgetBuffer(ACustomEdit, w, b);
if not Assigned(b) then Exit;
TextMark := gtk_text_buffer_get_insert(b);
gtk_text_buffer_get_iter_at_mark(b, @StartIter, TextMark);
Offset := gtk_text_iter_get_offset(@StartIter);
gtk_text_buffer_get_iter_at_offset(b, @EndIter, Offset+NewLength);
gtk_text_buffer_select_range(b, @StartIter, @EndIter);
end;
class procedure TGtk2WSCustomRichMemo.SetZoomFactor(
const AWinControl: TWinControl; AZoomFactor: Double);
var
w : PGtkWidget;
b : PGtkTextBuffer;
tag : PGtkTextTag;
istart : TGtkTextIter;
iend : TGtkTextIter;
p : PGtkTextAttributes;
sc : gdouble;
begin
GetWidgetBuffer(AWinControl, w, b);
if not Assigned(b) then Exit;
p:=GetAttrAtPos(AWinControl, 0);
sc:=p^.font_scale;
if sc=0 then sc:=1;
gtk_text_attributes_unref(p);
// restore the scale.
// for whatever reason, scale is always assumed as a multiplier!
// thus it is necessary to "unscale" the previous value as well
sc:=1/sc*AZoomFactor;
tag := gtk_text_buffer_create_tag(b, nil,
'scale', [ gdouble(sc),
'scale-set', gboolean(gTRUE),
nil]);
//gtk_text_buffer_get_start_iter(b, @istart);
gtk_text_buffer_get_iter_at_offset(b, @istart, 0);
gtk_text_buffer_get_end_iter(b, @iend);
gtk_text_buffer_apply_tag(b, tag, @istart, @iend);
//todo: set default font with scale
end;
function GtkDrawableDraw(widget: PGtkWidget;
event : PGdkEventExpose; gi: TGtk2InlineObject): gboolean; cdecl;
begin
if not Assigned(widget) then Exit;
if not gi.cnv.HandleAllocated then
gi.cnv.Handle:=GTK2WidgetSet.CreateDCForWidget(widget, event^.Window,false);
gi.il.Draw( gi.cnv, gi.Size);
Result:=gTRUE;
end;
class function TGtk2WSCustomRichMemo.InlineInsert(
const AWinControl: TWinControl; ATextStart, ATextLength: Integer;
const ASize: TSize; AHandler: TRichMemoInline;
var wsObj: TRichMemoInlineWSObject): Boolean;
var
w : PGtkWidget;
b : PGtkTextBuffer;
istart : TGtkTextIter;
anch : PGtkTextChildAnchor;
gi : TGtk2InlineObject;
draw : PGtkWidget;
sz : TSize;
const
ScreenDPI = 96; // todo: might change, should be received dynamically
PageDPI = 72; // not expected to be changed
DPIFactor = ScreenDPI / PageDPI;
begin
Result:=false;
GetWidgetBuffer(AWinControl, w, b);
if not Assigned(b) then Exit;
gi:=TGtk2InlineObject.Create;
gtk_text_buffer_get_iter_at_offset(b, @istart, ATextStart);
anch:=gtk_text_buffer_create_child_anchor(b, @istart);
draw:=gtk_drawing_area_new;
ConnectSignal( PGtkObject(draw), 'expose-event', @GtkDrawableDraw, gi);
sz.cx:=round(ASize.cx * DPIFactor);
sz.cy:=round(ASize.cy * DPIFactor);
gtk_widget_set_size_request(draw, sz.cx, sz.cy);
gtk_text_view_add_child_at_anchor(PGtkTextView(w), draw, anch);
gi.il:=AHandler;
gi.anch:=anch;
gi.wgt:=draw;
gi.size:=sz;
gi.cnv:=TCanvas.Create;
gtk_widget_show(draw);
gi.il.SetVisible(true);
wsObj:=gi;
Result:=true;
end;
class procedure TGtk2WSCustomRichMemo.InlineInvalidate(
const AWinControl: TWinControl; AHandler: TRichMemoInline;
wsObj: TRichMemoInlineWSObject);
var
gi : TGtk2InlineObject;
begin
if not Assigned(wsObj) or not (wsObj is TGtk2InlineObject) then Exit;
gi := TGtk2InlineObject(wsObj);
if Assigned(gi.wgt) then
gtk_widget_queue_draw(gi.wgt);
end;
class function TGtk2WSCustomRichMemo.GetTextAttributes(const AWinControl: TWinControl;
TextStart: Integer; var Params: TIntFontParams): Boolean;
var
attr : PGtkTextAttributes;
begin
GetAttributesAt(AWinControl, TextStart, false, attr, params);
Result := Assigned(attr);
if Result then gtk_text_attributes_unref(attr);
end;
function GtkInsertImageFromFile(const ARichMemo: TCustomRichMemo; APos: Integer;
const FileNameUTF8: string;
const AImgSize: TSize
): Boolean;
begin
Result:=TGtk2WSCustomRichMemo.ImageFromFile(ARichMemo, APos, FileNameUTF8, AImgSize);
end;
function GtkWSGetFontParams(fontref: HFONT; var params: TFontParams): Boolean;
var
gtkobj: PGDIObject;
pangofont: PPangoLayout;
PangoDesc: PPangoFontDescription;
sz : Integer;
isSzAbs : Boolean;
begin
gtkobj:=PGDIObject(fontref);
Result:=Assigned(gtkobj) and (gtkobj^.GDIType=gdiFont);
if not Result then Exit;
if gtkobj^.LogFont.lfFaceName = 'default' then begin
pangofont:=GTK2WidgetSet.GetDefaultGtkFont(False);
Result:=PANGO_IS_LAYOUT(pangofont);
if Result then
begin
// default font name
PangoDesc := pango_layout_get_font_description(pangofont);
if not Assigned(PangoDesc) then
PangoDesc := pango_context_get_font_description(pango_layout_get_context(pangofont));
params.Name := StrPas(pango_font_description_get_family(PangoDesc));
// default font size
if gtkobj^.LogFont.lfHeight = 0 then begin
isSzAbs := pango_font_description_get_size_is_absolute(PangoDesc);
sz := pango_font_description_get_size(PangoDesc);
if not isSzAbs then
params.Size := round(sz / PANGO_SCALE)
else
params.Size := round(sz/ScreenInfo.PixelsPerInchY*72);
end;
// rely on LogFont structure to be initialiazed (as it seems to be)
if gtkobj^.LogFont.lfItalic > 0 then Include(params.Style, fsItalic);
if gtkobj^.LogFont.lfWeight >= FW_BOLD then Include(params.Style, fsBold);
if gtkobj^.LogFont.lfUnderline > 0 then Include(params.Style, fsUnderline);
if gtkobj^.LogFont.lfStrikeOut > 0 then Include(params.Style, fsStrikeOut);
end;
end else
Result:=false;
end;
initialization
InsertImageFromFile := @GtkInsertImageFromFile;
WSGetFontParams:=@GtkWSGetFontParams;
end.