mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-30 17:43:44 +02:00
1038 lines
32 KiB
PHP
1038 lines
32 KiB
PHP
{%MainUnit gtkint.pp}
|
|
{******************************************************************************
|
|
gtklistsl.inc
|
|
TGtkListStringList and TGtkCListStringList
|
|
|
|
******************************************************************************
|
|
|
|
*****************************************************************************
|
|
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.
|
|
*****************************************************************************
|
|
}
|
|
|
|
|
|
{*************************************************************
|
|
Default compare functions
|
|
*************************************************************}
|
|
|
|
{function DefaultCompareFunc(a, b : gpointer) : gint; cdecl;
|
|
var AStr, BStr : PChar;
|
|
begin
|
|
gtk_label_get(PGtkLabel(PGtkBin(a)^.child), @AStr);
|
|
gtk_label_get(PGtkLabel(PGtkBin(b)^.child), @BStr);
|
|
Result:= strcomp(AStr, BStr);
|
|
end;}
|
|
|
|
{function DefaultCheckCompareFunc(a, b : gpointer) : gint; cdecl;
|
|
var AStr, BStr : PChar;
|
|
begin
|
|
gtk_label_get(PPointer(PGTKBox(PGtkBin(a)^.child)^.Children^.Next^.Data)^, @AStr);
|
|
gtk_label_get(PPointer(PGTKBox(PGtkBin(b)^.child)^.Children^.Next^.Data)^, @BStr);
|
|
Result:= strcomp(AStr, BStr);
|
|
end;}
|
|
|
|
{------------------------------------------------------------------------------
|
|
function gtkListItemDrawCB(Widget: PGtkWidget; area: PGDKRectangle;
|
|
data: gPointer) : GBoolean; cdecl;
|
|
|
|
Handler for draw events of every item in a TGtkListStringList.
|
|
------------------------------------------------------------------------------}
|
|
function gtkListItemDrawAfterCB(Widget: PGtkWidget; {%H-}area: PGDKRectangle;
|
|
data: gPointer): GBoolean; cdecl;
|
|
var
|
|
Msg: TLMDrawListItem;
|
|
ItemIndex: integer;
|
|
GtkList: PGtkList;
|
|
AreaRect: TRect;
|
|
State: TOwnerDrawState;
|
|
LCLList: TGtkListStringList;
|
|
begin
|
|
Result:=true;
|
|
|
|
//DebugLn('gtkListItemDrawCB ');
|
|
|
|
// get context
|
|
GtkList:=PGtkList(g_object_get_data(PGObject(Data),GtkListItemGtkListTag));
|
|
if GtkList=nil then
|
|
RaiseGDBException('gtkListItemDrawAfterCB GtkList=nil');
|
|
LCLList:=TGtkListStringList(g_object_get_data(PGObject(Data),
|
|
GtkListItemLCLListTag));
|
|
if LCLList=nil then
|
|
RaiseGDBException('gtkListItemDrawAfterCB LCLList=nil');
|
|
if [csDestroying,csLoading]*LCLList.Owner.ComponentState<>[] then exit;
|
|
|
|
// only owner draw lists are interested in drawing items themselves.
|
|
if LclList.Owner is TCustomListbox then
|
|
if TCustomListbox(LCLList.Owner).Style = lbStandard then
|
|
exit;
|
|
if LclList.Owner is TCustomCombobox then
|
|
if not TCustomCombobox(LclList.Owner).Style.IsOwnerDrawn then
|
|
exit;
|
|
|
|
// get itemindex and area
|
|
ItemIndex:=g_list_index(GtkList^.children,Data);
|
|
with Widget^.allocation do begin
|
|
AreaRect:=Bounds(x,y,width,height);
|
|
end;
|
|
|
|
// collect state flags
|
|
State:=[odBackgroundPainted];
|
|
if g_list_index(GtkList^.selection,Widget)>=0 then
|
|
Include(State,odSelected);
|
|
if not GTK_WIDGET_SENSITIVE(Widget) then
|
|
Include(State,odInactive);
|
|
if GTK_WIDGET_HAS_DEFAULT(Widget) then
|
|
Include(State,odDefault);
|
|
if GTK_WIDGET_HAS_FOCUS(Widget) then
|
|
Include(State,odFocused);
|
|
|
|
// create message and deliver
|
|
FillChar(Msg{%H-},SizeOf(Msg),0);
|
|
Msg.Msg:=LM_DrawListItem;
|
|
New(Msg.DrawListItemStruct);
|
|
try
|
|
FillChar(Msg.DrawListItemStruct^,SizeOf(TDrawListItemStruct),0);
|
|
with Msg.DrawListItemStruct^ do begin
|
|
ItemID:=ItemIndex;
|
|
Area:=AreaRect;
|
|
DC:=GetDC(HWnd({%H-}PtrUInt(Widget)));
|
|
ItemState:=State;
|
|
end;
|
|
//DebugLn('gtkListItemDrawAfterCB ',DbgSName(LCLList.Owner),' Widget=',DbgS(Widget));
|
|
Result := DeliverMessage(LCLList.Owner, Msg)=0;
|
|
ReleaseDC(HWnd({%H-}PtrUInt(Widget)),Msg.DrawListItemStruct^.DC);
|
|
finally
|
|
Dispose(Msg.DrawListItemStruct);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function gtkListItemExposeEvent(Widget: PGtkWidget;
|
|
Event : PGdkEventExpose; data: gPointer): GBoolean; cdecl;
|
|
|
|
GTK2 helper for drawing every item in a TGtkListStringList.
|
|
------------------------------------------------------------------------------}
|
|
function gtkListItemExposeEvent(Widget: PGtkWidget;
|
|
Event : PGdkEventExpose; data: gPointer): GBoolean; cdecl;
|
|
begin
|
|
Result := gtkListItemDrawAfterCB(Widget, @Event^.Area, data);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function gtkListItemToggledCB(Widget: PGtkWidget; Data: gPointer): GBoolean; cdecl;
|
|
|
|
Called when a toggle button has change in a
|
|
TGtkListStringList (TCustomCheckListBox).
|
|
------------------------------------------------------------------------------}
|
|
function gtkListItemToggledCB(Widget: PGtkWidget; Data: gPointer): GBoolean; cdecl;
|
|
var
|
|
GtkList: PGtkList;
|
|
LCLList: TGtkListStringList;
|
|
Mess: TLMessage;
|
|
ItemIndex: LongInt;
|
|
begin
|
|
Result:=true;
|
|
|
|
//DebugLn('gtkListItemDrawCB ');
|
|
|
|
// get context
|
|
GtkList:=PGtkList(g_object_get_data(PGObject(Data),GtkListItemGtkListTag));
|
|
if GtkList=nil then
|
|
RaiseGDBException('gtkListItemToggledCB GtkList=nil');
|
|
LCLList:=TGtkListStringList(g_object_get_data(PGObject(Data),
|
|
GtkListItemLCLListTag));
|
|
if LCLList=nil then
|
|
RaiseGDBException('gtkListItemToggledCB LCLList=nil');
|
|
if [csDestroying,csLoading]*LCLList.Owner.ComponentState<>[] then exit;
|
|
|
|
// get itemindex and area
|
|
ItemIndex:=g_list_index(GtkList^.children,Data);
|
|
|
|
if LockOnChange({%H-}PgtkObject(LCLList.Owner.Handle),0) > 0 then Exit;
|
|
|
|
if GtkWidgetIsA(Widget,gtk_toggle_button_get_type) then begin
|
|
g_object_set_data(PGObject(Widget), 'Grayed', nil);
|
|
end;
|
|
|
|
Mess.Msg := LM_CHANGED;
|
|
Mess.Result := 0;
|
|
Mess.WParam := ItemIndex;
|
|
//DebugLn(['gtkListItemToggledCB ',ItemIndex]);
|
|
DeliverMessage(LCLList.Owner, Mess);
|
|
end;
|
|
|
|
procedure GtkListItemSelectAfterCB(Widget: PGtkWidget; data: gpointer); cdecl;
|
|
|
|
procedure RaiseGTKListNotFound;
|
|
var
|
|
s: String;
|
|
ChildWidget: PGtkWidget;
|
|
BoxWidget: PGtkBox;
|
|
LabelWidget: PGtkLabel;
|
|
LabelText: PChar;
|
|
begin
|
|
s:='gtkListItemSelectAfterCB GtkList=nil li='+dbgs(Widget);
|
|
ChildWidget:=PGtkBin(Widget)^.child;
|
|
LabelWidget:=nil;
|
|
if GtkWidgetIsA(ChildWidget,gtk_label_get_type) then
|
|
LabelWidget:=PGTKLabel(ChildWidget)
|
|
else if GtkWidgetIsA(ChildWidget,gtk_box_get_type) then begin
|
|
BoxWidget:=PGTKBox(ChildWidget);
|
|
if (BoxWidget^.Children<>nil)
|
|
and (BoxWidget^.Children^.Next<>nil) then begin
|
|
LabelWidget:=BoxWidget^.Children^.Next^.Data;
|
|
if not GtkWidgetIsA(PGtkWidget(LabelWidget),gtk_label_get_type) then
|
|
LabelWidget:=nil;
|
|
end;
|
|
end;
|
|
if LabelWidget<>nil then begin
|
|
LabelText:=nil;
|
|
gtk_label_get(LabelWidget, @LabelText);
|
|
s:=s+' Text="'+DbgStr(StrPas(LabelText))+'"';
|
|
end;
|
|
RaiseGDBException(s);
|
|
end;
|
|
|
|
var
|
|
GtkList: PGtkList;
|
|
LCLList: TGtkListStringList;
|
|
//ItemIndex: LongInt;
|
|
Mess: TLMessage;
|
|
begin
|
|
{$IFDEF EventTrace}
|
|
Debugln('gtkListItemSelectAfterCB');
|
|
{$ENDIF}
|
|
// get context
|
|
GtkList:=PGtkList(g_object_get_data(PGObject(Data),GtkListItemGtkListTag));
|
|
if GtkList=nil then
|
|
RaiseGTKListNotFound;
|
|
LCLList:=TGtkListStringList(g_object_get_data(PGObject(Data),
|
|
GtkListItemLCLListTag));
|
|
if LCLList=nil then
|
|
RaiseGDBException('gtkListItemSelectAfterCB LCLList=nil');
|
|
if [csDestroying,csLoading]*LCLList.Owner.ComponentState<>[] then exit;
|
|
|
|
// get itemindex and area
|
|
//ItemIndex:=g_list_index(GtkList^.children,Data);
|
|
|
|
if LockOnChange({%H-}PGtkObject(LCLList.Owner.Handle),0) > 0 then Exit;
|
|
|
|
Mess.Msg := LM_SELCHANGE;
|
|
Mess.Result := 0;
|
|
DeliverMessage(LCLList.Owner, Mess);
|
|
end;
|
|
|
|
{*************************************************************}
|
|
{ TGtkListStringList methods }
|
|
{*************************************************************}
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.Create
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
constructor TGtkListStringList.Create(List: PGtkList; TheOwner: TWinControl;
|
|
const AWithCheckBox: Boolean);
|
|
begin
|
|
inherited Create;
|
|
if List = nil then RaiseGDBException(
|
|
'TGtkListStringList.Create Unspecified list widget');
|
|
FGtkList:= List;
|
|
if TheOwner = nil then RaiseGDBException(
|
|
'TGtkListStringList.Create Unspecified owner');
|
|
FOwner:=TheOwner;
|
|
FWithCheckBox := AWithCheckBox;
|
|
//DebugLn('TGtkListStringList.Create Self=',DbgS(Self),' List=',DbgS(List),' Owner=',DbgS(Owner));
|
|
Include(FStates,glsItemCacheNeedsUpdate);
|
|
ConnectAllCallbacks;
|
|
{$IFDEF CheckGtkList}
|
|
ConsistencyCheck;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
destructor TGtkListStringList.Destroy;
|
|
begin
|
|
// don't destroy the widgets
|
|
RemoveAllCallbacks;
|
|
ReAllocMem(FCachedItems,0);
|
|
FCachedItems:=nil;
|
|
FCachedCount:=0;
|
|
FCachedCapacity:=0;
|
|
//DebugLn('TGtkListStringList.Destroy Self=',DbgS(Self),' List=',DbgS(FGtkList),' Owner=',DbgS(Owner));
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TGtkListStringList.Add(const S: string): Integer;
|
|
begin
|
|
if FSorted then begin
|
|
Result := GetInsertPosition(S);
|
|
Insert(Count,S);
|
|
end else begin
|
|
Result:=Count;
|
|
Insert(Result,S);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.SetSorted
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.SetSorted(Val : boolean);
|
|
begin
|
|
if Val <> FSorted then begin
|
|
FSorted:= Val;
|
|
if FSorted then Sort;
|
|
end;
|
|
end;
|
|
|
|
procedure TGtkListStringList.CheckForInvalidFocus;
|
|
var
|
|
Window: PGtkWindow;
|
|
begin
|
|
{ This procedure works round a gtk problem - a deleted item may have the focus
|
|
according to an enclosing window, but the enclosing window does not notice
|
|
that the item has gone. }
|
|
Window := PGtkWindow(gtk_widget_get_ancestor(PGtkWidget(FGtkList),
|
|
gtk_window_get_type));
|
|
if (Window <> nil) and (Window^.focus_widget <> nil)
|
|
and (gtk_widget_get_ancestor(Window^.focus_widget, gtk_list_get_type)
|
|
= PGtkWidget(FGtkList))
|
|
then
|
|
Window^.focus_widget := nil;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkListStringList.ConnectItemCallbacks(Index: integer);
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.ConnectItemCallbacks(Index: integer);
|
|
var
|
|
ListItem: PGtkListItem;
|
|
begin
|
|
UpdateItemCache;
|
|
{$IFDEF EventTrace}
|
|
Debugln( 'connect ',strings[index]);
|
|
{$ENDIF}
|
|
ListItem:=FCachedItems[Index];
|
|
ConnectItemCallbacks(ListItem);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkListStringList.ConnectItemCallbacks(Li: PGtkListItem);
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.ConnectItemCallbacks(Li: PGtkListItem);
|
|
var
|
|
ChildWidget: Pointer;
|
|
begin
|
|
{$IFDEF EventTrace}
|
|
Debugln('connect itemCallback');
|
|
{$ENDIF}
|
|
g_object_set_data(PGObject(li),GtkListItemLCLListTag,Self);
|
|
g_object_set_data(PGObject(li),GtkListItemGtkListTag,FGtkList);
|
|
//DebugLn('TGtkListStringList.ConnectItemCallbacks Self=',DbgS(Self),
|
|
//' GtkList=',DbgS(FGtkList),
|
|
//' Owner=',DbgS(Owner),'=',Owner.ClassName,
|
|
//' LI=',DbgS(LI),
|
|
//' ');
|
|
//DebugLn(['TGtkListStringList.ConnectItemCallbacks ',DbgSName(Owner)]);
|
|
g_signal_connect_after(G_OBJECT(li), 'expose_event',
|
|
G_CALLBACK(@gtkListItemExposeEvent), li);
|
|
if FWithCheckBox then begin
|
|
ChildWidget := PPointer(PGTKBox(PGtkBin(Li)^.child)^.Children^.Data)^;
|
|
g_signal_connect_after(G_OBJECT(ChildWidget), 'toggled',
|
|
G_CALLBACK(@gtkListItemToggledCB), li);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkListStringList.ConnectAllCallbacks;
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.ConnectAllCallbacks;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
BeginUpdate;
|
|
UpdateItemCache;
|
|
i := FCachedCount - 1;
|
|
while i >= 0 do
|
|
begin
|
|
{$IFDEF EventTrace}
|
|
DebugLn('connect ',strings[i]);
|
|
{$ENDIF}
|
|
ConnectItemCallbacks(FCachedItems[i]);
|
|
Dec(i);
|
|
end;
|
|
EndUpdate;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkListStringList.RemoveItemCallbacks(Index: integer);
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.RemoveItemCallbacks(Index: integer);
|
|
begin
|
|
UpdateItemCache;
|
|
RemoveItemCallbacks(FCachedItems[Index]);
|
|
end;
|
|
|
|
procedure TGtkListStringList.RemoveItemCallbacks(AItem: PGtkListItem);
|
|
var
|
|
ChildWidget: Pointer;
|
|
begin
|
|
{$IFDEF EventTrace}
|
|
Debugln('connect itemCallback');
|
|
{$ENDIF}
|
|
g_object_set_data(PGObject(AItem), GtkListItemLCLListTag, nil);
|
|
g_object_set_data(PGObject(AItem), GtkListItemGtkListTag, nil);
|
|
|
|
g_signal_handlers_disconnect_by_func(
|
|
G_OBJECT(AItem), G_CALLBACK(@gtkListItemExposeEvent), AItem);
|
|
if FWithCheckBox
|
|
then begin
|
|
ChildWidget := PPointer(PGTKBox(PGtkBin(AItem)^.child)^.Children^.Data)^;
|
|
gtk_signal_disconnect_by_func(
|
|
PGtkObject(ChildWidget), TGTKSignalFunc(@gtkListItemToggledCB), AItem);
|
|
FreeWidgetInfo(ChildWidget);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TGtkListStringList.RemoveAllCallbacks;
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.RemoveAllCallbacks;
|
|
var
|
|
i: integer;
|
|
begin
|
|
BeginUpdate;
|
|
UpdateItemCache;
|
|
i := FCachedCount - 1;
|
|
while i >= 0 do
|
|
begin
|
|
RemoveItemCallbacks(FCachedItems[i]);
|
|
Dec(i);
|
|
end;
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TGtkListStringList.UpdateItemCache;
|
|
var
|
|
CurListItem: PGList;
|
|
i: integer;
|
|
begin
|
|
if not (glsItemCacheNeedsUpdate in FStates) then exit;
|
|
|
|
{$IFDEF DebugLCLComponents}
|
|
// if items were removed => mark them as destroyed
|
|
for i:=0 to FCachedCount-1 do begin
|
|
if (FGtkList=nil)
|
|
or (g_list_find(FGtkList^.children,FCachedItems[i])=nil) then begin
|
|
if DebugGtkWidgets.IsCreated(FCachedItems[i]) then begin
|
|
DebugLn(['TGtkListStringList.UpdateItemCache item vanished: ',i]);
|
|
DebugGtkWidgets.MarkDestroyed(FCachedItems[i]);
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
if (FGtkList<>nil) and (FGtkList^.children<>nil) then
|
|
FCachedCount:=g_list_length(FGtkList^.children)
|
|
else
|
|
FCachedCount:=0;
|
|
if FCachedCount=0 then
|
|
FCachedCapacity:=0
|
|
else begin
|
|
FCachedCapacity:=1;
|
|
while FCachedCapacity<FCachedCount do
|
|
FCachedCapacity:=FCachedCapacity shl 1;
|
|
FCachedCapacity:=FCachedCapacity shl 1;
|
|
end;
|
|
ReAllocMem(FCachedItems,SizeOf(PGtkListItem)*FCachedCapacity);
|
|
if FGtkList<>nil then begin
|
|
CurListItem:=FGtkList^.children;
|
|
i:=0;
|
|
while CurListItem<>nil do begin
|
|
FCachedItems[i]:=PGtkListItem(CurListItem^.Data);
|
|
{$IFDEF DebugLCLComponents}
|
|
if not DebugGtkWidgets.IsCreated(PGtkListItem(CurListItem^.Data)) then
|
|
begin
|
|
DebugLn(['TGtkListStringList.UpdateItemCache unknown item ',i,' ',DbgSName(Owner)]);
|
|
DumpStack;
|
|
end;
|
|
{$ENDIF}
|
|
inc(i);
|
|
CurListItem:=CurListItem^.Next;
|
|
end;
|
|
end;
|
|
Exclude(FStates,glsItemCacheNeedsUpdate);
|
|
end;
|
|
|
|
function TGtkListStringList.CacheValid: boolean;
|
|
begin
|
|
Result:=not (glsItemCacheNeedsUpdate in FStates);
|
|
end;
|
|
|
|
procedure TGtkListStringList.PutObject(Index: Integer; AnObject: TObject);
|
|
var
|
|
ListItem : PGtkListItem;
|
|
begin
|
|
//DebugLn('[TGtkListStringList.PutObject] Index=',Index,' Count=',Count);
|
|
ListItem:=GetListItem(Index);
|
|
if ListItem <> nil then
|
|
g_object_set_data(PGObject(ListItem),'LCLStringsObject',AnObject);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.Sort
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.Sort;
|
|
var
|
|
sl: TStringList;
|
|
begin
|
|
BeginUpdate;
|
|
// sort internally (sorting in the widget would be slow and unpretty ;)
|
|
sl:=TStringList.Create;
|
|
sl.Assign(Self);
|
|
sl.Sort; // currently this is quicksort ->
|
|
// Disadvantages: - worst case on sorted list
|
|
// - not keeping order
|
|
// ToDo: replace by mergesort and add customsort
|
|
// remember selected items
|
|
Assign(sl);
|
|
sl.Free;
|
|
EndUpdate;
|
|
end;
|
|
|
|
function TGtkListStringList.IsEqual(List: TStrings;
|
|
CompareObjects: boolean): boolean;
|
|
var
|
|
i, Cnt: integer;
|
|
CmpList: TStringList;
|
|
begin
|
|
if List=Self then begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
Result:=false;
|
|
if List=nil then exit;
|
|
Cnt:=Count;
|
|
if (Cnt<>List.Count) then exit;
|
|
BeginUpdate;
|
|
CmpList:=TStringList.Create;
|
|
try
|
|
CmpList.Assign(List);
|
|
CmpList.Sorted:=FSorted;
|
|
for i:=0 to Cnt-1 do begin
|
|
if (Strings[i]<>CmpList[i])
|
|
or (CompareObjects and (Objects[i]<>CmpList.Objects[i])) then
|
|
exit;
|
|
end;
|
|
finally
|
|
CmpList.Free;
|
|
EndUpdate;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TGtkListStringList.BeginUpdate;
|
|
begin
|
|
//NOTE: in TComboBox, event handling is done inside the 'changed' event
|
|
// of the entry widget. Here we are locking the main combo widget.
|
|
// Currently, there's no know bug origined from this flaw.
|
|
inc(FUpdateCount);
|
|
if (FUpdateCount=1) and (Owner<>nil) and (Owner.HandleAllocated) then
|
|
LockOnChange({%H-}PGtkObject(Owner.Handle),+1);
|
|
end;
|
|
|
|
procedure TGtkListStringList.EndUpdate;
|
|
begin
|
|
dec(FUpdateCount);
|
|
if (FUpdateCount=0) then begin
|
|
if (Owner<>nil) and (Owner.HandleAllocated) then
|
|
LockOnChange({%H-}PGtkObject(Owner.Handle),-1);
|
|
if (glsItemCacheNeedsUpdate in FStates) then
|
|
UpdateItemCache;
|
|
end;
|
|
end;
|
|
|
|
procedure TGtkListStringList.ConsistencyCheck;
|
|
var
|
|
CurListItem: PGList;
|
|
i: integer;
|
|
RealCachedCount: Integer;
|
|
Str1: string;
|
|
Str2: string;
|
|
begin
|
|
if FCachedCount>FCachedCapacity then RaiseGDBException('');
|
|
if (FCachedItems=nil) and (FCachedCapacity>0) then RaiseGDBException('');
|
|
if (FCachedItems<>nil) and (FCachedCapacity=0) then RaiseGDBException('');
|
|
|
|
UpdateItemCache;
|
|
if (FGtkList<>nil) and (FGtkList^.children<>nil) then
|
|
RealCachedCount:=g_list_length(FGtkList^.children)
|
|
else
|
|
RealCachedCount:=0;
|
|
if RealCachedCount<>FCachedCount then
|
|
RaiseGDBException('RealCachedCount='+IntToStr(RealCachedCount)
|
|
+' FCachedCount='+IntToStr(FCachedCount));
|
|
if FGtkList<>nil then begin
|
|
CurListItem:=FGtkList^.children;
|
|
i:=0;
|
|
while CurListItem<>nil do begin
|
|
if FCachedItems[i]<>PGtkListItem(CurListItem^.Data) then
|
|
RaiseGDBException(IntToStr(i));
|
|
inc(i);
|
|
CurListItem:=CurListItem^.Next;
|
|
end;
|
|
end;
|
|
|
|
if Sorted then begin
|
|
for i:=0 to FCachedCount-2 do begin
|
|
Str1:=Strings[i];
|
|
Str2:=Strings[i+1];
|
|
if (AnsiCompareText(Str1,Str2)>0) then
|
|
RaiseGDBException(IntToStr(i)+':'+Str1+'>'+IntToStr(i+1)+':'+Str2);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.Assign
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.Assign(Source : TPersistent);
|
|
var
|
|
i, Cnt: integer;
|
|
SrcStrings: TStrings;
|
|
begin
|
|
if (Source=Self) or (Source=nil) then exit;
|
|
if ((Source is TGtkListStringList)
|
|
and (TGtkListStringList(Source).FGtkList=FGtkList))
|
|
then
|
|
RaiseGDBException('TGtkListStringList.Assign: There 2 lists with the same FGtkList');
|
|
BeginUpdate;
|
|
//DebugLn('[TGtkListStringList.Assign] A ',Source.Classname,' Self=',DbgS(Self),' Source=',DbgS(Source));
|
|
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
|
|
SrcStrings:=TStrings(Source);
|
|
if IsEqual(SrcStrings,true) then exit;
|
|
Clear;
|
|
Cnt:=SrcStrings.Count;
|
|
for i:=0 to Cnt - 1 do begin
|
|
AddObject(SrcStrings[i],SrcStrings.Objects[i]);
|
|
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
|
|
EndUpdate;
|
|
end;
|
|
{$IFDEF CheckGtkList}
|
|
ConsistencyCheck;
|
|
{$ENDIF}
|
|
//DebugLn('[TGtkListStringList.Assign] END ',Source.Classname);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.Get
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
function TGtkListStringList.Get(Index : integer) : string;
|
|
var
|
|
Item : PChar;
|
|
ALabel : PGtkLabel;
|
|
begin
|
|
//DebugLn('[TGtkListStringList.Get] Index=',Index,' Count=',Count);
|
|
ALabel:=GetLabel(Index);
|
|
|
|
if ALabel = nil then
|
|
Result:= ''
|
|
else begin
|
|
Item:=nil;
|
|
gtk_label_get(ALabel, @Item);
|
|
Result:= StrPas(Item);
|
|
end;
|
|
end;
|
|
|
|
function TGtkListStringList.GetObject(Index: Integer): TObject;
|
|
var
|
|
ListItem : PGtkListItem;
|
|
begin
|
|
//DebugLn('[TGtkListStringList.GetObject] Index=',Index,' Count=',Count);
|
|
Result:=nil;
|
|
ListItem:=GetListItem(Index);
|
|
if ListItem<>nil then
|
|
Result:=TObject(g_object_get_data(PGObject(ListItem),'LCLStringsObject'));
|
|
end;
|
|
|
|
procedure TGtkListStringList.Put(Index: Integer; const S: string);
|
|
var
|
|
ALabel: PGtkLabel;
|
|
NewText: PChar;
|
|
SortedIndex: Integer;
|
|
begin
|
|
//DebugLn('[TGtkListStringList.Put] Index=',Index,' Count=',Count);
|
|
if Sorted then begin
|
|
SortedIndex:=GetInsertPosition(S);
|
|
// we move instead of insert => adjust position
|
|
if SortedIndex>Index then dec(SortedIndex);
|
|
end else
|
|
SortedIndex:=Index;
|
|
|
|
// change label
|
|
ALabel:=GetLabel(Index);
|
|
if ALabel = nil then
|
|
RaiseGDBException('TGtkListStringList.Put');
|
|
if S<>'' then
|
|
NewText:=PChar(S)
|
|
else
|
|
NewText:=#0;
|
|
gtk_label_set_text(ALabel, NewText);
|
|
//set default font
|
|
|
|
// repair sorting
|
|
if Sorted and (SortedIndex<>Index) then begin
|
|
Move(Index,SortedIndex);
|
|
end;
|
|
end;
|
|
|
|
function TGtkListStringList.GetListItem(Index: integer): PGtkListItem;
|
|
begin
|
|
if (Index < 0) or (Index >= Count) then
|
|
RaiseGDBException('TGtkListStringList.Get Out of bounds.')
|
|
else begin
|
|
UpdateItemCache;
|
|
Result:=FCachedItems[Index];
|
|
end;
|
|
end;
|
|
|
|
function TGtkListStringList.GetLabel(Index: integer): PGtkLabel;
|
|
var
|
|
ListItem: PGtkListItem;
|
|
begin
|
|
ListItem:=GetListItem(Index);
|
|
|
|
if FWithCheckBox then
|
|
Result := PPointer(PGTKBox(PGtkBin(ListItem)^.child)^.Children^.Next^.Data)^
|
|
else
|
|
Result := PGTKLabel(PGtkBin(ListItem)^.child);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.GetCount
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
function TGtkListStringList.GetCount: integer;
|
|
begin
|
|
if (FGtkList<>nil) and (FGtkList^.children <> nil) then begin
|
|
UpdateItemCache;
|
|
Result:=FCachedCount;
|
|
end else begin
|
|
Result:= 0
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.Clear
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.Clear;
|
|
var
|
|
i: integer;
|
|
begin
|
|
BeginUpdate;
|
|
RemoveAllCallbacks;
|
|
for i:=0 to FCachedCount-1 do begin
|
|
{$IFDEF DebugLCLComponents}
|
|
DebugGtkWidgets.MarkDestroyed(FCachedItems[i]);
|
|
{$ENDIF}
|
|
end;
|
|
Include(FStates,glsItemCacheNeedsUpdate);
|
|
CheckForInvalidFocus;
|
|
if gtkListGetSelectionMode(FGtkList)=GTK_SELECTION_BROWSE then begin
|
|
// GTK_SELECTION_BROWSE always auto selects one child
|
|
// -> disable it and enable it when a selection is needed
|
|
gtk_list_set_selection_mode(FGtkList,GTK_SELECTION_SINGLE);
|
|
end;
|
|
gtk_list_clear_items(FGtkList, 0, Count);
|
|
FCachedCount:=0;
|
|
//Update the internal Item Index cache
|
|
if FOwner.HandleAllocated and (FOwner is TCustomComboBox) then
|
|
PInteger(GetWidgetInfo({%H-}Pointer(FOwner.Handle))^.UserData)^ := -1;
|
|
EndUpdate;
|
|
{$IFDEF CheckGtkList}
|
|
ConsistencyCheck;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.Delete
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.Delete(Index: integer);
|
|
begin
|
|
UpdateItemCache;
|
|
RemoveItemCallbacks(Index);
|
|
{$IFDEF DebugLCLComponents}
|
|
DebugGtkWidgets.MarkDestroyed(FCachedItems[Index]);
|
|
{$ENDIF}
|
|
// remove item from cache
|
|
if (Index<FCachedCount-1) then begin
|
|
System.Move(FCachedItems[Index+1],FCachedItems[Index],
|
|
SizeOf(Pointer)*(FCachedCount-1-Index));
|
|
end;
|
|
// shrink cache (lazy)
|
|
dec(FCachedCount);
|
|
if (FCachedCount<(FCachedCapacity shr 2)) then begin
|
|
FCachedCapacity:=FCachedCapacity shr 1;
|
|
ReAllocMem(FCachedItems,SizeOf(PGtkListItem)*FCachedCapacity);
|
|
end;
|
|
// change selection mode if needed
|
|
if (gtkListGetSelectionMode(FGtkList)=GTK_SELECTION_BROWSE)
|
|
and (FGtkList^.selection<>nil)
|
|
and (g_list_nth_data(FGtkList^.children, Index)=FGtkList^.selection^.data) then begin
|
|
// item is selected and BROWSE mode is enabled
|
|
// -> change selection mode to prevent, that gtk auto selects another child
|
|
gtk_list_set_selection_mode(FGtkList,GTK_SELECTION_SINGLE);
|
|
end;
|
|
|
|
// remove item from gtk list
|
|
if Count = 0 then CheckForInvalidFocus;
|
|
gtk_list_clear_items(FGtkList, Index, Index + 1);
|
|
Include(FStates,glsItemCacheNeedsUpdate);
|
|
|
|
//Clear the combobox text and set item index to -1
|
|
if FOwner is TCustomComboBox then
|
|
TGtk2WSCustomComboBox.SetItemIndex(TCustomComboBox(FOwner), -1);
|
|
|
|
{$IFDEF CheckGtkList}
|
|
ConsistencyCheck;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TGtkListStringList.IndexOf(const S: string): Integer;
|
|
|
|
Returns index of item with string.
|
|
------------------------------------------------------------------------------}
|
|
function TGtkListStringList.IndexOf(const S: string): Integer;
|
|
var
|
|
l, m, r, cmp: integer;
|
|
begin
|
|
if FSorted then begin
|
|
l:=0;
|
|
r:=Count-1;
|
|
m:=l;
|
|
while (l<=r) do begin
|
|
m:=(l+r) shr 1;
|
|
cmp:=AnsiCompareText(S,Strings[m]);
|
|
|
|
if cmp<0 then
|
|
r:=m-1
|
|
else if cmp>0 then
|
|
l:=m+1
|
|
else begin
|
|
Result:=m;
|
|
exit;
|
|
end;
|
|
end;
|
|
Result:=-1;
|
|
end else begin
|
|
Result:=inherited IndexOf(S);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TGtkListStringList.Insert
|
|
Params:
|
|
Returns:
|
|
|
|
------------------------------------------------------------------------------}
|
|
procedure TGtkListStringList.Insert(Index : integer; const S : string);
|
|
var
|
|
li, cb, box,aLabel: PGtkWidget;
|
|
item_requisition: TGtkRequisition;
|
|
OldCount: LongInt;
|
|
LCLIndex: PInteger;
|
|
|
|
procedure RaiseIndexOutOfBounds;
|
|
begin
|
|
RaiseGDBException('TGtkListStringList.Insert: Index '+IntToStr(Index)
|
|
+' out of bounds. Count='+IntToStr(OldCount));
|
|
end;
|
|
|
|
begin
|
|
OldCount:=Count;
|
|
BeginUpdate;
|
|
try
|
|
if FSorted then begin
|
|
Index:=GetInsertPosition(S);
|
|
end;
|
|
if (Index < 0) or (Index > OldCount) then
|
|
RaiseIndexOutOfBounds;
|
|
if Owner = nil then RaiseGDBException(
|
|
'TGtkListStringList.Insert Unspecified owner');
|
|
|
|
// ToDo:
|
|
// - Icons
|
|
// - measure item
|
|
|
|
if FWithCheckBox
|
|
then begin
|
|
li := gtk_list_item_new;
|
|
box := gtk_hbox_new(False, 0); //^Pointer(PGTKBox(box)^.children^.Next^.Data)^
|
|
gtk_container_add(PGTKContainer(li), box);
|
|
cb := gtk_check_button_new;
|
|
gtk_box_pack_start(PGTKBox(box), cb, False, False, 0);
|
|
|
|
aLabel:=gtk_label_new(PChar(S));
|
|
if not TListBox(Owner).Font.IsDefault then begin
|
|
Gtk2WidgetSet.SetWidgetColor(aLabel, TListBox(Owner).Font.Color, clNone,
|
|
[GTK_STATE_NORMAL,GTK_STATE_ACTIVE,GTK_STATE_PRELIGHT,GTK_STATE_SELECTED]);
|
|
Gtk2WidgetSet.SetWidgetFont(aLabel, TListBox(Owner).Font);
|
|
end;
|
|
gtk_box_pack_start(PGTKBox(box), aLabel, False, False, 0);
|
|
end
|
|
else begin
|
|
li:=gtk_list_item_new_with_label(PChar(S));
|
|
aLabel:=PGtkBin(li)^.child;
|
|
if not TListBox(Owner).Font.IsDefault then begin
|
|
Gtk2WidgetSet.SetWidgetColor(aLabel, TListBox(Owner).Font.Color, clNone,
|
|
[GTK_STATE_NORMAL,GTK_STATE_ACTIVE,GTK_STATE_PRELIGHT,GTK_STATE_SELECTED]);
|
|
Gtk2WidgetSet.SetWidgetFont(aLabel, TListBox(Owner).Font);
|
|
end;
|
|
end;
|
|
{$IFDEF DebugLCLComponents}
|
|
DebugGtkWidgets.MarkCreated(li,dbgsName(Owner)+' Index='+dbgs(Index)+' Count='+dbgs(Count));
|
|
{$ENDIF}
|
|
{$IFDEF EventTrace}
|
|
Debugln('insertListItem',s);
|
|
{$ENDIF}
|
|
ConnectItemCallbacks(PGtkListItem(li));
|
|
// grow capacity
|
|
UpdateItemCache;
|
|
if (FCachedCapacity<=OldCount) then begin
|
|
if FCachedCapacity=0 then FCachedCapacity:=1;
|
|
while (FCachedCapacity<=OldCount) do
|
|
FCachedCapacity:=FCachedCapacity shl 1;
|
|
ReAllocMem(FCachedItems,SizeOf(PGtkListItem)*FCachedCapacity);
|
|
end;
|
|
// insert item in cache
|
|
inc(FCachedCount);
|
|
if Index<OldCount then
|
|
System.Move(FCachedItems[Index],FCachedItems[Index+1],
|
|
SizeOf(PGtkListItem)*(OldCount-Index));
|
|
FCachedItems[Index]:=PGtkListItem(li);
|
|
// insert in gtk
|
|
gtk_widget_show_all(li);
|
|
gtk_list_insert_items(FGtkList, g_list_append(nil, li), Index);
|
|
//if the item is inserted before the selected item the
|
|
//internal index cache becomes out of sync
|
|
if (FOwner is TCustomComboBox) and FOwner.HandleAllocated then
|
|
begin
|
|
LCLIndex := PInteger(GetWidgetInfo({%H-}Pointer(FOwner.Handle))^.UserData);
|
|
if Index <= LCLIndex^ then
|
|
Inc(LCLIndex^);
|
|
end;
|
|
// adjust gtk height
|
|
if (Owner is TCustomListBox)
|
|
and (TListBox(Owner).ItemHeight>1) then begin
|
|
if li^.Allocation.Width>1 then
|
|
item_requisition.Width:=li^.Allocation.Width
|
|
else
|
|
gtk_widget_size_request(li,@item_requisition);
|
|
gtk_widget_set_usize(li,Max(li^.Allocation.Width,item_requisition.Width),
|
|
TListBox(Owner).ItemHeight);
|
|
end;
|
|
finally
|
|
EndUpdate;
|
|
{$IFDEF CheckGtkList}
|
|
ConsistencyCheck;
|
|
{$ENDIF}
|
|
end;
|
|
//DebugLn('[TGtkListStringList.Insert] END Index=',Index,' Count=',Count,' ',S,',',Count);
|
|
end;
|
|
|
|
function TGtkListStringList.GetInsertPosition(const S: string): integer;
|
|
var
|
|
l: Integer;
|
|
Cnt: LongInt;
|
|
r: Integer;
|
|
m: LongInt;
|
|
cmp: LongInt;
|
|
begin
|
|
Cnt:=Count;
|
|
if FSorted then begin
|
|
l:=0;
|
|
r:=Cnt-1;
|
|
m:=l;
|
|
while (l<=r) do begin
|
|
m:=(l+r) shr 1;
|
|
cmp:=AnsiCompareText(S,Strings[m]);
|
|
if cmp<0 then
|
|
r:=m-1
|
|
else if cmp>0 then
|
|
l:=m+1
|
|
else
|
|
break;
|
|
end;
|
|
if (m<Cnt) and (AnsiCompareText(S,Strings[m])>0) then
|
|
inc(m);
|
|
Result:=m;
|
|
end else begin
|
|
Result:=Cnt;
|
|
end;
|
|
end;
|
|
|
|
procedure TGtkListStringList.Move(FromIndex, ToIndex: Integer);
|
|
var
|
|
Item: PGtkListItem;
|
|
begin
|
|
if (FromIndex=ToIndex) then exit;
|
|
|
|
//debugln('TGtkListStringList.Move From=',dbgs(FromIndex),' To=',dbgs(ToIndex));
|
|
Item:=GetListItem(FromIndex);
|
|
|
|
// move in gtk
|
|
MoveGListLink(FGtkList^.children,FromIndex,ToIndex);
|
|
if (GTK_WIDGET_VISIBLE (PGtkWidget(FGtkList))) then
|
|
gtk_widget_queue_resize (PGtkWidget(FGtkList));
|
|
|
|
// move in cache
|
|
if CacheValid then begin
|
|
if FromIndex<ToIndex then begin
|
|
System.Move(FCachedItems[FromIndex+1],FCachedItems[FromIndex],
|
|
SizeOf(PGtkListItem)*(ToIndex-FromIndex));
|
|
end else begin
|
|
System.Move(FCachedItems[ToIndex],FCachedItems[ToIndex+1],
|
|
SizeOf(PGtkListItem)*(FromIndex-ToIndex));
|
|
end;
|
|
FCachedItems[ToIndex]:=Item;
|
|
end;
|
|
end;
|
|
|