lazarus/lcl/interfaces/gtk3/gtk3private.pas

958 lines
28 KiB
ObjectPascal

{
*****************************************************************************
* gtk3private.pas *
* ------------- *
* *
* *
*****************************************************************************
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
}
unit gtk3private;
{$i gtk3defines.inc}
{$mode objfpc}{$H+}
interface
uses Classes, SysUtils, Controls, LazGtk3, LazGObject2, LazGLib2, LazUtf8Classes;
type
TGtkListStringsState = (glsItemCacheNeedsUpdate, glsCountNeedsUpdate);
TGtkListStringsStates = set of TGtkListStringsState;
// PPGtkListItem = ^PGtkListItem;
(*
{ TGtkListStringList }
TGtkListStringList = class(TStrings)
private
FGtkList : PGtkList;
FOwner: TWinControl;
FSorted : boolean;
FStates: TGtkListStringsStates;
FCachedCount: integer;
FCachedCapacity: integer;
FCachedItems: PPGtkListItem;
FUpdateCount: integer;
FWithCheckBox: Boolean;
protected
function GetListItem(Index: integer): PGtkListItem;
function GetLabel(Index: integer): PGtkLabel;
function GetCount: integer; override;
function Get(Index : Integer) : string; override;
function GetObject(Index: Integer): TObject; override;
procedure Put(Index: Integer; const S: string); override;
procedure PutObject(Index: Integer; AnObject: TObject); override;
procedure SetSorted(Val : boolean); virtual;
procedure CheckForInvalidFocus;
procedure ConnectItemCallbacks(Index: integer);
procedure ConnectItemCallbacks(Li: PGtkListItem); virtual;
procedure ConnectAllCallbacks; virtual;
procedure RemoveItemCallbacks(Index: integer);
procedure RemoveItemCallbacks(AItem: PGtkListItem); virtual;
procedure RemoveAllCallbacks; virtual;
procedure UpdateItemCache;
function CacheValid: boolean;
public
constructor Create(List : PGtkList; TheOwner: TWinControl;
const AWithCheckBox: Boolean);
destructor Destroy; override;
function Add(const S: string): Integer; override;
procedure Assign(Source : TPersistent); override;
procedure Clear; override;
procedure Delete(Index : integer); override;
function IndexOf(const S: string): Integer; override;
procedure Insert(Index : integer; const S: string); override;
function GetInsertPosition(const S: string): integer;
procedure Move(FromIndex, ToIndex: Integer); override;
procedure Sort; virtual;
function IsEqual(List: TStrings; CompareObjects: boolean): boolean;
procedure BeginUpdate;
procedure EndUpdate;
procedure ConsistencyCheck;
public
property Sorted: boolean read FSorted write SetSorted;
property Owner: TWinControl read FOwner;
end;
*)
{ TGtkListStoreStringList }
TGtkListStoreStringList = class(TStrings)
private
FChangeStamp: Integer;
FColumnIndex: Integer;
FGtkListStore: PGtkListStore;
FOwner: TWinControl;
FSorted: Boolean;
FStates: TGtkListStringsStates;
FCachedCount: Integer;
FCachedCapacity: Integer;
FCachedSize: Integer;
FCachedItems: PGtkTreeIter;
FUpdateCount: Integer;
protected
function GetCount: Integer; override;
function Get(Index: Integer): String; override;
function GetObject(Index: Integer): TObject; override;
procedure Put(Index: Integer; const S: String); override;
procedure PutObject(Index: Integer; AnObject: TObject); override;
procedure SetSorted(Val: Boolean);
procedure UpdateItemCache;
procedure GrowCache;
procedure ShrinkCache;
procedure IncreaseChangeStamp;
public
constructor Create(AListStore: PGtkListStore;
ColumnIndex: Integer; AOwner: TWinControl);
destructor Destroy; override;
function Add(const S: String): Integer; override;
procedure Assign(Source: TPersistent); override;
procedure Clear; override;
procedure Delete(Index: Integer); override;
function Find(const S: String; out Index: Integer): Boolean;
function IndexOf(const S: String): Integer; override;
procedure Insert(Index: Integer; const S: String); override;
procedure Move(CurIndex, NewIndex: Integer); override;
procedure Sort;
function IsEqual(List: TStrings): Boolean;
procedure BeginUpdate;
procedure EndUpdate;
public
property Sorted: Boolean read FSorted write SetSorted;
property Owner: TWinControl read FOwner;
property ChangeStamp: Integer read FChangeStamp;
end;
{ TGtk3MemoStrings }
TGtk3MemoStrings = class(TStrings)
private
FGtkText : PGtkTextView;
FGtkBuf: PGtkTextBuffer;
FTimerMove: guint;
FTimerSel: guint;
FOwner: TWinControl;
FQueueCursorMove: Integer;
FQueueSelLength: Integer;
protected
function GetTextStr: string; override;
function GetCount: integer; override;
function Get(Index : Integer) : string; override;
//procedure PutObject(Index: Integer; AObject: TObject); override;
//function GetObject(Index: Integer): TObject; override;
//procedure SetSorted(Val : boolean); virtual;
public
constructor Create(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;
procedure LoadFromFile(const FileName: string); override;
procedure SaveToFile(const FileName: string); override;
//procedure Sort; virtual;
procedure QueueCursorMove(APosition: Integer);
procedure QueueSelectLength(ALength: Integer);
public
//property Sorted: boolean read FSorted write SetSorted;
property Owner: TWinControl read FOwner;
property QueueSelLength: Integer read FQueueSelLength;
end;
implementation
uses StdCtrls, CheckLst, LCLProc, gtk3widgets, gtk3procs, Gtk3WSStdCtrls, Gtk3WSCheckLst;
{*************************************************************}
{ TGtkListStoreStringList methods }
{*************************************************************}
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.Create
Params:
Returns:
------------------------------------------------------------------------------}
constructor TGtkListStoreStringList.Create(AListStore: PGtkListStore;
ColumnIndex: Integer; AOwner: TWinControl);
begin
inherited Create;
if AListStore = nil
then RaiseGDBException('TGtkListStoreStringList.Create Unspecified list store');
FGtkListStore := AListStore;
if (ColumnIndex < 0) or (ColumnIndex >= gtk_tree_model_get_n_columns(PGtkTreeModel(FGtkListStore))) then
RaiseGDBException('TGtkListStoreStringList.Create Invalid Column Index');
FColumnIndex := ColumnIndex;
if AOwner = nil then
RaiseGDBException('TGtkListStoreStringList.Create Unspecified owner');
FOwner := AOwner;
FStates := [glsItemCacheNeedsUpdate, glsCountNeedsUpdate];
end;
destructor TGtkListStoreStringList.Destroy;
begin
FGtkListStore := nil;
// don't destroy the widgets
ReAllocMem(FCachedItems, 0);
inherited Destroy;
end;
function TGtkListStoreStringList.Add(const S: String): Integer;
begin
if FSorted then
Find(S, Result)
else
Result := Count;
//DebugLn(['TGtkListStoreStringList.Add ',S,' Count=',Result]);
Insert(Result, S);
end;
{------------------------------------------------------------------------------
Method: TGtkListStringList.SetSorted
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStoreStringList.SetSorted(Val: Boolean);
var
i: Integer;
begin
if Val = FSorted then Exit;
FSorted := Val;
if not FSorted then Exit;
for i := 0 to Count - 2 do
begin
if AnsiCompareText(Strings[i], Strings[i + 1]) < 0 then
begin
Sort;
Break;
end;
end;
end;
{------------------------------------------------------------------------------
procedure TGtkListStoreStringList.RemoveAllCallbacks;
------------------------------------------------------------------------------}
procedure TGtkListStoreStringList.UpdateItemCache;
var
i: Integer;
begin
if not (glsItemCacheNeedsUpdate in FStates) then exit;
//DebugLn(['TGtkListStoreStringList.UpdateItemCache ']); DumpStack;
FCachedSize := Count;
FCachedCapacity := Count;
ReAllocMem(FCachedItems, SizeOf(TGtkTreeIter) * FCachedCapacity);
if FGtkListStore <> nil then
for I := 0 to FCachedSize - 1 do
gtk_tree_model_iter_nth_child(PGtkTreeModel(FGtkListStore),
@FCachedItems[i], nil, I);
Exclude(FStates, glsItemCacheNeedsUpdate);
end;
procedure TGtkListStoreStringList.GrowCache;
begin
FCachedCapacity := ((FCachedCapacity * 5) div 4) + 10;
ReAllocMem(FCachedItems, SizeOf(TGtkTreeIter) * FCachedCapacity);
end;
procedure TGtkListStoreStringList.ShrinkCache;
begin
FCachedCapacity := FCachedSize + 1;
ReAllocMem(FCachedItems, SizeOf(TGtkTreeIter) * FCachedCapacity);
end;
procedure TGtkListStoreStringList.IncreaseChangeStamp;
begin
if FChangeStamp < High(FChangeStamp) then
Inc(FChangeStamp)
else
FChangeStamp := Low(FChangeStamp);
end;
procedure TGtkListStoreStringList.PutObject(Index: Integer; AnObject: TObject);
var
ListItem: TGtkTreeIter;
begin
if (Index < 0) or (Index >= Count)
then begin
RaiseGDBException('TGtkListStoreStringList.PutObject Out of bounds.');
Exit;
end;
if FGtkListStore = nil then Exit;
UpdateItemCache;
ListItem := FCachedItems[Index];
gtk_list_store_set(FGtkListStore, @ListItem, [FColumnIndex + 1, Pointer(AnObject), -1]);
IncreaseChangeStamp;
end;
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.Sort
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStoreStringList.Sort;
var
sl: TStringList;
OldSorted: Boolean;
begin
BeginUpdate;
// sort internally (sorting in the widget would be slow and unpretty ;)
sl := TStringList.Create;
sl.Assign(Self);
sl.Sort;
OldSorted := Sorted;
FSorted := False;
Assign(sl);
FSorted := OldSorted;
sl.Free;
EndUpdate;
end;
function TGtkListStoreStringList.IsEqual(List: TStrings): Boolean;
var
i, Cnt: Integer;
begin
if List = Self then Exit(True);
if List = nil then Exit(False);
Cnt := Count;
if (Cnt <> List.Count) then Exit(False);
for i := 0 to Cnt - 1 do
begin
if Strings[i] <> List[i] then Exit(False);
if Objects[i] <> List.Objects[i] then Exit(False);
end;
Result := True;
end;
procedure TGtkListStoreStringList.BeginUpdate;
begin
Inc(FUpdateCount);
end;
procedure TGtkListStoreStringList.EndUpdate;
begin
Dec(FUpdateCount);
end;
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.Assign
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStoreStringList.Assign(Source: TPersistent);
var
i, Cnt: Integer;
CmpList: TStrings;
OldSorted: Boolean;
begin
if (Source = Self) or (Source = nil) then Exit;
if ((Source is TGtkListStoreStringList)
and (TGtkListStoreStringList(Source).FGtkListStore = FGtkListStore)) then
RaiseGDBException('TGtkListStoreStringList.Assign: There are 2 lists with the same FGtkListStore');
BeginUpdate;
OldSorted := Sorted;
CmpList := nil;
try
if Source is TStrings then
begin
// clearing and resetting can change other properties of the widget,
// => don't change if the content is already the same
if Sorted then
begin
CmpList := TStringList.Create;
CmpList.Assign(TStrings(Source));
TStringList(CmpList).Sort;
end
else
CmpList := TStrings(Source);
if IsEqual(CmpList) then Exit;
Clear;
FSorted := False;
Cnt := TStrings(Source).Count;
for i := 0 to Cnt - 1 do
begin
AddObject(CmpList[i], CmpList.Objects[i]);
//DebugLn(['TGtkListStoreStringList.Assign ',i,' ',CmpList[i],' ',Count]);
end;
// ToDo: restore other settings
// Do not call inherited Assign as it does things we do not want to happen
end
else
inherited Assign(Source);
finally
fSorted := OldSorted;
if CmpList <> Source
then CmpList.Free;
EndUpdate;
end;
end;
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.Get
Params:
Returns:
------------------------------------------------------------------------------}
function TGtkListStoreStringList.Get(Index: Integer): String;
var
Item: PChar;
ListItem: TGtkTreeIter;
begin
Result := '';
if (Index < 0) or (Index >= Count)
then begin
RaiseGDBException('TGtkListStoreStringList.Get Out of bounds.');
Exit;
end;
if not Assigned(FOwner) or not FOwner.HandleAllocated then
begin
DebugLn('TGtkListStoreStringList.Get exiting, no owner or handle ');
exit;
end;
UpdateItemCache;
ListItem := FCachedItems[Index];
Item := nil;
if Gtk3IsWidget(TGtk3Widget(FOwner.Handle).Widget) and
(wtTreeModel in TGtk3Widget(FOwner.Handle).WidgetType) then
gtk_tree_model_get(PGtkTreeModel(FGtkListStore), @ListItem, [FColumnIndex, @Item, -1]);
if Item = nil then
Exit('');
Result := Item;
g_free(Item);
end;
function TGtkListStoreStringList.GetObject(Index: Integer): TObject;
var
ListItem: TGtkTreeIter;
begin
Result := nil;
if (Index < 0) or (Index >= Count)
then begin
RaiseGDBException('TGtkListStoreStringList.GetObject Out of bounds.');
Exit(nil);
end;
if FGtkListStore = nil then Exit(nil);
if not Assigned(FOwner) or not FOwner.HandleAllocated then
begin
// DebugLn('TGtkListStoreStringList.GetObject exiting, no owner or handle ');
exit(nil);
end;
UpdateItemCache;
ListItem := FCachedItems[Index];
if Gtk3IsWidget(TGtk3Widget(FOwner.Handle).Widget) and
(wtTreeModel in TGtk3Widget(FOwner.Handle).WidgetType) then
gtk_tree_model_get(PGtkTreeModel(FGtkListStore), @ListItem, [FColumnIndex + 1, @Result, -1]);
end;
procedure TGtkListStoreStringList.Put(Index: Integer; const S: String);
var
ListItem: TGtkTreeIter;
begin
if (Index < 0) or (Index >= Count)
then begin
RaiseGDBException('TGtkListStoreStringList.Put Out of bounds.');
Exit;
end;
if FGtkListStore = nil then Exit;
UpdateItemCache;
ListItem := FCachedItems[Index];
gtk_list_store_set(FGtkListStore, @ListItem, [FColumnIndex, PChar(S), -1]);
IncreaseChangeStamp;
end;
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.GetCount
Params:
Returns:
------------------------------------------------------------------------------}
function TGtkListStoreStringList.GetCount: Integer;
begin
if (glsCountNeedsUpdate in FStates) then
begin
if FGtkListStore <> nil then
FCachedCount := gtk_tree_model_iter_n_children(PGtkTreeModel(FGtkListStore), nil)
else
FCachedCount := 0;
Exclude(FStates, glsCountNeedsUpdate);
end;
Result := FCachedCount;
end;
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.Clear
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStoreStringList.Clear;
begin
//DebugLn(['TGtkListStoreStringList.Clear ']);
//while Count>0 do Delete(Count-1);
//Lock the widget to avoid trigger events
//Note: Assign/Clear is called inside CreateHandle before Handle is set
if FOwner.HandleAllocated then
begin
// WidgetInfo := GetWidgetInfo({%H-}Pointer(FOwner.Handle), False);
// Inc(WidgetInfo^.ChangeLock);
TGtk3Widget(FOwner.Handle).BeginUpdate;
try
gtk_list_store_clear(FGtkListStore);
//resize columns to optimal width. See issue #17837
//TODO: see if this is needed by TComboBox and others.
if wtListBox in TGtk3Widget(FOwner.Handle).WidgetType then
gtk_tree_view_columns_autosize(PGtkTreeView(TGtk3Widget(FOwner.Handle).GetContainerWidget));
finally
TGtk3Widget(FOwner.Handle).EndUpdate;
end;
// Dec(WidgetInfo^.ChangeLock);
// Update the internal Index cache
// PInteger(WidgetInfo^.UserData)^ := -1;
end;
IncreaseChangeStamp;
ReAllocMem(FCachedItems, 0);
FCachedCapacity := 0;
FCachedSize := 0;
Exclude(FStates, glsItemCacheNeedsUpdate);
FCachedCount := 0;
Exclude(FStates, glsCountNeedsUpdate);
end;
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.Delete
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStoreStringList.Delete(Index: Integer);
var
ListItem: TGtkTreeIter;
// WidgetInfo: PWidgetInfo;
begin
if not (glsItemCacheNeedsUpdate in FStates) then
ListItem := FCachedItems[Index]
else
gtk_tree_model_iter_nth_child(PGtkTreeModel(FGtkListStore), @ListItem, nil, Index);
//gtk_list_store_g
// WidgetInfo := GetWidgetInfo({%H-}Pointer(FOwner.Handle));
//Lock the widget to avoid trigger events
// Inc(WidgetInfo^.ChangeLock);
TGtk3Widget(FOwner.Handle).BeginUpdate;
gtk_list_store_remove(FGtkListStore, @ListItem);
TGtk3Widget(FOwner.Handle).EndUpdate;
// Dec(WidgetInfo^.ChangeLock);
IncreaseChangeStamp;
if not (glsCountNeedsUpdate in FStates) then
Dec(FCachedCount);
if (not (glsItemCacheNeedsUpdate in FStates)) and (Index = Count) then
begin
// cache is valid and the last item was deleted -> just remove last item
Dec(FCachedSize);
if (FCachedSize < FCachedCapacity div 2) then
ShrinkCache;
end
else
Include(FStates, glsItemCacheNeedsUpdate);
if wtComboBox in TGtk3Widget(FOwner.Handle).WidgetType then
begin
TGtk3WSCustomComboBox.SetText(FOwner, '');
//Update the internal Index cache
// PInteger(WidgetInfo^.UserData)^ := -1;
end;
end;
function TGtkListStoreStringList.Find(const S: String; out Index: Integer): Boolean;
var
L, R, I: Integer;
CompareRes: Integer;
begin
Result := False;
// Use binary search.
L := 0;
R := Count - 1;
while (L <= R) do
begin
I := L + (R - L) div 2;
CompareRes := AnsiCompareText(S, Strings[I]);
if (CompareRes > 0) then
L := I + 1
else
begin
R := I - 1;
if (CompareRes = 0) then
begin
Result := True;
L := I; // forces end of while loop
end;
end;
end;
Index := L;
end;
function TGtkListStoreStringList.IndexOf(const S: String): Integer;
begin
Result := -1;
BeginUpdate;
if FSorted then
begin
//Binary Search
if not Find(S, Result) then
Result := -1;
end else
Result := inherited IndexOf(S);
EndUpdate;
end;
{------------------------------------------------------------------------------
Method: TGtkListStoreStringList.Insert
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStoreStringList.Insert(Index: Integer; const S: String);
var
li: TGtkTreeIter;
LCLIndex: PInteger;
begin
if (Index < 0) or (Index > Count)
then begin
RaiseGDBException('TGtkListStoreStringList.Insert: Index ' + IntToStr(Index) + ' out of bounds. Count=' + IntToStr(Count));
Exit;
end;
if Owner = nil
then begin
RaiseGDBException('TGtkListStoreStringList.Insert Unspecified owner');
Exit;
end;
BeginUpdate;
try
// this call is few times faster than gtk_list_store_insert, gtk_list_store_set
gtk_list_store_insert_with_values(FGtkListStore, @li, Index, [FColumnIndex, PChar(S), -1]);
IncreaseChangeStamp;
if not (glsCountNeedsUpdate in FStates) then
Inc(FCachedCount);
if (not (glsItemCacheNeedsUpdate in FStates)) and (Index = Count - 1) then
begin
// cache is valid and item was added as last
// Add item to cache (instead of updating the whole cache)
// This accelerates Assign.
if FCachedSize = FCachedCapacity then GrowCache;
FCachedItems[FCachedSize] := li;
Inc(FCachedSize);
end
else
Include(FStates, glsItemCacheNeedsUpdate);
finally
EndUpdate;
end;
end;
procedure TGtkListStoreStringList.Move(CurIndex, NewIndex: Integer);
const
AState: Array[Boolean] of TCheckBoxState = (cbUnchecked, cbChecked);
var
AItemChecked: Boolean;
begin
if FOwner is TCheckListBox then
AItemChecked := TCheckListBox(FOwner).Checked[CurIndex];
inherited Move(CurIndex, NewIndex);
if FOwner is TCheckListBox then
TGtk3WSCustomCheckListBox.SetState(TCustomCheckListBox(FOwner),
NewIndex, AState[AItemChecked]);
end;
function UpdateMemoCursorCB(AStrings: TGtk3MemoStrings): gboolean; cdecl;
var
TextMark: PGtkTextMark;
CursorIter: TGtkTextIter;
begin
Result := gtk_false; // stop this timer
if AStrings.FQueueCursorMove = -1 then
begin
// always scroll so the cursor is visible
TextMark := gtk_text_buffer_get_insert(AStrings.FGtkBuf);
gtk_text_buffer_get_iter_at_mark(AStrings.FGtkBuf, @CursorIter, TextMark);
end
else begin
// SelStart was used and we should move to that location
gtk_text_buffer_get_iter_at_offset(AStrings.FGtkBuf, @CursorIter, AStrings.FQueueCursorMove);
gtk_text_buffer_place_cursor(AStrings.FGtkBuf, @CursorIter); // needed to move the cursor
TextMark := gtk_text_buffer_get_insert(AStrings.FGtkBuf);
end;
gtk_text_view_scroll_to_mark(AStrings.FGtkText, TextMark, 0, True, 0, 1);
AStrings.FQueueCursorMove := 0;
end;
function UpdateMemoSelLengthCB(AStrings: TGtk3MemoStrings): gboolean; cdecl;
var
TextMark: PGtkTextMark;
StartIter,
EndIter: TGtkTextIter;
Offset: Integer;
begin
Result := gtk_false; // stop this timer
TextMark := gtk_text_buffer_get_insert(AStrings.FGtkBuf);
gtk_text_buffer_get_iter_at_mark(AStrings.FGtkBuf, @StartIter, TextMark);
Offset := gtk_text_iter_get_offset(@StartIter);
gtk_text_buffer_get_iter_at_offset(AStrings.FGtkBuf, @EndIter, Offset+AStrings.FQueueSelLength);
gtk_text_buffer_select_range(AStrings.FGtkBuf, @StartIter, @EndIter);
AStrings.FQueueSelLength := -1;
end;
function TGtk3MemoStrings.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 TGtk3MemoStrings.GetCount: integer;
begin
Result := gtk_text_buffer_get_line_count(FGtkBuf);
if Get(Result-1) = '' then Dec(Result);
end;
function TGtk3MemoStrings.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 TGtk3MemoStrings.Create(TheOwner: TWinControl);
begin
inherited Create;
if TheOwner = nil then RaiseGDBException(
'TGtk3MemoStrings.Create Unspecified owner');
FGtkText:= PGtkTextView(TGtk3Widget(TheOwner.Handle).GetContainerWidget);
FGtkBuf := FGtkText^.get_buffer;
FOwner:=TheOwner;
FQueueSelLength := -1;
FTimerMove := 0;
FTimerSel := 0;
end;
destructor TGtk3MemoStrings.Destroy;
begin
// gtk_timeout_remove(FTimerSel);
// gtk_timeout_remove(FTimerMove);
// don't destroy the widgets
inherited Destroy;
end;
procedure TGtk3MemoStrings.Assign(Source: TPersistent);
var
S: TStrings absolute Source;
begin
if Source is TStrings then
begin
// 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 TGtk3MemoStrings.AddStrings(TheStrings: TStrings);
begin
SetTextStr(GetTextStr + TStrings(TheStrings).Text);
end;
procedure TGtk3MemoStrings.Clear;
begin
SetText('');
end;
procedure TGtk3MemoStrings.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 TGtk3MemoStrings.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 TGtk3MemoStrings.SetTextStr(const Value: string);
begin
if (Value <> Text) then
begin
TGtk3Widget(FOwner.Handle).BeginUpdate;
gtk_text_buffer_set_text(FGtkBuf, PChar(Value), -1);
TGtk3Widget(FOwner.Handle).EndUpdate;
end;
end;
procedure TGtk3MemoStrings.LoadFromFile(const FileName: string);
var
TheStream: TFileStreamUTF8;
begin
TheStream:=TFileStreamUtf8.Create(FileName,fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(TheStream);
finally
TheStream.Free;
end;
end;
procedure TGtk3MemoStrings.SaveToFile(const FileName: string);
var
TheStream: TFileStreamUTF8;
begin
TheStream:=TFileStreamUtf8.Create(FileName,fmCreate);
try
SaveToStream(TheStream);
finally
TheStream.Free;
end;
end;
procedure TGtk3MemoStrings.QueueCursorMove(APosition: Integer);
begin
// needed because there is a callback that updates the GtkBuffer
// internally so that it actually knows where the cursor is
if FQueueCursorMove = 0 then
FTimerMove := g_timeout_add(0,TGSourceFunc(@UpdateMemoCursorCB), Pointer(Self));
FQueueCursorMove := APosition;
end;
procedure TGtk3MemoStrings.QueueSelectLength(ALength: Integer);
begin
// needed because there is a callback that updates the GtkBuffer
// internally so that it actually knows where the cursor is
if FQueueSelLength = -1 then
FTimerSel := g_timeout_add(0,TGSourceFunc(@UpdateMemoSelLengthCB), Pointer(Self));
FQueueSelLength := ALength;
end;
end.