lazarus/lcl/interfaces/gtk/gtklistsl.inc

1142 lines
34 KiB
PHP

{%MainUnit gtkint.pp}
{******************************************************************************
gtklistsl.inc
TGtkListStringList and TGtkCListStringList
******************************************************************************
*****************************************************************************
* *
* This file is part of the Lazarus Component Library (LCL) *
* *
* See the file COPYING.LCL, 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. *
* *
*****************************************************************************
}
const
GtkListItemGtkListTag = 'GtkList';
GtkListItemLCLListTag = 'LCLList';
{*************************************************************}
{ 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; 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(gtk_object_get_data(PGtkObject(Data),GtkListItemGtkListTag));
if GtkList=nil then
RaiseException('gtkListItemDrawAfterCB GtkList=nil');
LCLList:=TGtkListStringList(gtk_object_get_data(PGtkObject(Data),
GtkListItemLCLListTag));
if LCLList=nil then
RaiseException('gtkListItemDrawAfterCB LCLList=nil');
if [csDestroying,csLoading]*LCLList.Owner.ComponentState<>[] then exit;
// only owner draw lists are interested in drawing items themselves.
//if not (csDesigning in LCLList.Owner.ComponentState) then begin
if LclList.Owner is TCustomListbox then
if TCustomListbox(LCLList.Owner).Style = lbStandard then
exit;
if LclList.Owner is TCustomCombobox then
if TCustomCombobox(LclList.Owner).Style < csOwnerDrawFixed then
exit;
//end;
// get itemindex and area
ItemIndex:=g_list_index(GtkList^.children,Data);
AreaRect:=Bounds(Area^.x,Area^.y,Area^.Width,Area^.Height);
// collect state flags
State:=[odPainted];
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,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(Widget));
ItemState:=State;
end;
//DebugLn('gtkListItemDrawCB A LCLList=',DbgS(LCLList),' Widget=',DbgS(Widget),' ',DbgS(Data));
//DebugLn('gtkListItemDrawCB B ',LCLList.ClassName,' ',DbgS(LCLList.Owner);
//DebugLn('gtkListItemDrawCB C ',LCLList.Owner.ClassName);
Result := DeliverMessage(LCLList.Owner, Msg)=0;
ReleaseDC(HWnd(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;
//ItemIndex: LongInt;
Mess: TLMessage;
begin
Result:=true;
//DebugLn('gtkListItemDrawCB ');
// get context
GtkList:=PGtkList(gtk_object_get_data(PGtkObject(Data),GtkListItemGtkListTag));
if GtkList=nil then
RaiseException('gtkListItemToggledCB GtkList=nil');
LCLList:=TGtkListStringList(gtk_object_get_data(PGtkObject(Data),
GtkListItemLCLListTag));
if LCLList=nil then
RaiseException('gtkListItemToggledCB LCLList=nil');
if [csDestroying,csLoading]*LCLList.Owner.ComponentState<>[] then exit;
// get itemindex and area
//ItemIndex:=g_list_index(GtkList^.children,Data);
if LockOnChange(PgtkObject(Widget),0) > 0 then Exit;
if GtkWidgetIsA(Widget,gtk_toggle_button_get_type) then begin
gtk_object_set_data(PgtkObject(Widget), 'Grayed', nil);
end;
Mess.Msg := LM_CHANGED;
Mess.Result := 0;
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;
RaiseException(s);
end;
var
GtkList: PGtkList;
LCLList: TGtkListStringList;
//ItemIndex: LongInt;
Mess: TLMessage;
begin
// get context
GtkList:=PGtkList(gtk_object_get_data(PGtkObject(Data),GtkListItemGtkListTag));
if GtkList=nil then
RaiseGTKListNotFound;
LCLList:=TGtkListStringList(gtk_object_get_data(PGtkObject(Data),
GtkListItemLCLListTag));
if LCLList=nil then
RaiseException('gtkListItemSelectAfterCB LCLList=nil');
if [csDestroying,csLoading]*LCLList.Owner.ComponentState<>[] then exit;
// get itemindex and area
//ItemIndex:=g_list_index(GtkList^.children,Data);
if LockOnChange(PgtkObject(Widget),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 RaiseException(
'TGtkListStringList.Create Unspecified list widget');
FGtkList:= List;
if TheOwner = nil then RaiseException(
'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
Result := GetInsertPosition(S)
else
Result:=Count;
Insert(Count,S);
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;
ListItem:=FCachedItems[Index];
ConnectItemCallbacks(ListItem);
end;
{------------------------------------------------------------------------------
procedure TGtkListStringList.ConnectItemCallbacks(Li: PGtkListItem);
------------------------------------------------------------------------------}
procedure TGtkListStringList.ConnectItemCallbacks(Li: PGtkListItem);
var
ChildWidget: Pointer;
begin
gtk_object_set_data(PGtkObject(li),GtkListItemLCLListTag,Self);
gtk_object_set_data(PGtkObject(li),GtkListItemGtkListTag,FGtkList);
//DebugLn('TGtkListStringList.ConnectItemCallbacks Self=',DbgS(Self),
//' GtkList=',DbgS(FGtkList),
//' Owner=',DbgS(Owner),'=',Owner.ClassName,
//' LI=',DbgS(LI),
//' ');
{$ifdef GTK2}
g_signal_connect_after(G_OBJECT(li), 'expose_event',
G_CALLBACK(@gtkListItemExposeEvent), li);
{$else}
gtk_signal_connect_after(PGtkObject(li), 'draw',
TGTKSignalFunc(@gtkListItemDrawAfterCB),li);
{$endif}
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;
if FOwner is TCustomComboBox then begin
gtk_signal_connect_after(PGtkObject(li), 'select',
TGTKSignalFunc(@gtkListItemSelectAfterCB),li);
end;
end;
{------------------------------------------------------------------------------
procedure TGtkListStringList.ConnectAllCallbacks;
------------------------------------------------------------------------------}
procedure TGtkListStringList.ConnectAllCallbacks;
var
i: Integer;
begin
BeginUpdate;
UpdateItemCache;
i := FCachedCount - 1;
while i >= 0 do
begin
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
gtk_object_set_data(PGtkObject(AItem), GtkListItemLCLListTag, nil);
gtk_object_set_data(PGtkObject(AItem), GtkListItemGtkListTag, nil);
{$ifdef GTK2}
g_signal_handlers_disconnect_by_func(
G_OBJECT(AItem), G_CALLBACK(@gtkListItemExposeEvent), AItem);
{$else}
gtk_signal_disconnect_by_func(
PGtkObject(AItem), TGTKSignalFunc(@gtkListItemDrawAfterCB), AItem);
{$endif}
if FWithCheckBox
then begin
ChildWidget := PPointer(PGTKBox(PGtkBin(AItem)^.child)^.Children^.Data)^;
gtk_signal_disconnect_by_func(
PGtkObject(ChildWidget), TGTKSignalFunc(@gtkListItemToggledCB), AItem);
end;
if FOwner is TCustomComboBox
then begin
gtk_signal_disconnect_by_func(
PGtkObject(AItem), TGTKSignalFunc(@gtkListItemSelectAfterCB), AItem);
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;
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);
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
gtk_object_set_data(PGtkObject(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
inc(FUpdateCount);
end;
procedure TGtkListStringList.EndUpdate;
begin
dec(FUpdateCount);
if (FUpdateCount=0) and (glsItemCacheNeedsUpdate in FStates) then
UpdateItemCache;
end;
procedure TGtkListStringList.ConsistencyCheck;
var
CurListItem: PGList;
i: integer;
RealCachedCount: Integer;
Str1: string;
Str2: string;
begin
if FCachedCount>FCachedCapacity then RaiseException('');
if (FCachedItems=nil) and (FCachedCapacity>0) then RaiseException('');
if (FCachedItems<>nil) and (FCachedCapacity=0) then RaiseException('');
UpdateItemCache;
if (FGtkList<>nil) and (FGtkList^.children<>nil) then
RealCachedCount:=g_list_length(FGtkList^.children)
else
RealCachedCount:=0;
if RealCachedCount<>FCachedCount then
RaiseException('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
RaiseException(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
RaiseException(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
RaiseException('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(gtk_object_get_data(PGtkObject(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
RaiseException('TGtkListStringList.Put');
if S<>'' then
NewText:=PChar(S)
else
NewText:=#0;
gtk_label_set_text(ALabel, NewText);
// 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
RaiseException('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;
begin
BeginUpdate;
RemoveAllCallbacks;
Include(FStates,glsItemCacheNeedsUpdate);
CheckForInvalidFocus;
gtk_list_clear_items(FGtkList, 0, Count);
FCachedCount:=0;
EndUpdate;
{$IFDEF CheckGtkList}
ConsistencyCheck;
{$ENDIF}
end;
{------------------------------------------------------------------------------
Method: TGtkListStringList.Delete
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStringList.Delete(Index: integer);
{$IFDEF Gtk1}
var
Next: PGList;
{$ENDIF}
begin
UpdateItemCache;
RemoveItemCallbacks(Index);
// 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;
// remove item from gtk list
if Count = 0 then
CheckForInvalidFocus
else begin
{$IFDEF Gtk1}
{ Work round gtk bug - crashes if deleting first item in list
and item has focus and there are remaining items }
if (Index = 0) and (PGTKContainer(FGtkList)^.focus_child <> nil)
and (gtk_list_child_position(FGtkList,PGTKContainer(FGtkList)^.focus_child) = 0) then begin
Next := FGtkList^.children^.next;
if Next <> nil then
gtk_widget_grab_focus(Next^.data);
end;
{$ENDIF}
end;
gtk_list_clear_items(FGtkList, Index, Index + 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
BeginUpdate;
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;
EndUpdate;
end;
{------------------------------------------------------------------------------
Method: TGtkListStringList.Insert
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkListStringList.Insert(Index : integer; const S : string);
var
li, cb, box: PGtkWidget;
item_requisition: TGtkRequisition;
OldCount: LongInt;
procedure RaiseIndexOutOfBounds;
begin
RaiseException('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 RaiseException(
'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);
gtk_box_pack_start(PGTKBox(box), gtk_label_new(PChar(S)), False, False, 0);
end
else begin
li:=gtk_list_item_new_with_label(PChar(S));
end;
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);
// 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;
{*************************************************************}
{ TGtkCListStringList methods }
{*************************************************************}
{------------------------------------------------------------------------------
Method: TGtkCListStringList.Create
Params:
Returns:
------------------------------------------------------------------------------}
constructor TGtkCListStringList.Create(List : PGtkCList);
begin
inherited Create;
if List = nil then
RaiseException('TGtkCListStringList.Create: Unspecified list widget');
FGtkCList:= List;
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.SetSorted
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkCListStringList.SetSorted(Val : boolean);
begin
if Val <> FSorted then begin
FSorted:= Val;
gtk_clist_set_auto_sort(FGtkCList, Val);
if Val then Sort;
end;
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.Sort
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkCListStringList.Sort;
begin
gtk_clist_sort(FGtkCList);
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.Assign
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkCListStringList.Assign(Source : TPersistent);
var
Counter : integer;
begin
{ Do not call inherited Assign as it does things we do not want to happen }
if Source is TStrings
then begin
Clear;
for Counter:= TStrings(Source).Count - 1 downto 0 do
InsertObject(0, TStrings(Source)[Counter], TStrings(Source).Objects[Counter]);
end
else inherited Assign(Source);
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.Clear
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkCListStringList.Clear;
begin
gtk_clist_clear(FGtkCList);
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.Delete
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkCListStringList.Delete(Index : integer);
begin
gtk_clist_remove(FGtkCList, Index);
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.Get
Params:
Returns:
------------------------------------------------------------------------------}
function TGtkCListStringList.Get(Index : integer) : string;
var
Item : PChar;
begin
if (Index < 0) or (Index >= Count)
then RaiseException('TGtkCListStringList.Get Out of bounds.')
else begin
Item := nil;
gtk_clist_get_text(FGtkCList, Index, 0, @Item);
Result:= StrPas(Item);
end;
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.GetCount
Params:
Returns:
------------------------------------------------------------------------------}
function TGtkCListStringList.GetCount : integer;
begin
Result:= FGtkCList^.rows;
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.GetObject
Params:
Returns:
------------------------------------------------------------------------------}
function TGtkCListStringList.GetObject(Index: Integer): TObject;
begin
pointer(Result) := gtk_clist_get_row_data(FGtkCList, Index);
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.Insert
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkCListStringList.Insert(Index : integer; const S : string);
type
TCSArr = record
Arr: array[0..15] of PChar;
Str: array[0..0] of Char;
end;
var
CS: ^TCSArr;
CSize: integer;
K: integer;
begin
CSize := sizeof(TCSArr)+length(S)+1;
GetMem(CS, CSize);
FillChar(CS^, sizeof(TCSArr), 0);
StrPCopy(CS^.Str, S);
CS^.Arr[0] := @CS^.Str;
for K := 1 to 15 do begin
CS^.Arr[K] := StrScan(CS^.Arr[K-1], #9);
if Assigned(CS^.Arr[K])
then begin
CS^.Arr[K][0] := #0;
Inc(CS^.Arr[K]);
end else
break;
end;
gtk_clist_insert(FGtkCList, Index, PPGChar(CS));
FreeMem(CS);
end;
{------------------------------------------------------------------------------
Method: TGtkCListStringList.PutObject
Params:
Returns:
------------------------------------------------------------------------------}
procedure TGtkCListStringList.PutObject(Index: Integer; AObject: TObject);
begin
gtk_clist_set_row_data(FGtkCList, Index, AObject);
end;