* gtk2 Listview now uses custom GtktreeModel that gets it's data directly from TListItems. Old behavior can be restored by compiling lcl with -dUseOrigTreeModel

git-svn-id: trunk@22945 -
This commit is contained in:
andrew 2009-12-03 16:15:31 +00:00
parent b8581d21e4
commit e49b42439b
6 changed files with 414 additions and 3 deletions

1
.gitattributes vendored
View File

@ -4724,6 +4724,7 @@ lcl/interfaces/gtk2/gtk2extrah.inc svneol=native#text/pascal
lcl/interfaces/gtk2/gtk2int.pas svneol=native#text/pascal
lcl/interfaces/gtk2/gtk2lclintf.inc svneol=native#text/pascal
lcl/interfaces/gtk2/gtk2lclintfh.inc svneol=native#text/pascal
lcl/interfaces/gtk2/gtk2listviewtreemodel.pas svneol=native#text/plain
lcl/interfaces/gtk2/gtk2memostrings.inc svneol=native#text/pascal
lcl/interfaces/gtk2/gtk2privatelist.inc svneol=native#text/pascal
lcl/interfaces/gtk2/gtk2privatewidget.inc svneol=native#text/plain

View File

@ -795,6 +795,10 @@ type
function MoveNext: Boolean;
property Current: TListItem read GetCurrent;
end;
TListItemsFlag = (lisfWSItemsCreated);
TListItemsFlags = set of TListItemsFlag;
{ TListItems }
{
@ -807,7 +811,8 @@ type
TListItems = class(TPersistent)
private
FOwner: TCustomListView;
FItems: TList;
FItems: TList;
FFlags: TListItemsFlags;
FCacheIndex: Integer; // Caches the last used item
FCacheItem: TListItem; //
procedure WSCreateCacheItem;
@ -842,6 +847,7 @@ type
function IndexOf(const AItem: TListItem): Integer;
function Insert(const AIndex: Integer) : TListItem;
procedure InsertItem(AItem: TListItem; const AIndex: Integer);
property Flags: TListItemsFlags read FFlags;
property Count: Integer read GetCount write SetCount;
property Item[const AIndex: Integer]: TListItem read GetItem write SetItem; default;
property Owner : TCustomListView read FOwner;

View File

@ -193,6 +193,7 @@ begin
FCacheIndex := n;
WSCreateCacheItem;
end;
Include(FFlags, lisfWSItemsCreated);
end;
{------------------------------------------------------------------------------}

View File

@ -0,0 +1,379 @@
{ $Id$
*****************************************************************************
* *
* 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 Gtk2ListViewTreeModel;
{$mode objfpc}{$H+}
interface
uses
// FPC
Classes, SysUtils,
// Gtk
gtk2, glib2,
// LCL
ComCtrls;
type
{ TLCLTreeModel }
PLCLListViewModel = ^TLCLListViewModel;
{ TLCLListViewModel }
TLCLListViewModel = object
{%REGION GObject} // Add nothing in or before this region!!
g_type_instance : TGTypeInstance;
ref_count : guint;
qdata : PGData;
{%ENDREGION}
{%REGION Interface methods in order!}
procedure row_changed(path:PGtkTreePath; iter:PGtkTreeIter); cdecl;
procedure row_inserted(path:PGtkTreePath; iter:PGtkTreeIter); cdecl;
procedure row_has_child_toggled(path:PGtkTreePath; iter:PGtkTreeIter); cdecl;
procedure row_deleted(path:PGtkTreePath); cdecl;
procedure rows_reordered(path:PGtkTreePath; iter:PGtkTreeIter; new_order:Pgint); cdecl;
function get_flags():TGtkTreeModelFlags; cdecl;
function get_n_columns():gint; cdecl;
function get_column_type(index:gint):GType; cdecl;
function get_iter(iter:PGtkTreeIter; path:PGtkTreePath):gboolean; cdecl;
function get_path(iter:PGtkTreeIter):PGtkTreePath; cdecl;
procedure get_value(iter:PGtkTreeIter; column:gint; value:PGValue); cdecl;
function iter_next(iter:PGtkTreeIter):gboolean; cdecl;
function iter_children(iter:PGtkTreeIter; parent:PGtkTreeIter):gboolean; cdecl;
function iter_has_child(iter:PGtkTreeIter):gboolean; cdecl;
function iter_n_children(iter:PGtkTreeIter):gint; cdecl;
function iter_nth_child(iter:PGtkTreeIter; parent:PGtkTreeIter; n:gint):gboolean; cdecl;
function iter_parent(iter:PGtkTreeIter; child:PGtkTreeIter):gboolean; cdecl;
procedure ref_node(iter:PGtkTreeIter); cdecl;
procedure unref_node(iter:PGtkTreeIter); cdecl;
{%ENDREGION}
procedure NotifyRowInserted(AIndex: PtrUInt);
procedure NotifyRowDeleted(AIndex: PtrUInt);
function TreeModel: PGtkTreeModel; inline;
public
ListView: TCustomListView;
end;
PMyTreeModelObjectClass = ^TLCLTreeModelClass;
TLCLTreeModelClass = record
parent_class: TGObjectClass;
end;
TLVItemHack = class(TListItem)
end;
function LCLListViewModelNew(AListView: TCustomListView): PLCLListViewModel;
function LCLLISTVIEW_MODEL_TYPE: GType;
procedure LCLListViewModelClassInit(g_class:gpointer; class_data:gpointer); cdecl;
procedure LCLListViewModelInit(instance:PGTypeInstance; g_class:gpointer); cdecl;
procedure LCLListViewModelInterfaceInit(g_iface:PGtkTreeModelIface; iface_data:gpointer); cdecl;
implementation
var
_LCLLISTVIEW_MODEL_TYPE: gulong = 0;
function LCLListViewModelNew(AListView: TCustomListView): PLCLListViewModel;
begin
Result := g_object_new(LCLLISTVIEW_MODEL_TYPE, nil);
Result^.ListView := AListView;
end;
function LCLLISTVIEW_MODEL_TYPE: GType;
var
TypeInfo: TGTypeInfo;
INterfaceINfo: TGInterfaceInfo;
begin
if _LCLLISTVIEW_MODEL_TYPE = 0 then
begin
with TypeInfo do
begin
class_size:= SizeOf(TLCLTreeModelClass);
base_init := nil;//TGBaseInitFunc;
base_finalize:= nil;// TGBaseFinalizeFunc;
class_init := @LCLListViewModelClassInit;// TGClassInitFunc;
class_finalize := nil;// TGClassFinalizeFunc;
class_data := nil;// gconstpointer;
instance_size := SizeOf(TLCLListViewModel);// guint16;
n_preallocs := 0;// guint16;
instance_init := @LCLListViewModelInit;// TGInstanceInitFunc;
value_table := nil;// PGTypeValueTable;
end;
with InterfaceInfo do
begin
interface_init := TGInterfaceInitFunc(@LCLListViewModelInterfaceInit);// TGInterfaceInitFunc;
interface_finalize := nil; //TGInterfaceFinalizeFunc;
interface_data := nil;//gpointer;
end;
_LCLLISTVIEW_MODEL_TYPE := g_type_register_static(G_TYPE_OBJECT, 'LCLListViewModel', @TypeInfo, 0);
g_type_add_interface_static(_LCLLISTVIEW_MODEL_TYPE, GTK_TYPE_TREE_MODEL, @InterfaceInfo);
end;
Result := _LCLLISTVIEW_MODEL_TYPE;
end;
procedure LCLListViewModelClassInit(g_class:gpointer; class_data:gpointer); cdecl;
begin
end;
procedure LCLListViewModelInit(instance:PGTypeInstance; g_class:gpointer); cdecl;
begin
end;
procedure LCLListViewModelInterfaceInit(g_iface:PGtkTreeModelIface; iface_data:gpointer); cdecl;
var
P: PPointer;
begin
// Do not change the order here!!
P := @g_iface^.row_changed;
P^ := @TLCLListViewModel.row_changed; Inc(P);
P^ := @TLCLListViewModel.row_inserted; Inc(P);
P^ := @TLCLListViewModel.row_has_child_toggled; Inc(P);
P^ := @TLCLListViewModel.row_deleted; Inc(P);
P^ := @TLCLListViewModel.rows_reordered; Inc(P);
P^ := @TLCLListViewModel.get_flags; Inc(P);
P^ := @TLCLListViewModel.get_n_columns; Inc(P);
P^ := @TLCLListViewModel.get_column_type; Inc(P);
P^ := @TLCLListViewModel.get_iter; Inc(P);
P^ := @TLCLListViewModel.get_path; Inc(P);
P^ := @TLCLListViewModel.get_value; Inc(P);
P^ := @TLCLListViewModel.iter_next; Inc(P);
P^ := @TLCLListViewModel.iter_children; Inc(P);
P^ := @TLCLListViewModel.iter_has_child; Inc(P);
P^ := @TLCLListViewModel.iter_n_children; Inc(P);
P^ := @TLCLListViewModel.iter_nth_child; Inc(P);
P^ := @TLCLListViewModel.iter_parent; Inc(P);
P^ := @TLCLListViewModel.ref_node; Inc(P);
P^ := @TLCLListViewModel.unref_node; Inc(P);
end;
{ TLCLListViewModel }
procedure TLCLListViewModel.row_changed(path: PGtkTreePath; iter: PGtkTreeIter
); cdecl;
begin
end;
procedure TLCLListViewModel.row_inserted(path: PGtkTreePath; iter: PGtkTreeIter); cdecl;
begin
end;
procedure TLCLListViewModel.row_has_child_toggled(path: PGtkTreePath;
iter: PGtkTreeIter); cdecl;
begin
end;
procedure TLCLListViewModel.row_deleted(path: PGtkTreePath); cdecl;
begin
end;
procedure TLCLListViewModel.rows_reordered(path: PGtkTreePath;
iter: PGtkTreeIter; new_order: Pgint); cdecl;
begin
end;
function TLCLListViewModel.get_flags(): TGtkTreeModelFlags; cdecl;
begin
Result := GTK_TREE_MODEL_LIST_ONLY or 0;
end;
function TLCLListViewModel.get_n_columns(): gint; cdecl;
begin
Result := 1;
end;
function TLCLListViewModel.get_column_type(index: gint): GType; cdecl;
begin
Result := G_TYPE_POINTER;
end;
function TLCLListViewModel.get_iter(iter: PGtkTreeIter; path: PGtkTreePath): gboolean; cdecl;
var
Index: PtrUInt;
begin
Result := False;
Index := gtk_tree_path_get_indices(path)[0];
if Index < ListView.Items.Count then
begin
ITer^.user_data:= Pointer(Index);
Exit(True);
end;
end;
function TLCLListViewModel.get_path(iter: PGtkTreeIter): PGtkTreePath; cdecl;
var
Index: PtrUint;
begin
Result := nil;
if iter = nil then
Exit;
Index := PtrUint(Iter^.user_data);
Result := gtk_tree_path_new_from_indices(Index, -1);
end;
procedure TLCLListViewModel.get_value(iter: PGtkTreeIter; column: gint;
value: PGValue); cdecl;
var
Index: Integer;
ValueType: GType;
Item: TLVItemHack;
SubIndex: Integer;
begin
Index := PtrUint(Iter^.user_data);
Item := TLVItemHack(ListView.Items.Item[Index]);
g_value_init(value, G_TYPE_POINTER);
g_value_set_pointer(value, Pointer(Item));
// We use custom renderers so the below is not needed and was never tested :)
{
Listview Columns in the tree model are stored like so
[ 0: Checked. 1: Pixbuf. 2: Caption. 3: Subitem(x) Pixbuf. 4: Subitem(x) Text etc
}
{ Case column of
0:
begin
g_value_init(value, G_TYPE_BOOLEAN);
g_value_set_boolean(value, Item.GetCheckedInternal);
end;
1:
begin
g_value_init(value, GTK_TYPE_PIXMAP);
//g_value_set_pointer(ListView.im); !!
g_value_set_pointer(value, nil);
end;
2:
begin
g_value_init(value, G_TYPE_STRING);
g_value_set_static_string(value,PChar(Item.Caption));
end;
else
SubIndex := (column - 3) div 2;
if Column and 1 = 0 then // Picbuf
begin
g_value_init(value, GTK_TYPE_PIXMAP);
//g_value_set_pointer(ListView.im); !!
g_value_set_pointer(value, nil);
end
else // Text;
begin
g_value_init(value, G_TYPE_STRING);
if SubIndex >= Item.SubItems.Count then
g_value_set_static_string(value,PChar(''))
else
g_value_set_static_string(value,PChar(Item.SubItems.Strings[SubIndex]));
end;
end;
}
end;
function TLCLListViewModel.iter_next(iter: PGtkTreeIter): gboolean; cdecl;
begin
Result := False;
if ListView = nil then
Exit;
Inc(PtrUInt(Iter^.user_data));
Result := PtrUint(Iter^.user_data) < ListView.items.Count;
end;
function TLCLListViewModel.iter_children(iter: PGtkTreeIter;
parent: PGtkTreeIter): gboolean; cdecl;
begin
Result := False;
end;
function TLCLListViewModel.iter_has_child(iter: PGtkTreeIter): gboolean;
cdecl;
begin
Result := false;
end;
function TLCLListViewModel.iter_n_children(iter: PGtkTreeIter): gint; cdecl;
begin
Result := 0;
if (Iter = nil) and (ListView <> nil) then
Result := ListView.Items.Count;
end;
function TLCLListViewModel.iter_nth_child(iter: PGtkTreeIter;
parent: PGtkTreeIter; n: gint): gboolean; cdecl;
begin
Result := False;
if (ListView = nil) or (parent <> nil) then
Exit;
if (Iter = nil) and (n < ListView.Items.Count) then begin
PtrUint(Iter^.user_data):=n;
Result := True;
end;
end;
function TLCLListViewModel.iter_parent(iter: PGtkTreeIter; child: PGtkTreeIter
): gboolean; cdecl;
begin
Result := False;
end;
procedure TLCLListViewModel.ref_node(iter: PGtkTreeIter); cdecl;
begin
end;
procedure TLCLListViewModel.unref_node(iter: PGtkTreeIter); cdecl;
begin
end;
procedure TLCLListViewModel.NotifyRowInserted(AIndex: PtrUInt);
var
Path: PGtkTreePath;
Iter: TGtkTreeIter;
begin
Iter.user_data:= Pointer(AIndex);
path :=gtk_tree_path_new_from_indices(AIndex, -1);
//emits a signal
gtk_tree_model_row_inserted(TreeModel, path, @iter);
gtk_tree_path_free(path);
end;
procedure TLCLListViewModel.NotifyRowDeleted(AIndex: PtrUInt);
var
Path: PGtkTreePath;
begin
path :=gtk_tree_path_new_from_indices(AIndex, -1);
//emits a signal
gtk_tree_model_row_deleted(TreeModel, path);
gtk_tree_path_free(path);
end;
function TLCLListViewModel.TreeModel: PGtkTreeModel; inline;
begin
Result := PGtkTreeModel(@Self);
end;
end.

View File

@ -231,7 +231,7 @@ type
implementation
uses Gtk2CellRenderer, GtkExtra;
uses Gtk2CellRenderer, GtkExtra{$IFNDEF USEORIGTREEMODEL}, Gtk2ListViewTreeModel{$ENDIF};
// Will be used commonly for ListViews and TreeViews
procedure GetCommonTreeViewWidgets(ATreeViewHandle: PGtkWidget;

View File

@ -764,10 +764,14 @@ begin
GetCommonTreeViewWidgets(PGtkWidget(ALV.Handle), Widgets);
with Widgets^ do begin
{$IFDEF USEORIGTREEMODEL}
if gtk_tree_model_iter_nth_child(TreeModel, @Iter, nil, AIndex) then
begin
gtk_list_store_remove(TreeModel, @Iter);
end;
{$ELSE}
PLCLListViewModel(TreeModel)^.NotifyRowDeleted(AIndex);
{$ENDIF}
end;
end;
@ -832,17 +836,25 @@ begin
if not WSCheckHandleAllocated(ALV, 'ItemInsert')
then Exit;
GetCommonTreeViewWidgets(PGtkWidget(ALV.Handle), Widgets);
with Widgets^ do
begin
if ItemCache = nil then
ItemCache := TStringList.Create;
{$IFDEF USEORIGTREEMODEL}
if AIndex = -1 then
Index := gtk_tree_model_iter_n_children(TreeModel, nil)
else
Index := AIndex;
gtk_list_store_insert_with_values(PGtkListStore(TreeModel), @Iter, Index, 0, Pointer(AItem), -1);
{$ELSE}
if not (lisfWSItemsCreated in ALV.Items.Flags) then
Exit;
PLCLListViewModel(TreeModel)^.NotifyRowInserted(AIndex);
{$ENDIF}
end;
end;
@ -1016,12 +1028,13 @@ var
begin
if not WSCheckHandleAllocated(ALV, 'ItemUpdate')
then Exit;
{$IFDEF USEORIGTREEMODEL}
GetCommonTreeViewWidgets(PGtkWidget(ALV.Handle), Widgets);
with Widgets^ do
if gtk_tree_model_iter_nth_child(TreeModel, @Iter, nil, AIndex) then
gtk_list_store_set(PGtkListStore(TreeModel), @Iter, [0, Pointer(AItem), -1]);
{$ENDIF}
end;
class function TGtk2WSCustomListView.CreateHandle(const AWinControl: TWinControl;
@ -1056,12 +1069,16 @@ begin
Images := nil;
PtrType := G_TYPE_POINTER;
{$IFDEF USEORIGTREEMODEL}
TreeModel := gtk_list_store_newv(1, @PtrType);
{$ENDIF}
TreeModel:= LCLListViewModelNew(TCustomListView(AWinControl));
MainView:=gtk_tree_view_new_with_model(TreeModel);
//MainView:=gtk_tree_view_new;
g_object_unref (G_OBJECT (TreeModel));
//gtk_tree_view_set_fixed_height_mode(PGtkTreeView(MainView), True);
TreeSelection := PGtkTreeSelection(gtk_tree_view_get_selection(PGtkTreeView(MainView)));
@ -1440,11 +1457,18 @@ begin
with Widgets^ do
begin
{$IFNDEF USEORIGTREEMODEL}
g_object_ref(TreeModel);
gtk_tree_view_set_model(PGtkTreeView(MainView), nil);
gtk_tree_view_set_model(PGtkTreeView(MainView), TreeModel);
g_object_unref(TreeModel);
{$ELSE}
if ItemCache = nil then
ItemCache := TStringList.Create;
gtk_list_store_clear(PGtkListStore(TreeModel));
for Index := 0 to AValue - 1 do
gtk_list_store_insert_with_values(PGtkListStore(TreeModel), @Iter, Index, 0, nil, -1);
{$ENDIF}
end;
end;