diff --git a/.gitattributes b/.gitattributes index cfd28379a3..e9bf5435fb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/lcl/comctrls.pp b/lcl/comctrls.pp index e89eb83d89..362a2b26e6 100644 --- a/lcl/comctrls.pp +++ b/lcl/comctrls.pp @@ -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; diff --git a/lcl/include/listitems.inc b/lcl/include/listitems.inc index 07ae180ad6..f9aae8c4df 100644 --- a/lcl/include/listitems.inc +++ b/lcl/include/listitems.inc @@ -193,6 +193,7 @@ begin FCacheIndex := n; WSCreateCacheItem; end; + Include(FFlags, lisfWSItemsCreated); end; {------------------------------------------------------------------------------} diff --git a/lcl/interfaces/gtk2/gtk2listviewtreemodel.pas b/lcl/interfaces/gtk2/gtk2listviewtreemodel.pas new file mode 100644 index 0000000000..e661172f85 --- /dev/null +++ b/lcl/interfaces/gtk2/gtk2listviewtreemodel.pas @@ -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. diff --git a/lcl/interfaces/gtk2/gtk2wscomctrls.pp b/lcl/interfaces/gtk2/gtk2wscomctrls.pp index 1b2c4eb216..c68a5cfc5a 100644 --- a/lcl/interfaces/gtk2/gtk2wscomctrls.pp +++ b/lcl/interfaces/gtk2/gtk2wscomctrls.pp @@ -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; diff --git a/lcl/interfaces/gtk2/gtk2wscustomlistview.inc b/lcl/interfaces/gtk2/gtk2wscustomlistview.inc index eb26f56538..718924534f 100644 --- a/lcl/interfaces/gtk2/gtk2wscustomlistview.inc +++ b/lcl/interfaces/gtk2/gtk2wscustomlistview.inc @@ -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;