///////////////////////////////////////////////////////////////////////////// // Name: src/gtk/dataview.cpp // Purpose: wxDataViewCtrl GTK+2 implementation // Author: Robert Roebling // Copyright: (c) 1998 Robert Roebling // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #if wxUSE_DATAVIEWCTRL #include "wx/dataview.h" #ifndef wxHAS_GENERIC_DATAVIEWCTRL #ifndef WX_PRECOMP #include "wx/log.h" #include "wx/dcclient.h" #include "wx/sizer.h" #include "wx/settings.h" #include "wx/crt.h" #endif #include "wx/stockitem.h" #include "wx/popupwin.h" #include "wx/listimpl.cpp" #include "wx/gtk/dc.h" #ifndef __WXGTK3__ #include "wx/gtk/dcclient.h" #endif #include "wx/gtk/private.h" #include "wx/gtk/private/event.h" #include "wx/gtk/private/gdkconv.h" #include "wx/gtk/private/list.h" #include "wx/gtk/private/treeview.h" using namespace wxGTKImpl; class wxGtkDataViewModelNotifier; #ifdef __WXGTK3__ #define wxConstGdkRect const GdkRectangle #else #define wxConstGdkRect GdkRectangle #endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- static wxDataViewCtrlInternal *gs_internal = NULL; class wxGtkTreeModelNode; extern "C" { typedef struct _GtkWxTreeModel GtkWxTreeModel; } // ---------------------------------------------------------------------------- // wxGtkTreePathList: self-destroying list of GtkTreePath objects. // ---------------------------------------------------------------------------- class wxGtkTreePathList : public wxGtkList { public: // Ctor takes ownership of the list. explicit wxGtkTreePathList(GList* list) : wxGtkList(list) { } ~wxGtkTreePathList() { // Delete the list contents, wxGtkList will delete the list itself. g_list_foreach(m_list, (GFunc)gtk_tree_path_free, NULL); } }; // ---------------------------------------------------------------------------- // wxGtkTreeSelectionLock: prevent selection from changing during the // lifetime of this object // ---------------------------------------------------------------------------- // Implementation note: it could be expected that setting the selection // function in this class ctor and resetting it back to the old value in its // dtor would work, However in GTK+2 gtk_tree_selection_get_select_function() // can't be passed NULL (see https://bugzilla.gnome.org/show_bug.cgi?id=626276 // which was only fixed in 2.90.5-304-g316b9da) so we can't do this. // // Instead, we always use the selection function (which // imposes extra overhead, albeit minimal one, on all selection operations) and // just set/reset the flag telling it whether it should allow or forbid the // selection. // // Also notice that currently only a single object of this class may exist at // any given moment. It's just simpler like this and we don't need anything // more for now. extern "C" { static gboolean wxdataview_selection_func(GtkTreeSelection * WXUNUSED(selection), GtkTreeModel * WXUNUSED(model), GtkTreePath * WXUNUSED(path), gboolean WXUNUSED(path_currently_selected), gpointer data) { return data == NULL; } } class wxGtkTreeSelectionLock { public: wxGtkTreeSelectionLock(GtkTreeSelection *selection, bool& alreadySet) : m_selection(selection) { wxASSERT_MSG( !ms_instance, "this class is not reentrant currently" ); ms_instance = this; if ( !alreadySet ) { alreadySet = true; CheckCurrentSelectionFunc(NULL); } else { CheckCurrentSelectionFunc(wxdataview_selection_func); } // Pass some non-NULL pointer as "data" for the callback, it doesn't // matter what it is as long as it's non-NULL. gtk_tree_selection_set_select_function(selection, wxdataview_selection_func, this, NULL); } ~wxGtkTreeSelectionLock() { CheckCurrentSelectionFunc(wxdataview_selection_func); gtk_tree_selection_set_select_function(m_selection, wxdataview_selection_func, NULL, NULL); ms_instance = NULL; } private: void CheckCurrentSelectionFunc(GtkTreeSelectionFunc func) { // We can only use gtk_tree_selection_get_select_function() with 2.14+ // so check for its availability both during compile- and run-time. #if GTK_CHECK_VERSION(2, 14, 0) if ( !wx_is_at_least_gtk2(14) ) return; // If this assert is triggered, it means the code elsewhere has called // gtk_tree_selection_set_select_function() but currently doing this // breaks this class so the code here needs to be changed. wxASSERT_MSG ( gtk_tree_selection_get_select_function(m_selection) == func, "selection function has changed unexpectedly, review this code!" ); #endif // GTK+ 2.14+ wxUnusedVar(func); } static wxGtkTreeSelectionLock *ms_instance; GtkTreeSelection * const m_selection; wxDECLARE_NO_COPY_CLASS(wxGtkTreeSelectionLock); }; wxGtkTreeSelectionLock *wxGtkTreeSelectionLock::ms_instance = NULL; //----------------------------------------------------------------------------- // wxDataViewCtrlInternal //----------------------------------------------------------------------------- WX_DECLARE_LIST(wxDataViewItem, ItemList); WX_DEFINE_LIST(ItemList) class wxDataViewCtrlInternal { public: wxDataViewCtrlInternal( wxDataViewCtrl *owner, wxDataViewModel *wx_model ); ~wxDataViewCtrlInternal(); // model iface GtkTreeModelFlags get_flags(); gboolean get_iter( GtkTreeIter *iter, GtkTreePath *path ); GtkTreePath *get_path( GtkTreeIter *iter); gboolean iter_next( GtkTreeIter *iter ); gboolean iter_children( GtkTreeIter *iter, GtkTreeIter *parent); gboolean iter_has_child( GtkTreeIter *iter ); gint iter_n_children( GtkTreeIter *iter ); gboolean iter_nth_child( GtkTreeIter *iter, GtkTreeIter *parent, gint n ); gboolean iter_parent( GtkTreeIter *iter, GtkTreeIter *child ); // dnd iface bool EnableDragSource( const wxDataFormat &format ); bool EnableDropTarget( const wxDataFormat &format ); gboolean row_draggable( GtkTreeDragSource *drag_source, GtkTreePath *path ); gboolean drag_data_delete( GtkTreeDragSource *drag_source, GtkTreePath* path ); gboolean drag_data_get( GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data ); gboolean drag_data_received( GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data ); gboolean row_drop_possible( GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection_data ); // notifactions from wxDataViewModel bool ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item ); bool ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item ); bool ItemChanged( const wxDataViewItem &item ); bool ValueChanged( const wxDataViewItem &item, unsigned int model_column ); bool Cleared(); bool BeforeReset(); bool AfterReset(); void Resort(); // sorting interface void SetSortOrder( GtkSortType sort_order ) { m_sort_order = sort_order; } GtkSortType GetSortOrder() const { return m_sort_order; } void SetSortColumn( int column ) { m_sort_column = column; } int GetSortColumn() const { return m_sort_column; } void SetDataViewSortColumn( wxDataViewColumn *column ) { m_dataview_sort_column = column; } wxDataViewColumn *GetDataViewSortColumn() { return m_dataview_sort_column; } bool IsSorted() const { return m_sort_column >= 0; } // Should we be sorted either because we have a configured sort column or // because we have a default sort order? bool ShouldBeSorted() const { return IsSorted() || GetDataViewModel()->HasDefaultCompare(); } // accessors wxDataViewModel* GetDataViewModel() { return m_wx_model; } const wxDataViewModel* GetDataViewModel() const { return m_wx_model; } wxDataViewCtrl* GetOwner() { return m_owner; } GtkWxTreeModel* GetGtkModel() { return m_gtk_model; } // item can be deleted already in the model int GetIndexOf( const wxDataViewItem &parent, const wxDataViewItem &item ); void OnInternalIdle(); protected: void InitTree(); void ScheduleRefresh(); wxGtkTreeModelNode *FindNode( const wxDataViewItem &item ); wxGtkTreeModelNode *FindNode( GtkTreeIter *iter ); wxGtkTreeModelNode *FindParentNode( const wxDataViewItem &item ); wxGtkTreeModelNode *FindParentNode( GtkTreeIter *iter ); void BuildBranch( wxGtkTreeModelNode *branch ); private: wxGtkTreeModelNode *m_root; wxDataViewModel *m_wx_model; GtkWxTreeModel *m_gtk_model; wxDataViewCtrl *m_owner; GtkSortType m_sort_order; wxDataViewColumn *m_dataview_sort_column; int m_sort_column; GtkTargetEntry m_dragSourceTargetEntry; wxCharBuffer m_dragSourceTargetEntryTarget; wxDataObject *m_dragDataObject; GtkTargetEntry m_dropTargetTargetEntry; wxCharBuffer m_dropTargetTargetEntryTarget; wxDataObject *m_dropDataObject; wxGtkDataViewModelNotifier *m_notifier; bool m_dirty; public: // Allow direct access to this one from wxDataViewCtrl as it's just a // simple flag and it doesn't make much sense to encapsulate it. bool m_selectionFuncSet; }; //----------------------------------------------------------------------------- // wxGtkTreeModelNode //----------------------------------------------------------------------------- static int LINKAGEMODE wxGtkTreeModelChildCmp( void** id1, void** id2 ) { int ret = gs_internal->GetDataViewModel()->Compare( wxDataViewItem(*id1), wxDataViewItem(*id2), gs_internal->GetSortColumn(), (gs_internal->GetSortOrder() == GTK_SORT_ASCENDING) ); return ret; } WX_DEFINE_ARRAY_PTR( wxGtkTreeModelNode*, wxGtkTreeModelNodes ); WX_DEFINE_ARRAY_PTR( void*, wxGtkTreeModelChildren ); class wxGtkTreeModelNode { public: wxGtkTreeModelNode( wxGtkTreeModelNode* parent, const wxDataViewItem &item, wxDataViewCtrlInternal *internal ) { m_parent = parent; m_item = item; m_internal = internal; } ~wxGtkTreeModelNode() { size_t count = m_nodes.GetCount(); size_t i; for (i = 0; i < count; i++) { wxGtkTreeModelNode *child = m_nodes.Item( i ); delete child; } } void AddNode( wxGtkTreeModelNode* child ) { m_nodes.Add( child ); void *id = child->GetItem().GetID(); m_children.Add( id ); if (m_internal->ShouldBeSorted()) { gs_internal = m_internal; m_children.Sort( &wxGtkTreeModelChildCmp ); } } void InsertNode( wxGtkTreeModelNode* child, unsigned pos ) { if (m_internal->ShouldBeSorted()) { AddNode(child); return; } void *id = child->GetItem().GetID(); // Insert into m_nodes so that the order of nodes in m_nodes is the // same as the order of their corresponding IDs in m_children: const unsigned int count = m_nodes.GetCount(); bool inserted = false; for (unsigned i = 0; i < count; i++) { wxGtkTreeModelNode *node = m_nodes[i]; int posInChildren = m_children.Index(node->GetItem().GetID()); if ( (unsigned)posInChildren >= pos ) { m_nodes.Insert(child, i); inserted = true; break; } } if ( !inserted ) m_nodes.Add(child); m_children.Insert( id, pos ); } void AddLeaf( void* id ) { InsertLeaf(id, m_children.size()); } void InsertLeaf( void* id, unsigned pos ) { m_children.Insert( id, pos ); if (m_internal->ShouldBeSorted()) { gs_internal = m_internal; m_children.Sort( &wxGtkTreeModelChildCmp ); } } void DeleteChild( void* id ) { m_children.Remove( id ); unsigned int count = m_nodes.GetCount(); unsigned int pos; for (pos = 0; pos < count; pos++) { wxGtkTreeModelNode *node = m_nodes.Item( pos ); if (node->GetItem().GetID() == id) { m_nodes.RemoveAt( pos ); delete node; break; } } } // returns position of child node for given item in children list or wxNOT_FOUND int FindChildByItem(const wxDataViewItem& item) const { const void* itemId = item.GetID(); const wxGtkTreeModelChildren& nodes = m_children; const int len = nodes.size(); for ( int i = 0; i < len; i++ ) { if ( nodes[i] == itemId ) return i; } return wxNOT_FOUND; } wxGtkTreeModelNode* GetParent() { return m_parent; } wxGtkTreeModelNodes &GetNodes() { return m_nodes; } wxGtkTreeModelChildren &GetChildren() { return m_children; } unsigned int GetChildCount() const { return m_children.GetCount(); } unsigned int GetNodesCount() const { return m_nodes.GetCount(); } wxDataViewItem &GetItem() { return m_item; } wxDataViewCtrlInternal *GetInternal() { return m_internal; } void Resort(); private: wxGtkTreeModelNode *m_parent; wxGtkTreeModelNodes m_nodes; wxGtkTreeModelChildren m_children; wxDataViewItem m_item; wxDataViewCtrlInternal *m_internal; }; //----------------------------------------------------------------------------- // data //----------------------------------------------------------------------------- extern bool g_blockEventsOnDrag; //----------------------------------------------------------------------------- // define new GTK+ class wxGtkTreeModel //----------------------------------------------------------------------------- extern "C" { #define GTK_TYPE_WX_TREE_MODEL (gtk_wx_tree_model_get_type ()) #define GTK_WX_TREE_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WX_TREE_MODEL, GtkWxTreeModel)) #define GTK_IS_WX_TREE_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WX_TREE_MODEL)) #define GTK_IS_WX_TREE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WX_TREE_MODEL)) struct _GtkWxTreeModel { GObject parent; /*< private >*/ gint stamp; wxDataViewCtrlInternal *internal; }; static GtkWxTreeModel *wxgtk_tree_model_new (void); static void wxgtk_tree_model_init (GTypeInstance* instance, void*); static void wxgtk_tree_model_tree_model_init (void* g_iface, void*); static void wxgtk_tree_model_sortable_init (void* g_iface, void*); static void wxgtk_tree_model_drag_source_init(void* g_iface, void*); static void wxgtk_tree_model_drag_dest_init (void* g_iface, void*); static GtkTreeModelFlags wxgtk_tree_model_get_flags (GtkTreeModel *tree_model); static gint wxgtk_tree_model_get_n_columns (GtkTreeModel *tree_model); static GType wxgtk_tree_model_get_column_type (GtkTreeModel *tree_model, gint index); static gboolean wxgtk_tree_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path); static GtkTreePath *wxgtk_tree_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter); static void wxgtk_tree_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value); static gboolean wxgtk_tree_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter); static gboolean wxgtk_tree_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent); static gboolean wxgtk_tree_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter); static gint wxgtk_tree_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter); static gboolean wxgtk_tree_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n); static gboolean wxgtk_tree_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child); /* sortable */ static gboolean wxgtk_tree_model_get_sort_column_id (GtkTreeSortable *sortable, gint *sort_column_id, GtkSortType *order); static void wxgtk_tree_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order); static void wxgtk_tree_model_set_sort_func (GtkTreeSortable *sortable, gint sort_column_id, GtkTreeIterCompareFunc func, gpointer data, GDestroyNotify destroy); static void wxgtk_tree_model_set_default_sort_func (GtkTreeSortable *sortable, GtkTreeIterCompareFunc func, gpointer data, GDestroyNotify destroy); static gboolean wxgtk_tree_model_has_default_sort_func (GtkTreeSortable *sortable); /* drag'n'drop */ static gboolean wxgtk_tree_model_row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path); static gboolean wxgtk_tree_model_drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path); static gboolean wxgtk_tree_model_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data); static gboolean wxgtk_tree_model_drag_data_received (GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data); static gboolean wxgtk_tree_model_row_drop_possible (GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection_data); static GType gtk_wx_tree_model_get_type (void) { static GType tree_model_type = 0; if (!tree_model_type) { const GTypeInfo tree_model_info = { sizeof (GObjectClass), NULL, /* base_init */ NULL, /* base_finalize */ NULL, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkWxTreeModel), 0, wxgtk_tree_model_init, NULL }; static const GInterfaceInfo tree_model_iface_info = { wxgtk_tree_model_tree_model_init, NULL, NULL }; static const GInterfaceInfo sortable_iface_info = { wxgtk_tree_model_sortable_init, NULL, NULL }; static const GInterfaceInfo drag_source_iface_info = { wxgtk_tree_model_drag_source_init, NULL, NULL }; static const GInterfaceInfo drag_dest_iface_info = { wxgtk_tree_model_drag_dest_init, NULL, NULL }; tree_model_type = g_type_register_static (G_TYPE_OBJECT, "GtkWxTreeModel", &tree_model_info, (GTypeFlags)0 ); g_type_add_interface_static (tree_model_type, GTK_TYPE_TREE_MODEL, &tree_model_iface_info); g_type_add_interface_static (tree_model_type, GTK_TYPE_TREE_SORTABLE, &sortable_iface_info); g_type_add_interface_static (tree_model_type, GTK_TYPE_TREE_DRAG_DEST, &drag_dest_iface_info); g_type_add_interface_static (tree_model_type, GTK_TYPE_TREE_DRAG_SOURCE, &drag_source_iface_info); } return tree_model_type; } static GtkWxTreeModel * wxgtk_tree_model_new(void) { GtkWxTreeModel *retval = (GtkWxTreeModel *) g_object_new (GTK_TYPE_WX_TREE_MODEL, NULL); return retval; } static void wxgtk_tree_model_tree_model_init(void* g_iface, void*) { GtkTreeModelIface* iface = static_cast(g_iface); iface->get_flags = wxgtk_tree_model_get_flags; iface->get_n_columns = wxgtk_tree_model_get_n_columns; iface->get_column_type = wxgtk_tree_model_get_column_type; iface->get_iter = wxgtk_tree_model_get_iter; iface->get_path = wxgtk_tree_model_get_path; iface->get_value = wxgtk_tree_model_get_value; iface->iter_next = wxgtk_tree_model_iter_next; iface->iter_children = wxgtk_tree_model_iter_children; iface->iter_has_child = wxgtk_tree_model_iter_has_child; iface->iter_n_children = wxgtk_tree_model_iter_n_children; iface->iter_nth_child = wxgtk_tree_model_iter_nth_child; iface->iter_parent = wxgtk_tree_model_iter_parent; } static void wxgtk_tree_model_sortable_init(void* g_iface, void*) { GtkTreeSortableIface* iface = static_cast(g_iface); iface->get_sort_column_id = wxgtk_tree_model_get_sort_column_id; iface->set_sort_column_id = wxgtk_tree_model_set_sort_column_id; iface->set_sort_func = wxgtk_tree_model_set_sort_func; iface->set_default_sort_func = wxgtk_tree_model_set_default_sort_func; iface->has_default_sort_func = wxgtk_tree_model_has_default_sort_func; } static void wxgtk_tree_model_drag_source_init(void* g_iface, void*) { GtkTreeDragSourceIface* iface = static_cast(g_iface); iface->row_draggable = wxgtk_tree_model_row_draggable; iface->drag_data_delete = wxgtk_tree_model_drag_data_delete; iface->drag_data_get = wxgtk_tree_model_drag_data_get; } static void wxgtk_tree_model_drag_dest_init(void* g_iface, void*) { GtkTreeDragDestIface* iface = static_cast(g_iface); iface->drag_data_received = wxgtk_tree_model_drag_data_received; iface->row_drop_possible = wxgtk_tree_model_row_drop_possible; } static void wxgtk_tree_model_init(GTypeInstance* instance, void*) { GtkWxTreeModel* tree_model = GTK_WX_TREE_MODEL(instance); tree_model->internal = NULL; // 0 is handled specially in wxGtkTreeCellDataFunc, so don't use it as the // stamp. do { tree_model->stamp = g_random_int(); } while ( tree_model->stamp == 0 ); } } // extern "C" //----------------------------------------------------------------------------- // implement callbacks from wxGtkTreeModel class by letting // them call the methods of wxWidgets' wxDataViewModel //----------------------------------------------------------------------------- static GtkTreeModelFlags wxgtk_tree_model_get_flags (GtkTreeModel *tree_model) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), (GtkTreeModelFlags)0 ); return wxtree_model->internal->get_flags(); } static gint wxgtk_tree_model_get_n_columns (GtkTreeModel *tree_model) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), 0); return wxtree_model->internal->GetDataViewModel()->GetColumnCount(); } static GType wxgtk_tree_model_get_column_type (GtkTreeModel *tree_model, gint index) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), G_TYPE_INVALID); GType gtype = G_TYPE_INVALID; wxString wxtype = wxtree_model->internal->GetDataViewModel()->GetColumnType( (unsigned int) index ); if (wxtype == wxT("string")) gtype = G_TYPE_STRING; else { gtype = G_TYPE_POINTER; // wxFAIL_MSG( wxT("non-string columns not supported for searching yet") ); } return gtype; } static gboolean wxgtk_tree_model_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE); return wxtree_model->internal->get_iter( iter, path ); } static GtkTreePath * wxgtk_tree_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter) { g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (tree_model), NULL); GtkWxTreeModel *wxtree_model = GTK_WX_TREE_MODEL (tree_model); if ( wxtree_model->stamp == 0 ) { // The model is temporarily invalid and can't be used, see Cleared(), // but we need to return some valid path from here -- just return an // empty one. return gtk_tree_path_new(); } g_return_val_if_fail (iter->stamp == wxtree_model->stamp, NULL); return wxtree_model->internal->get_path( iter ); } static void wxgtk_tree_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model) ); wxDataViewModel *model = wxtree_model->internal->GetDataViewModel(); wxString mtype = model->GetColumnType( (unsigned int) column ); if (mtype == wxT("string")) { wxVariant variant; g_value_init( value, G_TYPE_STRING ); wxDataViewItem item( (void*) iter->user_data ); model->GetValue( variant, item, (unsigned int) column ); g_value_set_string( value, variant.GetString().utf8_str() ); } else { wxFAIL_MSG( wxT("non-string columns not supported yet") ); } } static gboolean wxgtk_tree_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; // This happens when clearing the view by calling .._set_model( NULL ); if (iter->stamp == 0) return FALSE; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); g_return_val_if_fail (wxtree_model->stamp == iter->stamp, FALSE); return wxtree_model->internal->iter_next( iter ); } static gboolean wxgtk_tree_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); if (parent) { g_return_val_if_fail (wxtree_model->stamp == parent->stamp, FALSE); } return wxtree_model->internal->iter_children( iter, parent ); } static gboolean wxgtk_tree_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); g_return_val_if_fail (wxtree_model->stamp == iter->stamp, FALSE); return wxtree_model->internal->iter_has_child( iter ); } static gint wxgtk_tree_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), 0); g_return_val_if_fail ( !iter || wxtree_model->stamp == iter->stamp, 0); return wxtree_model->internal->iter_n_children( iter ); } static gboolean wxgtk_tree_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); return wxtree_model->internal->iter_nth_child( iter, parent, n ); } static gboolean wxgtk_tree_model_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); g_return_val_if_fail (wxtree_model->stamp == child->stamp, FALSE); return wxtree_model->internal->iter_parent( iter, child ); } /* drag'n'drop iface */ static gboolean wxgtk_tree_model_row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_source; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); return wxtree_model->internal->row_draggable( drag_source, path ); } static gboolean wxgtk_tree_model_drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_source; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); return wxtree_model->internal->drag_data_delete( drag_source, path ); } static gboolean wxgtk_tree_model_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_source; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); #if 0 wxPrintf( "drag_get_data\n"); wxGtkString atom_selection(gdk_atom_name(selection_data->selection)); wxPrintf( "selection %s\n", wxString::FromAscii(atom_selection) ); wxGtkString atom_target(gdk_atom_name(selection_data->target)); wxPrintf( "target %s\n", wxString::FromAscii(atom_target) ); wxGtkString atom_type(gdk_atom_name(selection_data->type)); wxPrintf( "type %s\n", wxString::FromAscii(atom_type) ); wxPrintf( "format %d\n", selection_data->format ); #endif return wxtree_model->internal->drag_data_get( drag_source, path, selection_data ); } static gboolean wxgtk_tree_model_drag_data_received (GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_dest; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); return wxtree_model->internal->drag_data_received( drag_dest, dest, selection_data ); } static gboolean wxgtk_tree_model_row_drop_possible (GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection_data) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_dest; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE); return wxtree_model->internal->row_drop_possible( drag_dest, dest_path, selection_data ); } /* sortable iface */ static gboolean wxgtk_tree_model_get_sort_column_id (GtkTreeSortable *sortable, gint *sort_column_id, GtkSortType *order) { GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) sortable; g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (sortable), FALSE); if (!wxtree_model->internal->IsSorted()) { if (sort_column_id) *sort_column_id = -1; return TRUE; } if (sort_column_id) *sort_column_id = wxtree_model->internal->GetSortColumn(); if (order) *order = wxtree_model->internal->GetSortOrder(); return TRUE; } static wxDataViewColumn *gs_lastLeftClickHeader = NULL; static void wxgtk_tree_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order) { GtkWxTreeModel *tree_model = (GtkWxTreeModel *) sortable; g_return_if_fail (GTK_IS_WX_TREE_MODEL (sortable) ); tree_model->internal->SetDataViewSortColumn( gs_lastLeftClickHeader ); if ((sort_column_id != (gint) tree_model->internal->GetSortColumn()) || (order != tree_model->internal->GetSortOrder())) { tree_model->internal->SetSortColumn( sort_column_id ); tree_model->internal->SetSortOrder( order ); gtk_tree_sortable_sort_column_changed (sortable); tree_model->internal->GetDataViewModel()->Resort(); } if (gs_lastLeftClickHeader) { wxDataViewCtrl *dv = tree_model->internal->GetOwner(); wxDataViewEvent event(wxEVT_DATAVIEW_COLUMN_SORTED, dv, gs_lastLeftClickHeader); dv->HandleWindowEvent( event ); } gs_lastLeftClickHeader = NULL; } static void wxgtk_tree_model_set_sort_func (GtkTreeSortable *sortable, gint WXUNUSED(sort_column_id), GtkTreeIterCompareFunc func, gpointer WXUNUSED(data), GDestroyNotify WXUNUSED(destroy)) { g_return_if_fail (GTK_IS_WX_TREE_MODEL (sortable) ); g_return_if_fail (func != NULL); } static void wxgtk_tree_model_set_default_sort_func (GtkTreeSortable *sortable, GtkTreeIterCompareFunc func, gpointer WXUNUSED(data), GDestroyNotify WXUNUSED(destroy)) { g_return_if_fail (GTK_IS_WX_TREE_MODEL (sortable) ); g_return_if_fail (func != NULL); //wxPrintf( "wxgtk_tree_model_set_default_sort_func\n" ); // TODO: remove this code } static gboolean wxgtk_tree_model_has_default_sort_func (GtkTreeSortable *sortable) { g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (sortable), FALSE ); return FALSE; } //----------------------------------------------------------------------------- // define new GTK+ class GtkWxRendererText //----------------------------------------------------------------------------- extern "C" { #define GTK_TYPE_WX_CELL_RENDERER_TEXT (gtk_wx_cell_renderer_text_get_type ()) #define GTK_WX_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WX_CELL_RENDERER_TEXT, GtkWxCellRendererText)) #define GTK_IS_WX_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WX_CELL_RENDERER_TEXT)) #define GTK_IS_WX_CELL_RENDERER_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WX_CELL_RENDERER_TEXT)) typedef struct _GtkWxCellRendererText GtkWxCellRendererText; struct _GtkWxCellRendererText { GtkCellRendererText parent; wxDataViewRenderer *wx_renderer; }; static GtkWxCellRendererText *gtk_wx_cell_renderer_text_new (void); static void gtk_wx_cell_renderer_text_init ( GTypeInstance* instance, void*); static void gtk_wx_cell_renderer_text_class_init( void* klass, void*); static GtkCellEditable *gtk_wx_cell_renderer_text_start_editing( GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path, wxConstGdkRect *background_area, wxConstGdkRect *cell_area, GtkCellRendererState flags ); static GObjectClass *text_cell_parent_class = NULL; } // extern "C" static GType gtk_wx_cell_renderer_text_get_type (void) { static GType cell_wx_type = 0; if (!cell_wx_type) { const GTypeInfo cell_wx_info = { sizeof (GtkCellRendererTextClass), NULL, /* base_init */ NULL, /* base_finalize */ gtk_wx_cell_renderer_text_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkWxCellRendererText), 0, /* n_preallocs */ gtk_wx_cell_renderer_text_init, NULL }; cell_wx_type = g_type_register_static( GTK_TYPE_CELL_RENDERER_TEXT, "GtkWxCellRendererText", &cell_wx_info, (GTypeFlags)0 ); } return cell_wx_type; } static void gtk_wx_cell_renderer_text_init(GTypeInstance* instance, void*) { GtkWxCellRendererText* cell = GTK_WX_CELL_RENDERER_TEXT(instance); cell->wx_renderer = NULL; } static void gtk_wx_cell_renderer_text_class_init(void* klass, void*) { GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); text_cell_parent_class = (GObjectClass*) g_type_class_peek_parent (klass); cell_class->start_editing = gtk_wx_cell_renderer_text_start_editing; } static GtkWxCellRendererText* gtk_wx_cell_renderer_text_new (void) { return (GtkWxCellRendererText*) g_object_new (GTK_TYPE_WX_CELL_RENDERER_TEXT, NULL); } static GtkCellEditable *gtk_wx_cell_renderer_text_start_editing( GtkCellRenderer *gtk_renderer, GdkEvent *gdk_event, GtkWidget *widget, const gchar *path, wxConstGdkRect *background_area, wxConstGdkRect *cell_area, GtkCellRendererState flags ) { GtkWxCellRendererText *wxgtk_renderer = (GtkWxCellRendererText *) gtk_renderer; wxDataViewRenderer *wx_renderer = wxgtk_renderer->wx_renderer; wxDataViewColumn *column = wx_renderer->GetOwner(); wxDataViewItem item(column->GetOwner()->GTKPathToItem(wxGtkTreePath(path))); wxDataViewCtrl *dv = column->GetOwner(); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_START_EDITING, dv, column, item); dv->HandleWindowEvent( event ); if (event.IsAllowed()) return GTK_CELL_RENDERER_CLASS(text_cell_parent_class)-> start_editing( gtk_renderer, gdk_event, widget, path, background_area, cell_area, flags ); else return NULL; } // ---------------------------------------------------------------------------- // GTK+ class GtkWxCellEditorBin: needed only to implement GtkCellEditable for // our editor widgets which don't necessarily implement it on their own. // ---------------------------------------------------------------------------- enum { CELL_EDITOR_BIN_PROP_0, CELL_EDITOR_BIN_PROP_EDITING_CANCELED }; extern "C" { #define GTK_TYPE_WX_CELL_EDITOR_BIN (gtk_wx_cell_editor_bin_get_type ()) // In GTK+ < 3.8 GtkBin can't be used as widget base type without defining our // own size_allocate and related (either size_request for GTK+ 2 or // get_preferred_height for GTK+ 3) vfuncs, so we use GtkHBox instead. But in // GTK+ 4, GtkHBox is removed, so we do use GtkBin with it. #ifdef __WXGTK4__ typedef GtkBin GtkWxCellEditorBinBase; typedef GtkBinClass GtkWxCellEditorBinBaseClass; // Notice that this can't be just a (const) variable as GTK+ type constants // are actually macros expanding into function calls, which shouldn't be // performed before the library is initialized, so we need to use either an // inline function or a define, which is simpler. #define GtkWxCellEditorBinBaseType GTK_TYPE_BIN #else // GTK+ < 4 // GtkHBox is deprecated since 3.2, so avoid warnings about using it. wxGCC_WARNING_SUPPRESS(deprecated-declarations) typedef GtkHBox GtkWxCellEditorBinBase; typedef GtkHBoxClass GtkWxCellEditorBinBaseClass; #define GtkWxCellEditorBinBaseType GTK_TYPE_HBOX wxGCC_WARNING_RESTORE(deprecated-declarations) #endif // GTK+ version struct GtkWxCellEditorBin { GtkWxCellEditorBinBase parent; // The actual user-created editor. wxWindow* editor; }; static GtkWidget* gtk_wx_cell_editor_bin_new(wxWindow* editor); static void gtk_wx_cell_editor_bin_class_init(void* klass, void*); static void gtk_wx_cell_editor_bin_get_property(GObject*, guint, GValue*, GParamSpec*); static void gtk_wx_cell_editor_bin_set_property(GObject*, guint, const GValue*, GParamSpec*); static void gtk_wx_cell_editor_bin_cell_editable_init(void* g_iface, void*); static void gtk_wx_cell_editor_bin_cell_editable_start_editing( GtkCellEditable *cell_editable, GdkEvent *event); } static GType gtk_wx_cell_editor_bin_get_type() { static GType cell_editor_bin_type = 0; if ( !cell_editor_bin_type ) { const GTypeInfo cell_editor_bin_info = { sizeof (GtkWxCellEditorBinBaseClass), NULL, /* base_init */ NULL, /* base_finalize */ gtk_wx_cell_editor_bin_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkWxCellEditorBin), 0, /* n_preallocs */ NULL, // init NULL }; cell_editor_bin_type = g_type_register_static( GtkWxCellEditorBinBaseType, "GtkWxCellEditorBin", &cell_editor_bin_info, (GTypeFlags)0 ); static const GInterfaceInfo cell_editable_iface_info = { gtk_wx_cell_editor_bin_cell_editable_init, NULL, NULL }; g_type_add_interface_static (cell_editor_bin_type, GTK_TYPE_CELL_EDITABLE, &cell_editable_iface_info); } return cell_editor_bin_type; } static void gtk_wx_cell_editor_bin_class_init(void* klass, void*) { GObjectClass* const oclass = G_OBJECT_CLASS(klass); oclass->set_property = gtk_wx_cell_editor_bin_set_property; oclass->get_property = gtk_wx_cell_editor_bin_get_property; if (wx_is_at_least_gtk2(20)) { g_object_class_override_property(oclass, CELL_EDITOR_BIN_PROP_EDITING_CANCELED, "editing-canceled"); } } // We need to provide these virtual methods as we must support the // editing-canceled property, but they don't seem to be actually ever called, // so it's not clear if we must implement them and how. For now just do // nothing. static void gtk_wx_cell_editor_bin_get_property(GObject*, guint, GValue*, GParamSpec*) { } static void gtk_wx_cell_editor_bin_set_property(GObject*, guint, const GValue*, GParamSpec*) { } static GtkWidget* gtk_wx_cell_editor_bin_new(wxWindow* editor) { if ( !editor ) return NULL; GtkWxCellEditorBin* const bin = (GtkWxCellEditorBin*)g_object_new (GTK_TYPE_WX_CELL_EDITOR_BIN, NULL); bin->editor = editor; gtk_container_add(GTK_CONTAINER(bin), editor->m_widget); return GTK_WIDGET(bin); } // GtkCellEditable interface implementation for GtkWxCellEditorBin static void gtk_wx_cell_editor_bin_cell_editable_init(void* g_iface, void*) { GtkCellEditableIface* iface = static_cast(g_iface); iface->start_editing = gtk_wx_cell_editor_bin_cell_editable_start_editing; } static void gtk_wx_cell_editor_bin_cell_editable_start_editing(GtkCellEditable *cell_editable, GdkEvent *event) { GtkWxCellEditorBin* const bin = (GtkWxCellEditorBin *)cell_editable; // If we have an editable widget inside the editor, forward to it. for ( wxWindow* win = bin->editor; win; ) { GtkWidget* const widget = win->m_widget; if ( GTK_IS_CELL_EDITABLE(widget) ) { gtk_cell_editable_start_editing(GTK_CELL_EDITABLE(widget), event); break; } if ( win == bin->editor ) win = win->GetChildren().front(); else win = win->GetNextSibling(); } } //----------------------------------------------------------------------------- // define new GTK+ class GtkWxCellRenderer //----------------------------------------------------------------------------- extern "C" { #define GTK_TYPE_WX_CELL_RENDERER (gtk_wx_cell_renderer_get_type ()) #define GTK_WX_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WX_CELL_RENDERER, GtkWxCellRenderer)) #define GTK_IS_WX_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WX_CELL_RENDERER)) #define GTK_IS_WX_CELL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WX_CELL_RENDERER)) typedef struct _GtkWxCellRenderer GtkWxCellRenderer; struct _GtkWxCellRenderer { GtkCellRenderer parent; /*< private >*/ wxDataViewCustomRenderer *cell; // Non null only while editing. GtkWidget* editor_bin; }; static GtkCellRenderer *gtk_wx_cell_renderer_new (void); static void gtk_wx_cell_renderer_init ( GTypeInstance* instance, void*); static void gtk_wx_cell_renderer_class_init( void* klass, void*); static void gtk_wx_cell_renderer_get_size ( GtkCellRenderer *cell, GtkWidget *widget, wxConstGdkRect *rectangle, gint *x_offset, gint *y_offset, gint *width, gint *height ); static void gtk_wx_cell_renderer_render ( GtkCellRenderer *cell, #ifdef __WXGTK3__ cairo_t* cr, #else GdkWindow *window, #endif GtkWidget *widget, wxConstGdkRect *background_area, wxConstGdkRect *cell_area, #ifndef __WXGTK3__ GdkRectangle *expose_area, #endif GtkCellRendererState flags ); static gboolean gtk_wx_cell_renderer_activate( GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path, wxConstGdkRect *background_area, wxConstGdkRect *cell_area, GtkCellRendererState flags ); static GtkCellEditable *gtk_wx_cell_renderer_start_editing( GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path, wxConstGdkRect *background_area, wxConstGdkRect *cell_area, GtkCellRendererState flags ); } // extern "C" static GType gtk_wx_cell_renderer_get_type (void) { static GType cell_wx_type = 0; if (!cell_wx_type) { const GTypeInfo cell_wx_info = { sizeof (GtkCellRendererClass), NULL, /* base_init */ NULL, /* base_finalize */ gtk_wx_cell_renderer_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkWxCellRenderer), 0, /* n_preallocs */ gtk_wx_cell_renderer_init, NULL }; cell_wx_type = g_type_register_static( GTK_TYPE_CELL_RENDERER, "GtkWxCellRenderer", &cell_wx_info, (GTypeFlags)0 ); } return cell_wx_type; } static void gtk_wx_cell_renderer_init(GTypeInstance* instance, void*) { GtkWxCellRenderer* cell = GTK_WX_CELL_RENDERER(instance); cell->cell = NULL; cell->editor_bin = NULL; } static void gtk_wx_cell_renderer_class_init(void* klass, void*) { GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); cell_class->get_size = gtk_wx_cell_renderer_get_size; cell_class->render = gtk_wx_cell_renderer_render; cell_class->activate = gtk_wx_cell_renderer_activate; cell_class->start_editing = gtk_wx_cell_renderer_start_editing; } static GtkCellRenderer* gtk_wx_cell_renderer_new (void) { return (GtkCellRenderer*) g_object_new (GTK_TYPE_WX_CELL_RENDERER, NULL); } static GtkCellEditable *gtk_wx_cell_renderer_start_editing( GtkCellRenderer *renderer, GdkEvent *WXUNUSED(event), GtkWidget *WXUNUSED(widget), const gchar *path, wxConstGdkRect *WXUNUSED(background_area), wxConstGdkRect *cell_area, GtkCellRendererState WXUNUSED(flags) ) { GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer; wxDataViewCustomRenderer *cell = wxrenderer->cell; // Renderer doesn't support in-place editing if (!cell->HasEditorCtrl()) return NULL; // An in-place editing control is still around if (cell->GetEditorCtrl()) return NULL; wxDataViewItem item(cell->GetOwner()->GetOwner()->GTKPathToItem(wxGtkTreePath(path))); if (!cell->StartEditing(item, wxRectFromGDKRect(cell_area))) return NULL; wxrenderer->editor_bin = gtk_wx_cell_editor_bin_new(cell->GetEditorCtrl()); gtk_widget_show(wxrenderer->editor_bin); return GTK_CELL_EDITABLE(wxrenderer->editor_bin); } static void gtk_wx_cell_renderer_get_size (GtkCellRenderer *renderer, GtkWidget *WXUNUSED(widget), wxConstGdkRect *cell_area, gint *x_offset, gint *y_offset, gint *width, gint *height) { GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer; wxDataViewCustomRenderer *cell = wxrenderer->cell; wxSize size = cell->GetSize(); wxDataViewCtrl * const ctrl = cell->GetOwner()->GetOwner(); // Uniform row height, if specified, overrides the value returned by the // renderer. if ( !ctrl->HasFlag(wxDV_VARIABLE_LINE_HEIGHT) ) { const int uniformHeight = ctrl->GTKGetUniformRowHeight(); if ( uniformHeight > 0 ) size.y = uniformHeight; } int xpad, ypad; gtk_cell_renderer_get_padding(renderer, &xpad, &ypad); int calc_width = xpad * 2 + size.x; int calc_height = ypad * 2 + size.y; if (x_offset) *x_offset = 0; if (y_offset) *y_offset = 0; if (cell_area && size.x > 0 && size.y > 0) { float xalign, yalign; gtk_cell_renderer_get_alignment(renderer, &xalign, &yalign); if (x_offset) { *x_offset = int(xalign * (cell_area->width - calc_width - 2 * xpad)); *x_offset = MAX(*x_offset, 0) + xpad; } if (y_offset) { *y_offset = int(yalign * (cell_area->height - calc_height - 2 * ypad)); *y_offset = MAX(*y_offset, 0) + ypad; } } if (width) *width = calc_width; if (height) *height = calc_height; } struct wxDataViewCustomRenderer::GTKRenderParams { #ifdef __WXGTK3__ cairo_t* cr; #else GdkWindow* window; GdkRectangle* expose_area; #endif GtkWidget* widget; wxConstGdkRect* background_area; int flags; }; static void gtk_wx_cell_renderer_render (GtkCellRenderer *renderer, #ifdef __WXGTK3__ cairo_t* cr, #else GdkWindow *window, #endif GtkWidget *widget, wxConstGdkRect *background_area, wxConstGdkRect *cell_area, #ifndef __WXGTK3__ GdkRectangle *expose_area, #endif GtkCellRendererState flags) { GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer; wxDataViewCustomRenderer *cell = wxrenderer->cell; wxDataViewCustomRenderer::GTKRenderParams renderParams; #ifdef __WXGTK3__ renderParams.cr = cr; #else renderParams.window = window; renderParams.expose_area = expose_area; #endif renderParams.widget = widget; renderParams.background_area = background_area; renderParams.flags = flags; cell->GTKSetRenderParams(&renderParams); wxRect rect(wxRectFromGDKRect(cell_area)); int xpad, ypad; gtk_cell_renderer_get_padding(renderer, &xpad, &ypad); rect = rect.Deflate(xpad, ypad); wxDC* dc = cell->GetDC(); #ifdef __WXGTK3__ wxGraphicsContext* context = dc->GetGraphicsContext(); void* nativeContext = NULL; if (context) nativeContext = context->GetNativeContext(); if (cr != nativeContext) dc->SetGraphicsContext(wxGraphicsContext::CreateFromNative(cr)); #else wxWindowDCImpl *impl = (wxWindowDCImpl *) dc->GetImpl(); // Reinitialize wxWindowDC's GDK window if drawing occurs into a different // window such as a DnD drop window. if (window != impl->m_gdkwindow) { impl->Destroy(); impl->m_gdkwindow = window; impl->SetUpDC(); } #endif int state = 0; if (flags & GTK_CELL_RENDERER_SELECTED) state |= wxDATAVIEW_CELL_SELECTED; if (flags & GTK_CELL_RENDERER_PRELIT) state |= wxDATAVIEW_CELL_PRELIT; if (flags & GTK_CELL_RENDERER_INSENSITIVE) state |= wxDATAVIEW_CELL_INSENSITIVE; if (flags & GTK_CELL_RENDERER_FOCUSED) state |= wxDATAVIEW_CELL_FOCUSED; cell->WXCallRender( rect, dc, state ); cell->GTKSetRenderParams(NULL); #ifdef __WXGTK3__ dc->SetGraphicsContext(NULL); #endif } static gboolean gtk_wx_cell_renderer_activate( GtkCellRenderer *renderer, GdkEvent *event, GtkWidget *widget, const gchar *path, wxConstGdkRect *WXUNUSED(background_area), wxConstGdkRect *cell_area, GtkCellRendererState WXUNUSED(flags) ) { GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer; wxDataViewCustomRenderer *cell = wxrenderer->cell; GdkRectangle rect; gtk_wx_cell_renderer_get_size (renderer, widget, cell_area, &rect.x, &rect.y, &rect.width, &rect.height); rect.x += cell_area->x; rect.y += cell_area->y; int xpad, ypad; gtk_cell_renderer_get_padding(renderer, &xpad, &ypad); rect.width -= xpad * 2; rect.height -= ypad * 2; wxRect renderrect(wxRectFromGDKRect(&rect)); wxDataViewCtrl * const ctrl = cell->GetOwner()->GetOwner(); wxDataViewModel *model = ctrl->GetModel(); wxDataViewItem item(ctrl->GTKPathToItem(wxGtkTreePath(path))); unsigned int model_col = cell->GetOwner()->GetModelColumn(); if ( !event ) { // activated by return cell->ActivateCell(renderrect, model, item, model_col, NULL); } else if ( event->type == GDK_BUTTON_PRESS ) { GdkEventButton *button_event = (GdkEventButton*)event; if ( button_event->button == 1 ) { wxMouseEvent mouse_event(wxEVT_LEFT_DOWN); InitMouseEvent(ctrl, mouse_event, button_event); mouse_event.m_x -= renderrect.x; mouse_event.m_y -= renderrect.y; return cell->ActivateCell(renderrect, model, item, model_col, &mouse_event); } } wxLogDebug("unexpected event type in gtk_wx_cell_renderer_activate()"); return false; } // --------------------------------------------------------- // wxGtkDataViewModelNotifier // --------------------------------------------------------- class wxGtkDataViewModelNotifier: public wxDataViewModelNotifier { public: wxGtkDataViewModelNotifier( wxDataViewModel *wx_model, wxDataViewCtrlInternal *internal ); ~wxGtkDataViewModelNotifier(); virtual bool ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item ) wxOVERRIDE; virtual bool ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item ) wxOVERRIDE; virtual bool ItemChanged( const wxDataViewItem &item ) wxOVERRIDE; virtual bool ValueChanged( const wxDataViewItem &item, unsigned int model_column ) wxOVERRIDE; virtual bool Cleared() wxOVERRIDE; virtual void Resort() wxOVERRIDE; virtual bool BeforeReset() wxOVERRIDE; virtual bool AfterReset() wxOVERRIDE; void UpdateLastCount(); private: wxDataViewModel *m_wx_model; wxDataViewCtrlInternal *m_internal; }; // --------------------------------------------------------- // wxGtkDataViewListModelNotifier // --------------------------------------------------------- wxGtkDataViewModelNotifier::wxGtkDataViewModelNotifier( wxDataViewModel *wx_model, wxDataViewCtrlInternal *internal ) { m_wx_model = wx_model; m_internal = internal; } wxGtkDataViewModelNotifier::~wxGtkDataViewModelNotifier() { m_wx_model = NULL; m_internal = NULL; } bool wxGtkDataViewModelNotifier::ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item ) { m_internal->ItemAdded( parent, item ); GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel(); GtkTreeIter iter; iter.stamp = wxgtk_model->stamp; iter.user_data = item.GetID(); wxGtkTreePath path(wxgtk_tree_model_get_path( GTK_TREE_MODEL(wxgtk_model), &iter )); gtk_tree_model_row_inserted( GTK_TREE_MODEL(wxgtk_model), path, &iter); return true; } bool wxGtkDataViewModelNotifier::ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item ) { GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel(); #if 0 // using _get_path for a deleted item cannot be // a good idea GtkTreeIter iter; iter.stamp = wxgtk_model->stamp; iter.user_data = (gpointer) item.GetID(); wxGtkTreePath path(wxgtk_tree_model_get_path( GTK_TREE_MODEL(wxgtk_model), &iter )); #else // so get the path from the parent GtkTreeIter parentIter; parentIter.stamp = wxgtk_model->stamp; parentIter.user_data = (gpointer) parent.GetID(); wxGtkTreePath parentPath(wxgtk_tree_model_get_path( GTK_TREE_MODEL(wxgtk_model), &parentIter )); // and add the final index ourselves wxGtkTreePath path(gtk_tree_path_copy(parentPath)); int index = m_internal->GetIndexOf( parent, item ); gtk_tree_path_append_index( path, index ); #endif m_internal->ItemDeleted( parent, item ); gtk_tree_model_row_deleted( GTK_TREE_MODEL(wxgtk_model), path ); // Did we remove the last child, causing 'parent' to become a leaf? if ( !m_wx_model->IsContainer(parent) ) { gtk_tree_model_row_has_child_toggled ( GTK_TREE_MODEL(wxgtk_model), parentPath, &parentIter ); } return true; } void wxGtkDataViewModelNotifier::Resort() { m_internal->Resort(); } bool wxGtkDataViewModelNotifier::ItemChanged( const wxDataViewItem &item ) { GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel(); GtkTreeIter iter; iter.stamp = wxgtk_model->stamp; iter.user_data = (gpointer) item.GetID(); wxGtkTreePath path(wxgtk_tree_model_get_path( GTK_TREE_MODEL(wxgtk_model), &iter )); gtk_tree_model_row_changed( GTK_TREE_MODEL(wxgtk_model), path, &iter ); m_internal->ItemChanged( item ); return true; } bool wxGtkDataViewModelNotifier::ValueChanged( const wxDataViewItem &item, unsigned int model_column ) { GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel(); wxDataViewCtrl *ctrl = m_internal->GetOwner(); // This adds GTK+'s missing MVC logic for ValueChanged unsigned int index; for (index = 0; index < ctrl->GetColumnCount(); index++) { wxDataViewColumn *column = ctrl->GetColumn( index ); if (column->GetModelColumn() == model_column) { GtkTreeView *widget = GTK_TREE_VIEW(ctrl->GtkGetTreeView()); GtkTreeViewColumn *gcolumn = GTK_TREE_VIEW_COLUMN(column->GetGtkHandle()); // Don't attempt to refresh not yet realized tree, it is useless // and results in GTK errors. if ( gtk_widget_get_realized(ctrl->GtkGetTreeView()) ) { // Get cell area GtkTreeIter iter; iter.stamp = wxgtk_model->stamp; iter.user_data = (gpointer) item.GetID(); wxGtkTreePath path(wxgtk_tree_model_get_path( GTK_TREE_MODEL(wxgtk_model), &iter )); GdkRectangle cell_area; gtk_tree_view_get_cell_area( widget, path, gcolumn, &cell_area ); // Don't try to redraw the column if it's invisible, this just // results in "BUG" messages from pixman_region32_init_rect() // and would be useful even if it didn't anyhow. if ( cell_area.width > 0 && cell_area.height > 0 ) { #ifdef __WXGTK3__ GtkAdjustment* hadjust = gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(widget)); #else GtkAdjustment* hadjust = gtk_tree_view_get_hadjustment( widget ); #endif double d = gtk_adjustment_get_value( hadjust ); int xdiff = (int) d; GtkAllocation a; gtk_widget_get_allocation(GTK_WIDGET(gtk_tree_view_column_get_button(gcolumn)), &a); int ydiff = a.height; // Redraw gtk_widget_queue_draw_area( GTK_WIDGET(widget), cell_area.x - xdiff, ydiff + cell_area.y, cell_area.width, cell_area.height ); } } m_internal->ValueChanged( item, model_column ); return true; } } return false; } bool wxGtkDataViewModelNotifier::BeforeReset() { GtkWidget *treeview = m_internal->GetOwner()->GtkGetTreeView(); gtk_tree_view_set_model( GTK_TREE_VIEW(treeview), NULL ); return true; } bool wxGtkDataViewModelNotifier::AfterReset() { GtkWidget *treeview = m_internal->GetOwner()->GtkGetTreeView(); GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel(); m_internal->Cleared(); gtk_tree_view_set_model( GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(wxgtk_model) ); return true; } bool wxGtkDataViewModelNotifier::Cleared() { GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel(); // There is no call to tell the model that everything // has been deleted so call row_deleted() for every // child of root... // It is important to avoid selection changed events being generated from // here as they would reference the already deleted model items, which // would result in crashes in any code attempting to handle these events. wxDataViewCtrl::SelectionEventsSuppressor noSelection(m_internal->GetOwner()); // We also need to prevent wxGtkTreeCellDataFunc from using the model items // not existing any longer, so change the model stamp to indicate that it // temporarily can't be used. const gint stampOrig = wxgtk_model->stamp; wxgtk_model->stamp = 0; { wxGtkTreePath path(gtk_tree_path_new_first()); // points to root const int count = m_internal->iter_n_children( NULL ); // number of children of root for (int i = 0; i < count; i++) gtk_tree_model_row_deleted( GTK_TREE_MODEL(wxgtk_model), path ); } wxgtk_model->stamp = stampOrig; m_internal->Cleared(); return true; } // --------------------------------------------------------- // wxDataViewRenderer // --------------------------------------------------------- static void wxgtk_cell_editable_editing_done( GtkCellEditable *editable, wxDataViewRenderer *wxrenderer ) { // "editing-cancelled" property is documented as being new since 2.20 in // GtkCellEditable, but seems to have existed basically forever (since GTK+ // 1.3 days) in GtkCellRendererText, so try to use it in any case. if ( g_object_class_find_property(G_OBJECT_GET_CLASS(editable), "editing-canceled") ) { gboolean wasCancelled; g_object_get(editable, "editing-canceled", &wasCancelled, NULL); if ( wasCancelled ) { wxrenderer->CancelEditing(); return; } } wxrenderer->FinishEditing(); } static void wxgtk_renderer_editing_started( GtkCellRenderer *WXUNUSED(cell), GtkCellEditable *editable, gchar *path, wxDataViewRenderer *wxrenderer ) { if (!editable) return; wxDataViewColumn *column = wxrenderer->GetOwner(); wxDataViewCtrl *dv = column->GetOwner(); wxDataViewItem item(dv->GTKPathToItem(wxGtkTreePath(path))); wxrenderer->NotifyEditingStarted(item); if (GTK_IS_CELL_EDITABLE(editable)) { g_signal_connect (editable, "editing_done", G_CALLBACK (wxgtk_cell_editable_editing_done), (gpointer) wxrenderer ); } } wxIMPLEMENT_ABSTRACT_CLASS(wxDataViewRenderer, wxDataViewRendererBase); wxDataViewRenderer::wxDataViewRenderer( const wxString &varianttype, wxDataViewCellMode mode, int align ) : wxDataViewRendererBase( varianttype, mode, align ) { m_renderer = NULL; m_mode = mode; // we haven't changed them yet m_usingDefaultAttrs = true; // NOTE: SetMode() and SetAlignment() needs to be called in the renderer's ctor, // after the m_renderer pointer has been initialized } bool wxDataViewRenderer::FinishEditing() { wxWindow* editorCtrl = m_editorCtrl; bool ret = wxDataViewRendererBase::FinishEditing(); if (editorCtrl && wxGetTopLevelParent(editorCtrl)->IsBeingDeleted()) { // remove editor widget before editor control is deleted, // to prevent several GTK warnings gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(GtkGetEditorWidget())); // delete editor control now, if it is deferred multiple erroneous // focus-out events will occur, causing debug warnings delete editorCtrl; } return ret; } void wxDataViewRenderer::GtkPackIntoColumn(GtkTreeViewColumn *column) { gtk_tree_view_column_pack_end( column, m_renderer, TRUE /* expand */); } void wxDataViewRenderer::GtkInitHandlers() { { g_signal_connect (m_renderer, "editing_started", G_CALLBACK (wxgtk_renderer_editing_started), this); } } GtkWidget* wxDataViewRenderer::GtkGetEditorWidget() const { return GetEditorCtrl()->m_widget; } void wxDataViewRenderer::SetMode( wxDataViewCellMode mode ) { m_mode = mode; GtkSetMode(mode); } void wxDataViewRenderer::SetEnabled(bool enabled) { // a) this sets the appearance to disabled grey and should only be done for // the active cells which are disabled, not for the cells which can never // be edited at all if ( GetMode() != wxDATAVIEW_CELL_INERT ) { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, enabled ); g_object_set_property( G_OBJECT(m_renderer), "sensitive", &gvalue ); g_value_unset( &gvalue ); } // b) this actually disables the control/renderer GtkSetMode(enabled ? GetMode() : wxDATAVIEW_CELL_INERT); } void wxDataViewRenderer::GtkSetMode( wxDataViewCellMode mode ) { GtkCellRendererMode gtkMode; switch (mode) { case wxDATAVIEW_CELL_INERT: gtkMode = GTK_CELL_RENDERER_MODE_INERT; break; case wxDATAVIEW_CELL_ACTIVATABLE: gtkMode = GTK_CELL_RENDERER_MODE_ACTIVATABLE; break; case wxDATAVIEW_CELL_EDITABLE: gtkMode = GTK_CELL_RENDERER_MODE_EDITABLE; break; default: wxFAIL_MSG( "unknown wxDataViewCellMode value" ); return; } GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, gtk_cell_renderer_mode_get_type() ); g_value_set_enum( &gvalue, gtkMode ); g_object_set_property( G_OBJECT(m_renderer), "mode", &gvalue ); g_value_unset( &gvalue ); } wxDataViewCellMode wxDataViewRenderer::GetMode() const { return m_mode; } void wxDataViewRenderer::GtkApplyAlignment(GtkCellRenderer *renderer) { int align = GetEffectiveAlignmentIfKnown(); if ( align == wxDVR_DEFAULT_ALIGNMENT ) return; // none set yet // horizontal alignment: gfloat xalign = 0.0; if (align & wxALIGN_RIGHT) xalign = 1.0; else if (align & wxALIGN_CENTER_HORIZONTAL) xalign = 0.5; GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_FLOAT ); g_value_set_float( &gvalue, xalign ); g_object_set_property( G_OBJECT(renderer), "xalign", &gvalue ); g_value_unset( &gvalue ); // vertical alignment: gfloat yalign = 0.0; if (align & wxALIGN_BOTTOM) yalign = 1.0; else if (align & wxALIGN_CENTER_VERTICAL) yalign = 0.5; GValue gvalue2 = G_VALUE_INIT; g_value_init( &gvalue2, G_TYPE_FLOAT ); g_value_set_float( &gvalue2, yalign ); g_object_set_property( G_OBJECT(renderer), "yalign", &gvalue2 ); g_value_unset( &gvalue2 ); } void wxDataViewRenderer::SetAlignment( int align ) { m_alignment = align; GtkUpdateAlignment(); } int wxDataViewRenderer::GetAlignment() const { return m_alignment; } void wxDataViewRenderer::EnableEllipsize(wxEllipsizeMode mode) { GtkCellRendererText * const rend = GtkGetTextRenderer(); if ( !rend ) return; // we use the same values in wxEllipsizeMode as PangoEllipsizeMode so we // can just cast between them GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, PANGO_TYPE_ELLIPSIZE_MODE ); g_value_set_enum( &gvalue, static_cast(mode) ); g_object_set_property( G_OBJECT(rend), "ellipsize", &gvalue ); g_value_unset( &gvalue ); } wxEllipsizeMode wxDataViewRenderer::GetEllipsizeMode() const { GtkCellRendererText * const rend = GtkGetTextRenderer(); if ( !rend ) return wxELLIPSIZE_NONE; GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, PANGO_TYPE_ELLIPSIZE_MODE ); g_object_get_property( G_OBJECT(rend), "ellipsize", &gvalue ); wxEllipsizeMode mode = static_cast(g_value_get_enum( &gvalue )); g_value_unset( &gvalue ); return mode; } bool wxDataViewRenderer::IsHighlighted() const { return m_itemBeingRendered.IsOk() && GetOwner()->GetOwner()->IsSelected(m_itemBeingRendered); } wxVariant wxDataViewRenderer::GtkGetValueFromString(const wxString& str) const { return str; } void wxDataViewRenderer::GtkOnTextEdited(const char *itempath, const wxString& str) { m_item = wxDataViewItem(GetView()->GTKPathToItem(wxGtkTreePath(itempath))); wxVariant value(GtkGetValueFromString(str)); DoHandleEditingDone(&value); } void wxDataViewRenderer::SetAttr(const wxDataViewItemAttr& WXUNUSED(attr)) { // There is no way to apply attributes to an arbitrary renderer, so we // simply can't do anything here. } // --------------------------------------------------------- // wxDataViewTextRenderer // --------------------------------------------------------- extern "C" { static void wxGtkTextRendererEditedCallback( GtkCellRendererText *WXUNUSED(renderer), gchar *arg1, gchar *arg2, gpointer user_data ) { wxDataViewRenderer *cell = (wxDataViewRenderer*) user_data; cell->GtkOnTextEdited(arg1, wxGTK_CONV_BACK_FONT( arg2, cell->GetOwner()->GetOwner()->GetFont())); } } namespace { // helper function used by wxDataViewTextRenderer and // wxDataViewCustomRenderer::RenderText(): it applies the attributes to the // given text renderer void GtkApplyAttr(GtkCellRendererText *renderer, const wxDataViewItemAttr& attr) { if (attr.HasColour()) { GValue gvalue = G_VALUE_INIT; #ifdef __WXGTK3__ g_value_init(&gvalue, GDK_TYPE_RGBA); g_value_set_boxed(&gvalue, static_cast(attr.GetColour())); g_object_set_property(G_OBJECT(renderer), "foreground-rgba", &gvalue); #else const GdkColor* const gcol = attr.GetColour().GetColor(); g_value_init( &gvalue, GDK_TYPE_COLOR ); g_value_set_boxed( &gvalue, gcol ); g_object_set_property( G_OBJECT(renderer), "foreground_gdk", &gvalue ); #endif g_value_unset( &gvalue ); } else { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, FALSE ); g_object_set_property( G_OBJECT(renderer), "foreground-set", &gvalue ); g_value_unset( &gvalue ); } if (attr.GetItalic()) { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, PANGO_TYPE_STYLE ); g_value_set_enum( &gvalue, PANGO_STYLE_ITALIC ); g_object_set_property( G_OBJECT(renderer), "style", &gvalue ); g_value_unset( &gvalue ); } else { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, FALSE ); g_object_set_property( G_OBJECT(renderer), "style-set", &gvalue ); g_value_unset( &gvalue ); } if (attr.GetBold()) { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, PANGO_TYPE_WEIGHT ); g_value_set_enum( &gvalue, PANGO_WEIGHT_BOLD ); g_object_set_property( G_OBJECT(renderer), "weight", &gvalue ); g_value_unset( &gvalue ); } else { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, FALSE ); g_object_set_property( G_OBJECT(renderer), "weight-set", &gvalue ); g_value_unset( &gvalue ); } if (attr.HasBackgroundColour()) { GValue gvalue = G_VALUE_INIT; wxColour colour = attr.GetBackgroundColour(); #ifdef __WXGTK3__ g_value_init(&gvalue, GDK_TYPE_RGBA); g_value_set_boxed(&gvalue, static_cast(colour)); g_object_set_property(G_OBJECT(renderer), "cell-background-rgba", &gvalue); #else const GdkColor * const gcol = colour.GetColor(); g_value_init( &gvalue, GDK_TYPE_COLOR ); g_value_set_boxed( &gvalue, gcol ); g_object_set_property( G_OBJECT(renderer), "cell-background_gdk", &gvalue ); #endif g_value_unset( &gvalue ); } else { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, FALSE ); g_object_set_property( G_OBJECT(renderer), "cell-background-set", &gvalue ); g_value_unset( &gvalue ); } } } // anonymous namespace wxIMPLEMENT_CLASS(wxDataViewTextRenderer, wxDataViewRenderer); wxDataViewTextRenderer::wxDataViewTextRenderer( const wxString &varianttype, wxDataViewCellMode mode, int align ) : wxDataViewRenderer( varianttype, mode, align ) { #if wxUSE_MARKUP m_useMarkup = false; #endif // wxUSE_MARKUP GtkWxCellRendererText *text_renderer = gtk_wx_cell_renderer_text_new(); text_renderer->wx_renderer = this; m_renderer = (GtkCellRenderer*) text_renderer; if (mode & wxDATAVIEW_CELL_EDITABLE) { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, true ); g_object_set_property( G_OBJECT(m_renderer), "editable", &gvalue ); g_value_unset( &gvalue ); g_signal_connect_after( m_renderer, "edited", G_CALLBACK(wxGtkTextRendererEditedCallback), this ); GtkInitHandlers(); } SetMode(mode); SetAlignment(align); } #if wxUSE_MARKUP void wxDataViewTextRenderer::EnableMarkup(bool enable) { m_useMarkup = enable; } #endif // wxUSE_MARKUP const char* wxDataViewTextRenderer::GetTextPropertyName() const { #if wxUSE_MARKUP if ( m_useMarkup ) return "markup"; #endif // wxUSE_MARKUP return "text"; } bool wxDataViewTextRenderer::SetTextValue(const wxString& str) { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_STRING ); g_value_set_string( &gvalue, wxGTK_CONV_FONT( str, GetOwner()->GetOwner()->GetFont() ) ); g_object_set_property( G_OBJECT(m_renderer), GetTextPropertyName(), &gvalue ); g_value_unset( &gvalue ); return true; } bool wxDataViewTextRenderer::GetTextValue(wxString& str) const { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_STRING ); g_object_get_property( G_OBJECT(m_renderer), GetTextPropertyName(), &gvalue ); str = wxGTK_CONV_BACK_FONT( g_value_get_string( &gvalue ), const_cast(this)->GetOwner()->GetOwner()->GetFont() ); g_value_unset( &gvalue ); return true; } void wxDataViewTextRenderer::GtkUpdateAlignment() { wxDataViewRenderer::GtkUpdateAlignment(); if (!wx_is_at_least_gtk2(10)) return; int align = GetEffectiveAlignmentIfKnown(); if ( align == wxDVR_DEFAULT_ALIGNMENT ) return; // none set yet // horizontal alignment: PangoAlignment pangoAlign = PANGO_ALIGN_LEFT; if (align & wxALIGN_RIGHT) pangoAlign = PANGO_ALIGN_RIGHT; else if (align & wxALIGN_CENTER_HORIZONTAL) pangoAlign = PANGO_ALIGN_CENTER; GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, pango_alignment_get_type() ); g_value_set_enum( &gvalue, pangoAlign ); g_object_set_property( G_OBJECT(m_renderer), "alignment", &gvalue ); g_value_unset( &gvalue ); } void wxDataViewTextRenderer::SetAttr(const wxDataViewItemAttr& attr) { // An optimization: don't bother resetting the attributes if we're already // using the defaults. if ( attr.IsDefault() && m_usingDefaultAttrs ) return; GtkApplyAttr(GtkGetTextRenderer(), attr); m_usingDefaultAttrs = attr.IsDefault(); } GtkCellRendererText *wxDataViewTextRenderer::GtkGetTextRenderer() const { return GTK_CELL_RENDERER_TEXT(m_renderer); } // --------------------------------------------------------- // wxDataViewBitmapRenderer // --------------------------------------------------------- namespace { // set "pixbuf" property on the given renderer void SetPixbufProp(GtkCellRenderer *renderer, GdkPixbuf *pixbuf) { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_OBJECT ); g_value_set_object( &gvalue, pixbuf ); g_object_set_property( G_OBJECT(renderer), "pixbuf", &gvalue ); g_value_unset( &gvalue ); } } // anonymous namespace wxIMPLEMENT_CLASS(wxDataViewBitmapRenderer, wxDataViewRenderer); wxDataViewBitmapRenderer::wxDataViewBitmapRenderer( const wxString &varianttype, wxDataViewCellMode mode, int align ) : wxDataViewRenderer( varianttype, mode, align ) { m_renderer = gtk_cell_renderer_pixbuf_new(); SetMode(mode); SetAlignment(align); } bool wxDataViewBitmapRenderer::SetValue( const wxVariant &value ) { if (value.GetType() == wxT("wxBitmap")) { wxBitmap bitmap; bitmap << value; // GetPixbuf() may create a Pixbuf representation in the wxBitmap // object (and it will stay there and remain owned by wxBitmap) SetPixbufProp(m_renderer, bitmap.IsOk() ? bitmap.GetPixbuf() : NULL); } else if (value.GetType() == wxT("wxIcon")) { wxIcon icon; icon << value; SetPixbufProp(m_renderer, icon.IsOk() ? icon.GetPixbuf() : NULL); } else { SetPixbufProp(m_renderer, NULL); } return true; } bool wxDataViewBitmapRenderer::GetValue( wxVariant &WXUNUSED(value) ) const { return false; } // --------------------------------------------------------- // wxDataViewToggleRenderer // --------------------------------------------------------- extern "C" { static void wxGtkToggleRendererToggledCallback( GtkCellRendererToggle *renderer, gchar *path, gpointer user_data ); } static void wxGtkToggleRendererToggledCallback( GtkCellRendererToggle *renderer, gchar *path, gpointer user_data ) { wxDataViewToggleRenderer *cell = (wxDataViewToggleRenderer*) user_data; // get old value GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_object_get_property( G_OBJECT(renderer), "active", &gvalue ); // invert it wxVariant value = !g_value_get_boolean( &gvalue ); g_value_unset( &gvalue ); if (!cell->Validate( value )) return; wxDataViewCtrl * const ctrl = cell->GetOwner()->GetOwner(); wxDataViewModel *model = ctrl->GetModel(); wxDataViewItem item(ctrl->GTKPathToItem(wxGtkTreePath(path))); unsigned int model_col = cell->GetOwner()->GetModelColumn(); model->ChangeValue( value, item, model_col ); } wxIMPLEMENT_CLASS(wxDataViewToggleRenderer, wxDataViewRenderer); wxDataViewToggleRenderer::wxDataViewToggleRenderer( const wxString &varianttype, wxDataViewCellMode mode, int align ) : wxDataViewRenderer( varianttype, mode, align ) { m_renderer = (GtkCellRenderer*) gtk_cell_renderer_toggle_new(); if (mode & wxDATAVIEW_CELL_ACTIVATABLE) { g_signal_connect_after( m_renderer, "toggled", G_CALLBACK(wxGtkToggleRendererToggledCallback), this ); } else { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, false ); g_object_set_property( G_OBJECT(m_renderer), "activatable", &gvalue ); g_value_unset( &gvalue ); } SetMode(mode); SetAlignment(align); } void wxDataViewToggleRenderer::ShowAsRadio() { gtk_cell_renderer_toggle_set_radio(GTK_CELL_RENDERER_TOGGLE(m_renderer), TRUE); } bool wxDataViewToggleRenderer::SetValue( const wxVariant &value ) { bool tmp = value; GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, tmp ); g_object_set_property( G_OBJECT(m_renderer), "active", &gvalue ); g_value_unset( &gvalue ); return true; } bool wxDataViewToggleRenderer::GetValue( wxVariant &value ) const { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_object_get_property( G_OBJECT(m_renderer), "active", &gvalue ); value = g_value_get_boolean( &gvalue ) != 0; g_value_unset( &gvalue ); return true; } // --------------------------------------------------------- // wxDataViewCustomRenderer // --------------------------------------------------------- #ifndef __WXGTK3__ class wxDataViewCtrlDCImpl: public wxWindowDCImpl { public: wxDataViewCtrlDCImpl( wxDC *owner, wxDataViewCtrl *window ) : wxWindowDCImpl( owner ) { GtkWidget *widget = window->m_treeview; // Set later m_gdkwindow = NULL; m_window = window; m_context = window->GTKGetPangoDefaultContext(); m_layout = pango_layout_new( m_context ); m_fontdesc = pango_font_description_copy(gtk_widget_get_style(widget)->font_desc); m_cmap = gtk_widget_get_colormap( widget ? widget : window->m_widget ); // Set m_gdkwindow later // SetUpDC(); } }; class wxDataViewCtrlDC: public wxWindowDC { public: wxDataViewCtrlDC( wxDataViewCtrl *window ) : wxWindowDC( new wxDataViewCtrlDCImpl( this, window ) ) { } }; #endif // --------------------------------------------------------- // wxDataViewCustomRenderer // --------------------------------------------------------- wxIMPLEMENT_CLASS(wxDataViewCustomRenderer, wxDataViewRenderer); wxDataViewCustomRenderer::wxDataViewCustomRenderer( const wxString &varianttype, wxDataViewCellMode mode, int align, bool no_init ) : wxDataViewCustomRendererBase( varianttype, mode, align ) { m_dc = NULL; m_text_renderer = NULL; m_renderParams = NULL; if (no_init) m_renderer = NULL; else Init(mode, align); } void wxDataViewCustomRenderer::GtkUpdateAlignment() { wxDataViewCustomRendererBase::GtkUpdateAlignment(); if ( m_text_renderer ) GtkApplyAlignment(GTK_CELL_RENDERER(m_text_renderer)); } void wxDataViewCustomRenderer::GtkInitTextRenderer() { m_text_renderer = GTK_CELL_RENDERER_TEXT(gtk_cell_renderer_text_new()); g_object_ref_sink(m_text_renderer); GtkApplyAlignment(GTK_CELL_RENDERER(m_text_renderer)); gtk_cell_renderer_set_padding(GTK_CELL_RENDERER(m_text_renderer), 0, 0); } GtkCellRendererText *wxDataViewCustomRenderer::GtkGetTextRenderer() const { if ( !m_text_renderer ) { // we create it on demand so need to do it even from a const function const_cast(this)->GtkInitTextRenderer(); } return m_text_renderer; } GtkWidget* wxDataViewCustomRenderer::GtkGetEditorWidget() const { return GTK_WX_CELL_RENDERER(m_renderer)->editor_bin; } void wxDataViewCustomRenderer::RenderText( const wxString &text, int xoffset, wxRect cell, wxDC *WXUNUSED(dc), int WXUNUSED(state) ) { GtkCellRendererText * const textRenderer = GtkGetTextRenderer(); GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_STRING ); g_value_set_string( &gvalue, wxGTK_CONV_FONT( text, GetOwner()->GetOwner()->GetFont() ) ); g_object_set_property( G_OBJECT(textRenderer), "text", &gvalue ); g_value_unset( &gvalue ); GtkApplyAttr(textRenderer, GetAttr()); GdkRectangle cell_area; wxRectToGDKRect(cell, cell_area); cell_area.x += xoffset; cell_area.width -= xoffset; gtk_cell_renderer_render( GTK_CELL_RENDERER(textRenderer), #ifdef __WXGTK3__ m_renderParams->cr, #else m_renderParams->window, #endif m_renderParams->widget, m_renderParams->background_area, &cell_area, #ifndef __WXGTK3__ m_renderParams->expose_area, #endif GtkCellRendererState(m_renderParams->flags)); } bool wxDataViewCustomRenderer::Init(wxDataViewCellMode mode, int align) { GtkWxCellRenderer *renderer = (GtkWxCellRenderer *) gtk_wx_cell_renderer_new(); renderer->cell = this; m_renderer = (GtkCellRenderer*) renderer; SetMode(mode); SetAlignment(align); GtkInitHandlers(); return true; } wxDataViewCustomRenderer::~wxDataViewCustomRenderer() { if (m_dc) delete m_dc; if (m_text_renderer) g_object_unref(m_text_renderer); } wxDC *wxDataViewCustomRenderer::GetDC() { if (m_dc == NULL) { wxDataViewCtrl* ctrl = NULL; wxDataViewColumn* column = GetOwner(); if (column) ctrl = column->GetOwner(); #ifdef __WXGTK3__ wxASSERT(m_renderParams); cairo_t* cr = m_renderParams->cr; wxASSERT(cr && cairo_status(cr) == 0); m_dc = new wxGTKCairoDC(cr, ctrl); #else if (ctrl == NULL) return NULL; m_dc = new wxDataViewCtrlDC(ctrl); #endif } return m_dc; } // --------------------------------------------------------- // wxDataViewProgressRenderer // --------------------------------------------------------- wxIMPLEMENT_CLASS(wxDataViewProgressRenderer, wxDataViewCustomRenderer); wxDataViewProgressRenderer::wxDataViewProgressRenderer( const wxString &label, const wxString &varianttype, wxDataViewCellMode mode, int align ) : wxDataViewCustomRenderer( varianttype, mode, align, true ) { m_label = label; m_value = 0; m_renderer = (GtkCellRenderer*) gtk_cell_renderer_progress_new(); SetMode(mode); SetAlignment(align); #if !wxUSE_UNICODE // We can't initialize the renderer just yet because we don't have the // pointer to the column that uses this renderer yet and so attempt to // dereference GetOwner() to get the font that is used as a source of // encoding in multibyte-to-Unicode conversion in GTKSetLabel() in // non-Unicode builds would crash. So simply remember to do it later. if ( !m_label.empty() ) m_needsToSetLabel = true; else #endif // !wxUSE_UNICODE { GTKSetLabel(); } } wxDataViewProgressRenderer::~wxDataViewProgressRenderer() { } void wxDataViewProgressRenderer::GTKSetLabel() { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_STRING ); // Take care to not use GetOwner() here if the label is empty, we can be // called from ctor when GetOwner() is still NULL in this case. wxScopedCharBuffer buf; if ( m_label.empty() ) buf = wxScopedCharBuffer::CreateNonOwned(""); else buf = wxGTK_CONV_FONT(m_label, GetOwner()->GetOwner()->GetFont()); g_value_set_string( &gvalue, buf); g_object_set_property( G_OBJECT(m_renderer), "text", &gvalue ); g_value_unset( &gvalue ); #if !wxUSE_UNICODE m_needsToSetLabel = false; #endif // !wxUSE_UNICODE } bool wxDataViewProgressRenderer::SetValue( const wxVariant &value ) { #if !wxUSE_UNICODE if ( m_needsToSetLabel ) GTKSetLabel(); #endif // !wxUSE_UNICODE gint tmp = (long) value; GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_INT ); g_value_set_int( &gvalue, tmp ); g_object_set_property( G_OBJECT(m_renderer), "value", &gvalue ); g_value_unset( &gvalue ); return true; } bool wxDataViewProgressRenderer::GetValue( wxVariant &WXUNUSED(value) ) const { return false; } bool wxDataViewProgressRenderer::Render( wxRect cell, wxDC *dc, int WXUNUSED(state) ) { double pct = (double)m_value / 100.0; wxRect bar = cell; bar.width = (int)(cell.width * pct); dc->SetPen( *wxTRANSPARENT_PEN ); dc->SetBrush( *wxBLUE_BRUSH ); dc->DrawRectangle( bar ); dc->SetBrush( *wxTRANSPARENT_BRUSH ); dc->SetPen( *wxBLACK_PEN ); dc->DrawRectangle( cell ); return true; } wxSize wxDataViewProgressRenderer::GetSize() const { return wxSize(40,12); } // ------------------------------------- // wxDataViewChoiceRenderer // ------------------------------------- wxDataViewChoiceRenderer::wxDataViewChoiceRenderer( const wxArrayString &choices, wxDataViewCellMode mode, int alignment ) : wxDataViewCustomRenderer( "string", mode, alignment, true ) { m_choices = choices; m_renderer = (GtkCellRenderer*) gtk_cell_renderer_combo_new(); GtkListStore *store = gtk_list_store_new( 1, G_TYPE_STRING ); for (size_t n = 0; n < m_choices.GetCount(); n++) { gtk_list_store_insert_with_values( store, NULL, n, 0, static_cast(m_choices[n].utf8_str()), -1 ); } g_object_set (m_renderer, "model", store, "text-column", 0, "has-entry", FALSE, NULL); bool editable = (mode & wxDATAVIEW_CELL_EDITABLE) != 0; g_object_set (m_renderer, "editable", editable, NULL); SetAlignment(alignment); g_signal_connect_after( m_renderer, "edited", G_CALLBACK(wxGtkTextRendererEditedCallback), this ); GtkInitHandlers(); } bool wxDataViewChoiceRenderer::Render( wxRect rect, wxDC *dc, int state ) { RenderText( m_data, 0, rect, dc, state ); return true; } wxSize wxDataViewChoiceRenderer::GetSize() const { return wxSize(70,20); } bool wxDataViewChoiceRenderer::SetValue( const wxVariant &value ) { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_STRING ); g_value_set_string(&gvalue, wxGTK_CONV_FONT(value.GetString(), GetOwner()->GetOwner()->GetFont())); g_object_set_property( G_OBJECT(m_renderer), "text", &gvalue ); g_value_unset( &gvalue ); return true; } bool wxDataViewChoiceRenderer::GetValue( wxVariant &value ) const { GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_STRING ); g_object_get_property( G_OBJECT(m_renderer), "text", &gvalue ); wxString temp = wxGTK_CONV_BACK_FONT(g_value_get_string(&gvalue), GetOwner()->GetOwner()->GetFont()); g_value_unset( &gvalue ); value = temp; return true; } void wxDataViewChoiceRenderer::GtkUpdateAlignment() { wxDataViewCustomRenderer::GtkUpdateAlignment(); if (!wx_is_at_least_gtk2(10)) return; int align = GetEffectiveAlignmentIfKnown(); if ( align == wxDVR_DEFAULT_ALIGNMENT ) return; // none set yet // horizontal alignment: PangoAlignment pangoAlign = PANGO_ALIGN_LEFT; if (align & wxALIGN_RIGHT) pangoAlign = PANGO_ALIGN_RIGHT; else if (align & wxALIGN_CENTER_HORIZONTAL) pangoAlign = PANGO_ALIGN_CENTER; GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, pango_alignment_get_type() ); g_value_set_enum( &gvalue, pangoAlign ); g_object_set_property( G_OBJECT(m_renderer), "alignment", &gvalue ); g_value_unset( &gvalue ); } // ---------------------------------------------------------------------------- // wxDataViewChoiceByIndexRenderer // ---------------------------------------------------------------------------- wxDataViewChoiceByIndexRenderer::wxDataViewChoiceByIndexRenderer( const wxArrayString &choices, wxDataViewCellMode mode, int alignment ) : wxDataViewChoiceRenderer( choices, mode, alignment ) { m_variantType = wxS("long"); } wxVariant wxDataViewChoiceByIndexRenderer::GtkGetValueFromString(const wxString& str) const { return static_cast(GetChoices().Index(str)); } bool wxDataViewChoiceByIndexRenderer::SetValue( const wxVariant &value ) { wxVariant string_value = GetChoice( value.GetLong() ); return wxDataViewChoiceRenderer::SetValue( string_value ); } bool wxDataViewChoiceByIndexRenderer::GetValue( wxVariant &value ) const { wxVariant string_value; if (!wxDataViewChoiceRenderer::GetValue( string_value )) return false; value = (long) GetChoices().Index( string_value.GetString() ); return true; } // --------------------------------------------------------- // wxDataViewIconTextRenderer // --------------------------------------------------------- wxIMPLEMENT_CLASS(wxDataViewIconTextRenderer, wxDataViewCustomRenderer); wxDataViewIconTextRenderer::wxDataViewIconTextRenderer ( const wxString &varianttype, wxDataViewCellMode mode, int align ) : wxDataViewTextRenderer(varianttype, mode, align) { m_rendererIcon = gtk_cell_renderer_pixbuf_new(); } wxDataViewIconTextRenderer::~wxDataViewIconTextRenderer() { } void wxDataViewIconTextRenderer::GtkPackIntoColumn(GtkTreeViewColumn *column) { // add the icon renderer first gtk_tree_view_column_pack_start(column, m_rendererIcon, FALSE /* !expand */); // add the text renderer too wxDataViewRenderer::GtkPackIntoColumn(column); } bool wxDataViewIconTextRenderer::SetValue( const wxVariant &value ) { m_value << value; SetTextValue(m_value.GetText()); const wxIcon& icon = m_value.GetIcon(); SetPixbufProp(m_rendererIcon, icon.IsOk() ? icon.GetPixbuf() : NULL); return true; } bool wxDataViewIconTextRenderer::GetValue(wxVariant& value) const { wxString str; if ( !GetTextValue(str) ) return false; // user doesn't have any way to edit the icon so leave it unchanged value << wxDataViewIconText(str, m_value.GetIcon()); return true; } wxVariant wxDataViewIconTextRenderer::GtkGetValueFromString(const wxString& str) const { // we receive just the text part of our value as it's the only one which // can be edited, but we need the full wxDataViewIconText value for the // model wxVariant valueIconText; valueIconText << wxDataViewIconText(str, m_value.GetIcon()); return valueIconText; } // --------------------------------------------------------- // wxDataViewColumn // --------------------------------------------------------- static gboolean gtk_dataview_header_button_press_callback( GtkWidget *WXUNUSED(widget), GdkEventButton *gdk_event, wxDataViewColumn *column ) { if (gdk_event->type != GDK_BUTTON_PRESS) return FALSE; if (gdk_event->button == 1) { gs_lastLeftClickHeader = column; wxDataViewCtrl *dv = column->GetOwner(); wxDataViewEvent event(wxEVT_DATAVIEW_COLUMN_HEADER_CLICK, dv, column); if (dv->HandleWindowEvent( event )) return FALSE; } if (gdk_event->button == 3) { wxDataViewCtrl *dv = column->GetOwner(); wxDataViewEvent event(wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK, dv, column); if (dv->HandleWindowEvent( event )) return FALSE; } return FALSE; } extern "C" { static void wxGtkTreeCellDataFunc( GtkTreeViewColumn *WXUNUSED(column), GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data ) { g_return_if_fail (GTK_IS_WX_TREE_MODEL (model)); GtkWxTreeModel *tree_model = (GtkWxTreeModel *) model; if ( !tree_model->stamp ) { // The model is temporarily invalid and can't be used, see the code in // wxGtkDataViewModelNotifier::Cleared(). return; } wxDataViewRenderer *cell = (wxDataViewRenderer*) data; wxDataViewItem item( (void*) iter->user_data ); const unsigned column = cell->GetOwner()->GetModelColumn(); wxDataViewModel *wx_model = tree_model->internal->GetDataViewModel(); if (!wx_model->IsVirtualListModel()) { gboolean visible; if (wx_model->IsContainer( item )) { visible = wx_model->HasContainerColumns( item ) || (column == 0); } else { visible = true; } GValue gvalue = G_VALUE_INIT; g_value_init( &gvalue, G_TYPE_BOOLEAN ); g_value_set_boolean( &gvalue, visible ); g_object_set_property( G_OBJECT(renderer), "visible", &gvalue ); g_value_unset( &gvalue ); if ( !visible ) return; } cell->GtkSetCurrentItem(item); cell->PrepareForItem(wx_model, item, column); } } // extern "C" #include WX_DEFINE_LIST(wxDataViewColumnList) wxDataViewColumn::wxDataViewColumn( const wxString &title, wxDataViewRenderer *cell, unsigned int model_column, int width, wxAlignment align, int flags ) : wxDataViewColumnBase( cell, model_column ) { Init( align, flags, width ); SetTitle( title ); } wxDataViewColumn::wxDataViewColumn( const wxBitmap &bitmap, wxDataViewRenderer *cell, unsigned int model_column, int width, wxAlignment align, int flags ) : wxDataViewColumnBase( bitmap, cell, model_column ) { Init( align, flags, width ); SetBitmap( bitmap ); } void wxDataViewColumn::Init(wxAlignment align, int flags, int width) { m_isConnected = false; GtkTreeViewColumn *column = gtk_tree_view_column_new(); m_column = (GtkWidget*) column; SetFlags( flags ); SetAlignment( align ); SetWidth( width ); // Create container for icon and label GtkWidget* box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1); gtk_widget_show( box ); // gtk_container_set_border_width((GtkContainer*)box, 2); m_image = gtk_image_new(); gtk_box_pack_start(GTK_BOX(box), m_image, FALSE, FALSE, 1); m_label = gtk_label_new(""); gtk_box_pack_end( GTK_BOX(box), GTK_WIDGET(m_label), FALSE, FALSE, 1 ); gtk_tree_view_column_set_widget( column, box ); wxDataViewRenderer * const colRenderer = GetRenderer(); GtkCellRenderer * const cellRenderer = colRenderer->GetGtkHandle(); colRenderer->GtkPackIntoColumn(column); gtk_tree_view_column_set_cell_data_func( column, cellRenderer, wxGtkTreeCellDataFunc, (gpointer) colRenderer, NULL ); } void wxDataViewColumn::OnInternalIdle() { if (m_isConnected) return; if (gtk_widget_get_realized(GetOwner()->m_treeview)) { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); GtkWidget* button = gtk_tree_view_column_get_button(column); if (button) { g_signal_connect(button, "button_press_event", G_CALLBACK (gtk_dataview_header_button_press_callback), this); // otherwise the event will be blocked by GTK+ gtk_tree_view_column_set_clickable( column, TRUE ); m_isConnected = true; } } } void wxDataViewColumn::SetOwner( wxDataViewCtrl *owner ) { wxDataViewColumnBase::SetOwner( owner ); GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); gtk_tree_view_column_set_title( column, wxGTK_CONV_FONT(GetTitle(), GetOwner()->GetFont() ) ); } void wxDataViewColumn::SetTitle( const wxString &title ) { wxDataViewCtrl *ctrl = GetOwner(); gtk_label_set_text( GTK_LABEL(m_label), ctrl ? wxGTK_CONV_FONT(title, ctrl->GetFont()) : wxGTK_CONV_SYS(title) ); if (title.empty()) gtk_widget_hide( m_label ); else gtk_widget_show( m_label ); } wxString wxDataViewColumn::GetTitle() const { return wxGTK_CONV_BACK_FONT( gtk_label_get_text( GTK_LABEL(m_label) ), GetOwner()->GetFont() ); } void wxDataViewColumn::SetBitmap( const wxBitmap &bitmap ) { wxDataViewColumnBase::SetBitmap( bitmap ); if (bitmap.IsOk()) { GtkImage *gtk_image = GTK_IMAGE(m_image); gtk_image_set_from_pixbuf(GTK_IMAGE(gtk_image), bitmap.GetPixbuf()); gtk_widget_show( m_image ); } else { gtk_widget_hide( m_image ); } } void wxDataViewColumn::SetHidden( bool hidden ) { gtk_tree_view_column_set_visible( GTK_TREE_VIEW_COLUMN(m_column), !hidden ); } void wxDataViewColumn::SetResizeable( bool resizable ) { gtk_tree_view_column_set_resizable( GTK_TREE_VIEW_COLUMN(m_column), resizable ); } void wxDataViewColumn::SetAlignment( wxAlignment align ) { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); gfloat xalign = 0.0; if (align == wxALIGN_RIGHT) xalign = 1.0; if (align == wxALIGN_CENTER_HORIZONTAL || align == wxALIGN_CENTER) xalign = 0.5; gtk_tree_view_column_set_alignment( column, xalign ); if (m_renderer && m_renderer->GetAlignment() == -1) m_renderer->GtkUpdateAlignment(); } wxAlignment wxDataViewColumn::GetAlignment() const { gfloat xalign = gtk_tree_view_column_get_alignment( GTK_TREE_VIEW_COLUMN(m_column) ); if (xalign == 1.0) return wxALIGN_RIGHT; if (xalign == 0.5) return wxALIGN_CENTER_HORIZONTAL; return wxALIGN_LEFT; } void wxDataViewColumn::SetSortable( bool sortable ) { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); if ( sortable ) { gtk_tree_view_column_set_sort_column_id( column, GetModelColumn() ); } else { gtk_tree_view_column_set_sort_column_id( column, -1 ); gtk_tree_view_column_set_sort_indicator( column, FALSE ); gtk_tree_view_column_set_clickable( column, FALSE ); } } bool wxDataViewColumn::IsSortable() const { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); return gtk_tree_view_column_get_clickable( column ) != 0; } bool wxDataViewColumn::IsSortKey() const { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); return gtk_tree_view_column_get_sort_indicator( column ) != 0; } bool wxDataViewColumn::IsResizeable() const { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); return gtk_tree_view_column_get_resizable( column ) != 0; } bool wxDataViewColumn::IsHidden() const { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); return !gtk_tree_view_column_get_visible( column ); } void wxDataViewColumn::SetSortOrder( bool ascending ) { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); GtkSortType order = GTK_SORT_DESCENDING; if (ascending) order = GTK_SORT_ASCENDING; gtk_tree_view_column_set_sort_order(column, order); gtk_tree_view_column_set_sort_indicator( column, TRUE ); wxDataViewCtrlInternal* internal = m_owner->GtkGetInternal(); internal->SetSortOrder(order); internal->SetSortColumn(m_model_column); internal->SetDataViewSortColumn(this); } bool wxDataViewColumn::IsSortOrderAscending() const { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column); return (gtk_tree_view_column_get_sort_order( column ) != GTK_SORT_DESCENDING); } void wxDataViewColumn::SetMinWidth( int width ) { gtk_tree_view_column_set_min_width( GTK_TREE_VIEW_COLUMN(m_column), width ); } int wxDataViewColumn::GetMinWidth() const { return gtk_tree_view_column_get_min_width( GTK_TREE_VIEW_COLUMN(m_column) ); } int wxDataViewColumn::GetWidth() const { return gtk_tree_view_column_get_width( GTK_TREE_VIEW_COLUMN(m_column) ); } void wxDataViewColumn::SetWidth( int width ) { // Notice that we don't have anything to do for wxCOL_WIDTH_DEFAULT and // wxCOL_WIDTH_AUTOSIZE as the native control tries to use the appropriate // width by default anyhow, don't use GTK_TREE_VIEW_COLUMN_AUTOSIZE to // force it because, as mentioned in GTK+ documentation, it's completely // inappropriate for controls with a lot of items (because it's O(N)) and // it would also prevent the user from resizing the column manually which // we want to allow for resizeable columns. if ( width >= 0 ) { gtk_tree_view_column_set_sizing( GTK_TREE_VIEW_COLUMN(m_column), GTK_TREE_VIEW_COLUMN_FIXED ); gtk_tree_view_column_set_fixed_width( GTK_TREE_VIEW_COLUMN(m_column), width ); } } void wxDataViewColumn::SetReorderable( bool reorderable ) { gtk_tree_view_column_set_reorderable( GTK_TREE_VIEW_COLUMN(m_column), reorderable ); } bool wxDataViewColumn::IsReorderable() const { return gtk_tree_view_column_get_reorderable( GTK_TREE_VIEW_COLUMN(m_column) ) != 0; } //----------------------------------------------------------------------------- // wxGtkTreeModelNode //----------------------------------------------------------------------------- #if 0 class wxGtkTreeModelChildWithPos { public: unsigned int pos; void *id; }; static int wxGtkTreeModelChildWithPosCmp( const void* data1, const void* data2, const void* user_data ) { const wxGtkTreeModelChildWithPos* child1 = (const wxGtkTreeModelChildWithPos*) data1; const wxGtkTreeModelChildWithPos* child2 = (const wxGtkTreeModelChildWithPos*) data2; const wxDataViewCtrlInternal *internal = (const wxDataViewCtrlInternal *) user_data; int ret = internal->GetDataViewModel()->Compare( child1->id, child2->id, internal->GetSortColumn(), (internal->GetSortOrder() == GTK_SORT_DESCENDING) ); return ret; } #else static int LINKAGEMODE wxGtkTreeModelChildPtrCmp( void*** data1, void*** data2 ) { return gs_internal->GetDataViewModel()->Compare( wxDataViewItem(**data1), wxDataViewItem(**data2), gs_internal->GetSortColumn(), (gs_internal->GetSortOrder() == GTK_SORT_ASCENDING) ); } WX_DEFINE_ARRAY_PTR( void**, wxGtkTreeModelChildrenPtr ); #endif void wxGtkTreeModelNode::Resort() { size_t child_count = GetChildCount(); if (child_count == 0) return; size_t node_count = GetNodesCount(); if (child_count == 1) { if (node_count == 1) { wxGtkTreeModelNode *node = m_nodes.Item( 0 ); node->Resort(); } return; } gint *new_order = new gint[child_count]; #if 1 // m_children has the original *void // ptrs points to these wxGtkTreeModelChildrenPtr ptrs; size_t i; for (i = 0; i < child_count; i++) ptrs.Add( &(m_children[i]) ); // Sort the ptrs gs_internal = m_internal; ptrs.Sort( &wxGtkTreeModelChildPtrCmp ); wxGtkTreeModelChildren temp; void** base_ptr = &(m_children[0]); // Transfer positions to new_order array and // IDs to temp for (i = 0; i < child_count; i++) { new_order[i] = ptrs[i] - base_ptr; temp.Add( *ptrs[i] ); } // Transfer IDs back to m_children m_children.Clear(); WX_APPEND_ARRAY( temp, m_children ); #endif #if 0 // Too slow // Build up array with IDs and original positions wxGtkTreeModelChildWithPos* temp = new wxGtkTreeModelChildWithPos[child_count]; size_t i; for (i = 0; i < child_count; i++) { temp[i].pos = i; temp[i].id = m_children[i]; } // Sort array keeping original positions wxQsort( temp, child_count, sizeof(wxGtkTreeModelChildWithPos), &wxGtkTreeModelChildWithPosCmp, m_internal ); // Transfer positions to new_order array and // IDs to m_children m_children.Clear(); for (i = 0; i < child_count; i++) { new_order[i] = temp[i].pos; m_children.Add( temp[i].id ); } // Delete array delete [] temp; #endif #if 0 // Too slow wxGtkTreeModelChildren temp; WX_APPEND_ARRAY( temp, m_children ); gs_internal = m_internal; m_children.Sort( &wxGtkTreeModelChildCmp ); unsigned int pos; for (pos = 0; pos < child_count; pos++) { void *id = m_children.Item( pos ); int old_pos = temp.Index( id ); new_order[pos] = old_pos; } #endif GtkTreeModel *gtk_tree_model = GTK_TREE_MODEL( m_internal->GetGtkModel() ); GtkTreeIter iter; iter.user_data = GetItem().GetID(); iter.stamp = m_internal->GetGtkModel()->stamp; gtk_tree_model_rows_reordered( gtk_tree_model, wxGtkTreePath(m_internal->get_path(&iter)), &iter, new_order ); delete [] new_order; unsigned int pos; for (pos = 0; pos < node_count; pos++) { wxGtkTreeModelNode *node = m_nodes.Item( pos ); node->Resort(); } } //----------------------------------------------------------------------------- // wxDataViewCtrlInternal //----------------------------------------------------------------------------- wxDataViewCtrlInternal::wxDataViewCtrlInternal( wxDataViewCtrl *owner, wxDataViewModel *wx_model ) { m_owner = owner; m_wx_model = wx_model; m_root = NULL; m_sort_order = GTK_SORT_ASCENDING; m_sort_column = -1; m_dataview_sort_column = NULL; m_dragDataObject = NULL; m_dropDataObject = NULL; m_dirty = false; m_selectionFuncSet = false; m_gtk_model = wxgtk_tree_model_new(); m_gtk_model->internal = this; m_notifier = new wxGtkDataViewModelNotifier( wx_model, this ); wx_model->AddNotifier( m_notifier ); // g_object_unref( gtk_model ); ??? if (!m_wx_model->IsVirtualListModel()) InitTree(); gtk_tree_view_set_model( GTK_TREE_VIEW(m_owner->GtkGetTreeView()), GTK_TREE_MODEL(m_gtk_model) ); } wxDataViewCtrlInternal::~wxDataViewCtrlInternal() { m_wx_model->RemoveNotifier( m_notifier ); // remove the model from the GtkTreeView before it gets destroyed gtk_tree_view_set_model( GTK_TREE_VIEW( m_owner->GtkGetTreeView() ), NULL ); g_object_unref( m_gtk_model ); delete m_root; delete m_dragDataObject; delete m_dropDataObject; } void wxDataViewCtrlInternal::ScheduleRefresh() { m_dirty = true; } void wxDataViewCtrlInternal::OnInternalIdle() { if (m_dirty) { GtkWidget *widget = m_owner->GtkGetTreeView(); gtk_widget_queue_draw( widget ); m_dirty = false; } } void wxDataViewCtrlInternal::InitTree() { wxDataViewItem item; m_root = new wxGtkTreeModelNode( NULL, item, this ); BuildBranch( m_root ); } void wxDataViewCtrlInternal::BuildBranch( wxGtkTreeModelNode *node ) { if (node->GetChildCount() == 0) { wxDataViewItemArray children; unsigned int count = m_wx_model->GetChildren( node->GetItem(), children ); unsigned int pos; for (pos = 0; pos < count; pos++) { wxDataViewItem child = children[pos]; if (m_wx_model->IsContainer( child )) node->AddNode( new wxGtkTreeModelNode( node, child, this ) ); else node->AddLeaf( child.GetID() ); // Don't send any events here } } } // GTK+ dnd iface bool wxDataViewCtrlInternal::EnableDragSource( const wxDataFormat &format ) { wxGtkString atom_str( gdk_atom_name( format ) ); m_dragSourceTargetEntryTarget = wxCharBuffer( atom_str ); m_dragSourceTargetEntry.target = m_dragSourceTargetEntryTarget.data(); m_dragSourceTargetEntry.flags = 0; m_dragSourceTargetEntry.info = static_cast(-1); gtk_tree_view_enable_model_drag_source( GTK_TREE_VIEW(m_owner->GtkGetTreeView() ), GDK_BUTTON1_MASK, &m_dragSourceTargetEntry, 1, (GdkDragAction) GDK_ACTION_COPY ); return true; } bool wxDataViewCtrlInternal::EnableDropTarget( const wxDataFormat &format ) { wxGtkString atom_str( gdk_atom_name( format ) ); m_dropTargetTargetEntryTarget = wxCharBuffer( atom_str ); m_dropTargetTargetEntry.target = m_dropTargetTargetEntryTarget.data(); m_dropTargetTargetEntry.flags = 0; m_dropTargetTargetEntry.info = static_cast(-1); gtk_tree_view_enable_model_drag_dest( GTK_TREE_VIEW(m_owner->GtkGetTreeView() ), &m_dropTargetTargetEntry, 1, (GdkDragAction) GDK_ACTION_COPY ); return true; } gboolean wxDataViewCtrlInternal::row_draggable( GtkTreeDragSource *WXUNUSED(drag_source), GtkTreePath *path ) { delete m_dragDataObject; m_dragDataObject = NULL; #ifdef __WXGTK4__ return false; #else wxDataViewCtrl* const dvc = GetOwner(); wxDataViewItem item(dvc->GTKPathToItem(path)); if ( !item ) return FALSE; wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, dvc, item); wxGCC_WARNING_SUPPRESS(deprecated-declarations) gint x, y; gtk_widget_get_pointer(m_owner->GtkGetTreeView(), &x, &y); wxGCC_WARNING_RESTORE() event.SetPosition(x, y); if (!m_owner->HandleWindowEvent( event )) return FALSE; if (!event.IsAllowed()) return FALSE; wxDataObject *obj = event.GetDataObject(); if (!obj) return FALSE; m_dragDataObject = obj; return TRUE; #endif } gboolean wxDataViewCtrlInternal::drag_data_delete(GtkTreeDragSource *WXUNUSED(drag_source), GtkTreePath *WXUNUSED(path)) { return FALSE; } gboolean wxDataViewCtrlInternal::drag_data_get( GtkTreeDragSource *WXUNUSED(drag_source), GtkTreePath *path, GtkSelectionData *selection_data ) { wxDataViewItem item(GetOwner()->GTKPathToItem(path)); if ( !item ) return FALSE; GdkAtom target = gtk_selection_data_get_target(selection_data); if (!m_dragDataObject->IsSupported(target)) return FALSE; size_t size = m_dragDataObject->GetDataSize(target); if (size == 0) return FALSE; void *buf = malloc( size ); gboolean res = FALSE; if (m_dragDataObject->GetDataHere(target, buf)) { res = TRUE; gtk_selection_data_set(selection_data, target, 8, (const guchar*) buf, size ); } free( buf ); return res; } gboolean wxDataViewCtrlInternal::drag_data_received(GtkTreeDragDest *WXUNUSED(drag_dest), GtkTreePath *path, GtkSelectionData *selection_data) { wxDataViewItem item(GetOwner()->GTKPathToItem(path)); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP, m_owner, item); event.SetDataFormat(gtk_selection_data_get_target(selection_data)); event.SetDataSize(gtk_selection_data_get_length(selection_data)); event.SetDataBuffer(const_cast(gtk_selection_data_get_data(selection_data))); if (!m_owner->HandleWindowEvent( event )) return FALSE; if (!event.IsAllowed()) return FALSE; return TRUE; } gboolean wxDataViewCtrlInternal::row_drop_possible(GtkTreeDragDest *WXUNUSED(drag_dest), GtkTreePath *path, GtkSelectionData *selection_data) { wxDataViewItem item(GetOwner()->GTKPathToItem(path)); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner, item); event.SetDataFormat(gtk_selection_data_get_target(selection_data)); event.SetDataSize(gtk_selection_data_get_length(selection_data)); if (!m_owner->HandleWindowEvent( event )) return FALSE; if (!event.IsAllowed()) return FALSE; return TRUE; } // notifications from wxDataViewModel bool wxDataViewCtrlInternal::Cleared() { if (m_root) { delete m_root; m_root = NULL; } InitTree(); ScheduleRefresh(); return true; } void wxDataViewCtrlInternal::Resort() { if (!m_wx_model->IsVirtualListModel()) m_root->Resort(); ScheduleRefresh(); } bool wxDataViewCtrlInternal::ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item ) { if (!m_wx_model->IsVirtualListModel()) { wxGtkTreeModelNode *parent_node = FindNode( parent ); wxCHECK_MSG(parent_node, false, "Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel"); wxDataViewItemArray modelSiblings; m_wx_model->GetChildren(parent, modelSiblings); const int modelSiblingsSize = modelSiblings.size(); int posInModel = modelSiblings.Index(item, /*fromEnd=*/true); wxCHECK_MSG( posInModel != wxNOT_FOUND, false, "adding non-existent item?" ); const wxGtkTreeModelChildren& nodeSiblings = parent_node->GetChildren(); const int nodeSiblingsSize = nodeSiblings.size(); int nodePos = 0; if ( posInModel == modelSiblingsSize - 1 ) { nodePos = nodeSiblingsSize; } else if ( modelSiblingsSize == nodeSiblingsSize + 1 ) { // This is the simple case when our node tree already matches the // model and only this one item is missing. nodePos = posInModel; } else { // It's possible that a larger discrepancy between the model and // our realization exists. This can happen e.g. when adding a bunch // of items to the model and then calling ItemsAdded() just once // afterwards. In this case, we must find the right position by // looking at sibling items. // append to the end if we won't find a better position: nodePos = nodeSiblingsSize; for ( int nextItemPos = posInModel + 1; nextItemPos < modelSiblingsSize; nextItemPos++ ) { int nextNodePos = parent_node->FindChildByItem(modelSiblings[nextItemPos]); if ( nextNodePos != wxNOT_FOUND ) { nodePos = nextNodePos; break; } } } if (m_wx_model->IsContainer( item )) parent_node->InsertNode( new wxGtkTreeModelNode( parent_node, item, this ), nodePos ); else parent_node->InsertLeaf( item.GetID(), nodePos ); } ScheduleRefresh(); return true; } bool wxDataViewCtrlInternal::ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item ) { if (!m_wx_model->IsVirtualListModel()) { wxGtkTreeModelNode *parent_node = FindNode( parent ); wxASSERT_MSG(parent_node, "Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel"); parent_node->DeleteChild( item.GetID() ); } ScheduleRefresh(); return true; } bool wxDataViewCtrlInternal::ItemChanged( const wxDataViewItem &item ) { wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, m_owner, item); m_owner->HandleWindowEvent( event ); return true; } bool wxDataViewCtrlInternal::ValueChanged( const wxDataViewItem &item, unsigned int view_column ) { wxDataViewColumn* const column = m_owner->GetColumn(view_column); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, m_owner, column, item); m_owner->HandleWindowEvent( event ); return true; } // GTK+ model iface GtkTreeModelFlags wxDataViewCtrlInternal::get_flags() { int flags = 0; if ( m_wx_model->IsListModel() ) flags |= GTK_TREE_MODEL_LIST_ONLY; if ( !m_wx_model->IsVirtualListModel() ) flags |= GTK_TREE_MODEL_ITERS_PERSIST; return GtkTreeModelFlags(flags); } gboolean wxDataViewCtrlInternal::get_iter( GtkTreeIter *iter, GtkTreePath *path ) { if (m_wx_model->IsVirtualListModel()) { wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model; unsigned int i = (unsigned int)gtk_tree_path_get_indices (path)[0]; if (i >= wx_model->GetCount()) return FALSE; iter->stamp = m_gtk_model->stamp; // user_data is just the index +1 iter->user_data = wxUIntToPtr(i+1); return TRUE; } else { int depth = gtk_tree_path_get_depth( path ); wxGtkTreeModelNode *node = m_root; int i; for (i = 0; i < depth; i++) { BuildBranch( node ); gint pos = gtk_tree_path_get_indices (path)[i]; if (pos < 0) return FALSE; if ((size_t)pos >= node->GetChildCount()) return FALSE; void* id = node->GetChildren().Item( (size_t) pos ); if (i == depth-1) { iter->stamp = m_gtk_model->stamp; iter->user_data = id; return TRUE; } size_t count = node->GetNodes().GetCount(); size_t pos2; for (pos2 = 0; pos2 < count; pos2++) { wxGtkTreeModelNode *child_node = node->GetNodes().Item( pos2 ); if (child_node->GetItem().GetID() == id) { node = child_node; break; } } } } return FALSE; } GtkTreePath *wxDataViewCtrlInternal::get_path( GtkTreeIter *iter ) { // When this is called from ItemDeleted(), the item is already // deleted in the model. GtkTreePath *retval = gtk_tree_path_new (); if (m_wx_model->IsVirtualListModel()) { // iter is root, add nothing if (!iter->user_data) return retval; // user_data is just the index +1 int i = ( (wxUIntPtr) iter->user_data ) -1; gtk_tree_path_append_index (retval, i); } else { void *id = iter->user_data; wxGtkTreeModelNode *node = FindParentNode( iter ); while (node) { int pos = node->GetChildren().Index( id ); gtk_tree_path_prepend_index( retval, pos ); id = node->GetItem().GetID(); node = node->GetParent(); } } return retval; } gboolean wxDataViewCtrlInternal::iter_next( GtkTreeIter *iter ) { if (m_wx_model->IsVirtualListModel()) { wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model; // user_data is just the index +1 int n = ( (wxUIntPtr) iter->user_data ) -1; if (n == -1) { iter->user_data = NULL; return FALSE; } if (n >= (int) wx_model->GetCount()-1) { iter->user_data = NULL; return FALSE; } // user_data is just the index +1 (+2 because we need the next) iter->user_data = wxUIntToPtr(n+2); } else { wxGtkTreeModelNode *parent = FindParentNode( iter ); if( parent == NULL ) { iter->user_data = NULL; return FALSE; } int pos = parent->GetChildren().Index( iter->user_data ); if (pos == (int) parent->GetChildCount()-1) { iter->user_data = NULL; return FALSE; } iter->user_data = parent->GetChildren().Item( pos+1 ); } return TRUE; } gboolean wxDataViewCtrlInternal::iter_children( GtkTreeIter *iter, GtkTreeIter *parent ) { if (m_wx_model->IsVirtualListModel()) { // this is a list, nodes have no children if (parent) return FALSE; iter->stamp = m_gtk_model->stamp; iter->user_data = (gpointer) 1; return TRUE; } else { if (iter == NULL) { if (m_root->GetChildCount() == 0) return FALSE; iter->stamp = m_gtk_model->stamp; iter->user_data = (gpointer) m_root->GetChildren().Item( 0 ); return TRUE; } wxDataViewItem item; if (parent) item = wxDataViewItem( (void*) parent->user_data ); if (!m_wx_model->IsContainer( item )) return FALSE; wxGtkTreeModelNode *parent_node = FindNode( parent ); wxCHECK_MSG(parent_node, FALSE, "Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel"); BuildBranch( parent_node ); if (parent_node->GetChildCount() == 0) return FALSE; iter->stamp = m_gtk_model->stamp; iter->user_data = (gpointer) parent_node->GetChildren().Item( 0 ); } return TRUE; } gboolean wxDataViewCtrlInternal::iter_has_child( GtkTreeIter *iter ) { if (m_wx_model->IsVirtualListModel()) { wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model; if (iter == NULL) return (wx_model->GetCount() > 0); // this is a list, nodes have no children return FALSE; } else { if (iter == NULL) return (m_root->GetChildCount() > 0); wxDataViewItem item( (void*) iter->user_data ); bool is_container = m_wx_model->IsContainer( item ); if (!is_container) return FALSE; wxGtkTreeModelNode *node = FindNode( iter ); wxCHECK_MSG(node, FALSE, "Did you forget a call to ItemAdded()? The iterator is unknown to the wxGtkTreeModel"); BuildBranch( node ); return (node->GetChildCount() > 0); } } gint wxDataViewCtrlInternal::iter_n_children( GtkTreeIter *iter ) { if (m_wx_model->IsVirtualListModel()) { wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model; if (iter == NULL) return (gint) wx_model->GetCount(); return 0; } else { if (iter == NULL) return m_root->GetChildCount(); wxDataViewItem item( (void*) iter->user_data ); if (!m_wx_model->IsContainer( item )) return 0; wxGtkTreeModelNode *parent_node = FindNode( iter ); wxCHECK_MSG(parent_node, FALSE, "Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel"); BuildBranch( parent_node ); return parent_node->GetChildCount(); } } gboolean wxDataViewCtrlInternal::iter_nth_child( GtkTreeIter *iter, GtkTreeIter *parent, gint n ) { if (m_wx_model->IsVirtualListModel()) { wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model; if (parent) return FALSE; if (n < 0) return FALSE; if (n >= (gint) wx_model->GetCount()) return FALSE; iter->stamp = m_gtk_model->stamp; // user_data is just the index +1 iter->user_data = wxUIntToPtr(n+1); return TRUE; } else { void* id = NULL; if (parent) id = (void*) parent->user_data; wxDataViewItem item( id ); if (!m_wx_model->IsContainer( item )) return FALSE; wxGtkTreeModelNode *parent_node = FindNode( parent ); wxCHECK_MSG(parent_node, FALSE, "Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel"); BuildBranch( parent_node ); iter->stamp = m_gtk_model->stamp; iter->user_data = parent_node->GetChildren().Item( n ); return TRUE; } } gboolean wxDataViewCtrlInternal::iter_parent( GtkTreeIter *iter, GtkTreeIter *child ) { if (m_wx_model->IsVirtualListModel()) { return FALSE; } else { wxGtkTreeModelNode *node = FindParentNode( child ); if (!node) return FALSE; iter->stamp = m_gtk_model->stamp; iter->user_data = (gpointer) node->GetItem().GetID(); return TRUE; } } // item can be deleted already in the model int wxDataViewCtrlInternal::GetIndexOf( const wxDataViewItem &parent, const wxDataViewItem &item ) { if (m_wx_model->IsVirtualListModel()) { return wxPtrToUInt(item.GetID()) - 1; } else { wxGtkTreeModelNode *parent_node = FindNode( parent ); wxGtkTreeModelChildren &children = parent_node->GetChildren(); size_t j; for (j = 0; j < children.GetCount(); j++) { if (children[j] == item.GetID()) return j; } } return -1; } static wxGtkTreeModelNode* wxDataViewCtrlInternal_FindNode( wxDataViewModel * model, wxGtkTreeModelNode *treeNode, const wxDataViewItem &item ) { if( model == NULL ) return NULL; ItemList list; list.DeleteContents( true ); wxDataViewItem it( item ); while( it.IsOk() ) { wxDataViewItem * pItem = new wxDataViewItem( it ); list.Insert( pItem ); it = model->GetParent( it ); } wxGtkTreeModelNode * node = treeNode; for( ItemList::compatibility_iterator n = list.GetFirst(); n; n = n->GetNext() ) { if( node && node->GetNodes().GetCount() != 0 ) { int len = node->GetNodes().GetCount(); wxGtkTreeModelNodes &nodes = node->GetNodes(); int j = 0; for( ; j < len; j ++) { if( nodes[j]->GetItem() == *(n->GetData())) { node = nodes[j]; break; } } if( j == len ) { return NULL; } } else return NULL; } return node; } wxGtkTreeModelNode *wxDataViewCtrlInternal::FindNode( GtkTreeIter *iter ) { if (!iter) return m_root; wxDataViewItem item( (void*) iter->user_data ); if (!item.IsOk()) return m_root; wxGtkTreeModelNode *result = wxDataViewCtrlInternal_FindNode( m_wx_model, m_root, item ); /* if (!result) { wxLogDebug( "Not found %p", iter->user_data ); char *crash = NULL; *crash = 0; } // TODO: remove this code */ return result; } wxGtkTreeModelNode *wxDataViewCtrlInternal::FindNode( const wxDataViewItem &item ) { if (!item.IsOk()) return m_root; wxGtkTreeModelNode *result = wxDataViewCtrlInternal_FindNode( m_wx_model, m_root, item ); /* if (!result) { wxLogDebug( "Not found %p", item.GetID() ); char *crash = NULL; *crash = 0; } // TODO: remove this code */ return result; } static wxGtkTreeModelNode* wxDataViewCtrlInternal_FindParentNode( wxDataViewModel * model, wxGtkTreeModelNode *treeNode, const wxDataViewItem &item ) { if( model == NULL ) return NULL; ItemList list; list.DeleteContents( true ); if( !item.IsOk() ) return NULL; wxDataViewItem it( model->GetParent( item ) ); while( it.IsOk() ) { wxDataViewItem * pItem = new wxDataViewItem( it ); list.Insert( pItem ); it = model->GetParent( it ); } wxGtkTreeModelNode * node = treeNode; for( ItemList::compatibility_iterator n = list.GetFirst(); n; n = n->GetNext() ) { if( node && node->GetNodes().GetCount() != 0 ) { int len = node->GetNodes().GetCount(); wxGtkTreeModelNodes nodes = node->GetNodes(); int j = 0; for( ; j < len; j ++) { if( nodes[j]->GetItem() == *(n->GetData())) { node = nodes[j]; break; } } if( j == len ) { return NULL; } } else return NULL; } //Examine whether the node is item's parent node int len = node->GetChildCount(); for( int i = 0; i < len ; i ++ ) { if( node->GetChildren().Item( i ) == item.GetID() ) return node; } return NULL; } wxGtkTreeModelNode *wxDataViewCtrlInternal::FindParentNode( GtkTreeIter *iter ) { if (!iter) return NULL; wxDataViewItem item( (void*) iter->user_data ); if (!item.IsOk()) return NULL; return wxDataViewCtrlInternal_FindParentNode( m_wx_model, m_root, item ); } wxGtkTreeModelNode *wxDataViewCtrlInternal::FindParentNode( const wxDataViewItem &item ) { if (!item.IsOk()) return NULL; return wxDataViewCtrlInternal_FindParentNode( m_wx_model, m_root, item ); } //----------------------------------------------------------------------------- // wxDataViewCtrl signal callbacks //----------------------------------------------------------------------------- static void wxdataview_selection_changed_callback( GtkTreeSelection* WXUNUSED(selection), wxDataViewCtrl *dv ) { if (!gtk_widget_get_realized(dv->m_widget)) return; wxDataViewEvent event(wxEVT_DATAVIEW_SELECTION_CHANGED, dv, dv->GetSelection()); dv->HandleWindowEvent( event ); } static void wxdataview_row_activated_callback( GtkTreeView* WXUNUSED(treeview), GtkTreePath *path, GtkTreeViewColumn *WXUNUSED(column), wxDataViewCtrl *dv ) { wxDataViewItem item(dv->GTKPathToItem(path)); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_ACTIVATED, dv, item); dv->HandleWindowEvent( event ); } static gboolean wxdataview_test_expand_row_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter, GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv ) { wxDataViewItem item( (void*) iter->user_data ); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_EXPANDING, dv, item); dv->HandleWindowEvent( event ); return !event.IsAllowed(); } static void wxdataview_row_expanded_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter, GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv ) { wxDataViewItem item( (void*) iter->user_data ); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_EXPANDED, dv, item); dv->HandleWindowEvent( event ); } static gboolean wxdataview_test_collapse_row_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter, GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv ) { wxDataViewItem item( (void*) iter->user_data ); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_COLLAPSING, dv, item); dv->HandleWindowEvent( event ); return !event.IsAllowed(); } static void wxdataview_row_collapsed_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter, GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv ) { wxDataViewItem item( (void*) iter->user_data ); wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_COLLAPSED, dv, item); dv->HandleWindowEvent( event ); } //----------------------------------------------------------------------------- // wxDataViewCtrl //----------------------------------------------------------------------------- void wxDataViewCtrl::AddChildGTK(wxWindowGTK*) { // this is for cell editing controls, which will be // made children of the GtkTreeView automatically } //----------------------------------------------------------------------------- // "motion_notify_event" //----------------------------------------------------------------------------- static gboolean gtk_dataview_motion_notify_callback( GtkWidget *WXUNUSED(widget), GdkEventMotion *gdk_event, wxDataViewCtrl *dv ) { int x = gdk_event->x; int y = gdk_event->y; if (gdk_event->is_hint) { #ifdef __WXGTK3__ gdk_window_get_device_position(gdk_event->window, gdk_event->device, &x, &y, NULL); #else gdk_window_get_pointer(gdk_event->window, &x, &y, NULL); #endif } wxGtkTreePath path; GtkTreeViewColumn *column = NULL; gint cell_x = 0; gint cell_y = 0; if (gtk_tree_view_get_path_at_pos( GTK_TREE_VIEW(dv->GtkGetTreeView()), x, y, path.ByRef(), &column, &cell_x, &cell_y)) { if (path) { GtkTreeIter iter; dv->GtkGetInternal()->get_iter( &iter, path ); } } return FALSE; } //----------------------------------------------------------------------------- // "button_press_event" //----------------------------------------------------------------------------- static gboolean gtk_dataview_button_press_callback( GtkWidget *WXUNUSED(widget), GdkEventButton *gdk_event, wxDataViewCtrl *dv ) { if ((gdk_event->button == 3) && (gdk_event->type == GDK_BUTTON_PRESS)) { wxGtkTreePath path; GtkTreeViewColumn *column = NULL; gint cell_x = 0; gint cell_y = 0; gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW(dv->GtkGetTreeView()), (int) gdk_event->x, (int) gdk_event->y, path.ByRef(), &column, &cell_x, &cell_y ); // If the right click is on an item that isn't selected, select it, as is // commonly done. Do not do it if the item under mouse is already selected, // because it could be a part of multi-item selection. GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dv->GtkGetTreeView())); if ( !gtk_tree_selection_path_is_selected(selection, path) ) { gtk_tree_selection_unselect_all(selection); gtk_tree_selection_select_path(selection, path); } wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, dv, dv->GTKPathToItem(path)); return dv->HandleWindowEvent( event ); } return FALSE; } wxIMPLEMENT_DYNAMIC_CLASS(wxDataViewCtrl, wxDataViewCtrlBase); wxDataViewCtrl::~wxDataViewCtrl() { // Stop editing before destroying the control to remove any event handlers // which are added when editing started: if we didn't do this, the base // class dtor would assert as it checks for any leftover handlers. if ( m_treeview ) { GtkTreeViewColumn *col; gtk_tree_view_get_cursor(GTK_TREE_VIEW(m_treeview), NULL, &col); wxDataViewColumn * const wxcol = FromGTKColumn(col); if ( wxcol ) { // This won't do anything if we're not editing it wxcol->GetRenderer()->CancelEditing(); } GTKDisconnect(m_treeview); GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview)); if (selection) GTKDisconnect(selection); } m_cols.Clear(); delete m_internal; } void wxDataViewCtrl::Init() { m_treeview = NULL; m_internal = NULL; m_cols.DeleteContents( true ); m_uniformRowHeight = -1; } bool wxDataViewCtrl::Create(wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) { if (!PreCreation( parent, pos, size ) || !CreateBase( parent, id, pos, size, style, validator, name )) { wxFAIL_MSG( wxT("wxDataViewCtrl creation failed") ); return false; } m_widget = gtk_scrolled_window_new (NULL, NULL); g_object_ref(m_widget); GTKScrolledWindowSetBorder(m_widget, style); m_treeview = gtk_tree_view_new(); gtk_container_add (GTK_CONTAINER (m_widget), m_treeview); m_focusWidget = GTK_WIDGET(m_treeview); bool fixed = (style & wxDV_VARIABLE_LINE_HEIGHT) == 0; gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), fixed ); if (style & wxDV_MULTIPLE) { GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE ); } gtk_tree_view_set_headers_visible( GTK_TREE_VIEW(m_treeview), (style & wxDV_NO_HEADER) == 0 ); #ifdef __WXGTK210__ if (wx_is_at_least_gtk2(10)) { GtkTreeViewGridLines grid = GTK_TREE_VIEW_GRID_LINES_NONE; if ((style & wxDV_HORIZ_RULES) != 0 && (style & wxDV_VERT_RULES) != 0) grid = GTK_TREE_VIEW_GRID_LINES_BOTH; else if (style & wxDV_VERT_RULES) grid = GTK_TREE_VIEW_GRID_LINES_VERTICAL; else if (style & wxDV_HORIZ_RULES) grid = GTK_TREE_VIEW_GRID_LINES_HORIZONTAL; if (grid != GTK_TREE_VIEW_GRID_LINES_NONE) gtk_tree_view_set_grid_lines( GTK_TREE_VIEW(m_treeview), grid ); } #endif #ifndef __WXGTK4__ wxGCC_WARNING_SUPPRESS(deprecated-declarations) gtk_tree_view_set_rules_hint( GTK_TREE_VIEW(m_treeview), (style & wxDV_ROW_LINES) != 0 ); wxGCC_WARNING_RESTORE() #endif gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (m_widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_show (m_treeview); m_parent->DoAddChild( this ); PostCreation(size); g_signal_connect_after(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview)), "changed", G_CALLBACK(wxdataview_selection_changed_callback), this); g_signal_connect_after (m_treeview, "row-activated", G_CALLBACK (wxdataview_row_activated_callback), this); g_signal_connect (m_treeview, "test-collapse-row", G_CALLBACK (wxdataview_test_collapse_row_callback), this); g_signal_connect_after (m_treeview, "row-collapsed", G_CALLBACK (wxdataview_row_collapsed_callback), this); g_signal_connect (m_treeview, "test-expand-row", G_CALLBACK (wxdataview_test_expand_row_callback), this); g_signal_connect_after (m_treeview, "row-expanded", G_CALLBACK (wxdataview_row_expanded_callback), this); g_signal_connect (m_treeview, "motion_notify_event", G_CALLBACK (gtk_dataview_motion_notify_callback), this); g_signal_connect (m_treeview, "button_press_event", G_CALLBACK (gtk_dataview_button_press_callback), this); return true; } wxDataViewItem wxDataViewCtrl::GTKPathToItem(GtkTreePath *path) const { GtkTreeIter iter; return wxDataViewItem(path && m_internal->get_iter(&iter, path) ? iter.user_data : NULL); } void wxDataViewCtrl::OnInternalIdle() { wxWindow::OnInternalIdle(); if ( !m_internal ) return; m_internal->OnInternalIdle(); unsigned int cols = GetColumnCount(); unsigned int i; for (i = 0; i < cols; i++) { wxDataViewColumn *col = GetColumn( i ); col->OnInternalIdle(); } if (m_ensureVisibleDefered.IsOk()) { ExpandAncestors(m_ensureVisibleDefered); GtkTreeIter iter; iter.user_data = (gpointer) m_ensureVisibleDefered.GetID(); wxGtkTreePath path(m_internal->get_path( &iter )); gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW(m_treeview), path, NULL, false, 0.0, 0.0 ); m_ensureVisibleDefered = wxDataViewItem(0); } } bool wxDataViewCtrl::AssociateModel( wxDataViewModel *model ) { wxDELETE(m_internal); if (!wxDataViewCtrlBase::AssociateModel( model )) return false; if ( model ) m_internal = new wxDataViewCtrlInternal( this, model ); return true; } bool wxDataViewCtrl::EnableDragSource( const wxDataFormat &format ) { wxCHECK_MSG( m_internal, false, "model must be associated before calling EnableDragSource" ); return m_internal->EnableDragSource( format ); } bool wxDataViewCtrl::EnableDropTarget( const wxDataFormat &format ) { wxCHECK_MSG( m_internal, false, "model must be associated before calling EnableDragTarget" ); return m_internal->EnableDropTarget( format ); } bool wxDataViewCtrl::AppendColumn( wxDataViewColumn *col ) { if (!wxDataViewCtrlBase::AppendColumn(col)) return false; m_cols.Append( col ); if (gtk_tree_view_column_get_sizing( GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ) != GTK_TREE_VIEW_COLUMN_FIXED) { gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), FALSE ); } gtk_tree_view_append_column( GTK_TREE_VIEW(m_treeview), GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ); return true; } bool wxDataViewCtrl::PrependColumn( wxDataViewColumn *col ) { if (!wxDataViewCtrlBase::PrependColumn(col)) return false; m_cols.Insert( col ); if (gtk_tree_view_column_get_sizing( GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ) != GTK_TREE_VIEW_COLUMN_FIXED) { gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), FALSE ); } gtk_tree_view_insert_column( GTK_TREE_VIEW(m_treeview), GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()), 0 ); return true; } bool wxDataViewCtrl::InsertColumn( unsigned int pos, wxDataViewColumn *col ) { if (!wxDataViewCtrlBase::InsertColumn(pos,col)) return false; m_cols.Insert( pos, col ); if (gtk_tree_view_column_get_sizing( GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ) != GTK_TREE_VIEW_COLUMN_FIXED) { gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), FALSE ); } gtk_tree_view_insert_column( GTK_TREE_VIEW(m_treeview), GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()), pos ); return true; } unsigned int wxDataViewCtrl::GetColumnCount() const { return m_cols.GetCount(); } wxDataViewColumn* wxDataViewCtrl::FromGTKColumn(GtkTreeViewColumn *gtk_col) const { if ( !gtk_col ) return NULL; wxDataViewColumnList::const_iterator iter; for (iter = m_cols.begin(); iter != m_cols.end(); ++iter) { wxDataViewColumn *col = *iter; if (GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) == gtk_col) { return col; } } wxFAIL_MSG( "No matching column?" ); return NULL; } wxDataViewColumn* wxDataViewCtrl::GetColumn( unsigned int pos ) const { GtkTreeViewColumn *gtk_col = gtk_tree_view_get_column( GTK_TREE_VIEW(m_treeview), pos ); return FromGTKColumn(gtk_col); } bool wxDataViewCtrl::DeleteColumn( wxDataViewColumn *column ) { gtk_tree_view_remove_column( GTK_TREE_VIEW(m_treeview), GTK_TREE_VIEW_COLUMN(column->GetGtkHandle()) ); m_cols.DeleteObject( column ); return true; } bool wxDataViewCtrl::ClearColumns() { wxDataViewColumnList::iterator iter; for (iter = m_cols.begin(); iter != m_cols.end(); ++iter) { wxDataViewColumn *col = *iter; gtk_tree_view_remove_column( GTK_TREE_VIEW(m_treeview), GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ); } m_cols.Clear(); return true; } int wxDataViewCtrl::GetColumnPosition( const wxDataViewColumn *column ) const { GtkTreeViewColumn *gtk_column = GTK_TREE_VIEW_COLUMN(column->GetGtkHandle()); wxGtkList list(gtk_tree_view_get_columns(GTK_TREE_VIEW(m_treeview))); return g_list_index( list, (gconstpointer) gtk_column ); } wxDataViewColumn *wxDataViewCtrl::GetSortingColumn() const { wxCHECK_MSG( m_internal, NULL, "model must be associated before calling GetSortingColumn" ); return m_internal->GetDataViewSortColumn(); } void wxDataViewCtrl::DoExpand( const wxDataViewItem & item ) { GtkTreeIter iter; iter.user_data = item.GetID(); wxGtkTreePath path(m_internal->get_path( &iter )); gtk_tree_view_expand_row( GTK_TREE_VIEW(m_treeview), path, false ); } void wxDataViewCtrl::Collapse( const wxDataViewItem & item ) { wxCHECK_RET( m_internal, "model must be associated before calling Collapse" ); GtkTreeIter iter; iter.user_data = item.GetID(); wxGtkTreePath path(m_internal->get_path( &iter )); gtk_tree_view_collapse_row( GTK_TREE_VIEW(m_treeview), path ); } bool wxDataViewCtrl::IsExpanded( const wxDataViewItem & item ) const { wxCHECK_MSG( m_internal, false, "model must be associated before calling IsExpanded" ); GtkTreeIter iter; iter.user_data = item.GetID(); wxGtkTreePath path(m_internal->get_path( &iter )); return gtk_tree_view_row_expanded( GTK_TREE_VIEW(m_treeview), path ) != 0; } wxDataViewItem wxDataViewCtrl::DoGetCurrentItem() const { // The tree doesn't have any current item if it hadn't been created yet but // it's arguably not an error to call this function in this case so just // return an invalid item without asserting. if ( !m_treeview || !m_internal ) return wxDataViewItem(); wxGtkTreePath path; gtk_tree_view_get_cursor(GTK_TREE_VIEW(m_treeview), path.ByRef(), NULL); return GTKPathToItem(path); } void wxDataViewCtrl::DoSetCurrentItem(const wxDataViewItem& item) { wxCHECK_RET( m_treeview, "Current item can't be set before creating the control." ); wxCHECK_RET( m_internal, "model must be associated before setting current item" ); // We need to make sure the model knows about this item or the path would // be invalid and gtk_tree_view_set_cursor() would silently do nothing. ExpandAncestors(item); // We also need to preserve the existing selection from changing. // Unfortunately the only way to do it seems to use our own selection // function and forbid any selection changes during set cursor call. wxGtkTreeSelectionLock lock(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview)), m_internal->m_selectionFuncSet); // Do move the cursor now. GtkTreeIter iter; iter.user_data = item.GetID(); wxGtkTreePath path(m_internal->get_path( &iter )); gtk_tree_view_set_cursor(GTK_TREE_VIEW(m_treeview), path, NULL, FALSE); } wxDataViewItem wxDataViewCtrl::GetTopItem() const { #if GTK_CHECK_VERSION(2,8,0) if (!wx_is_at_least_gtk2(8)) return wxDataViewItem(); wxGtkTreePath start; if ( gtk_tree_view_get_visible_range ( GTK_TREE_VIEW(m_treeview), start.ByRef(), NULL ) ) { return GTKPathToItem(start); } #endif return wxDataViewItem(); } int wxDataViewCtrl::GetCountPerPage() const { wxGtkTreePath path; GtkTreeViewColumn *column; if ( !gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW(m_treeview), 0, 0, path.ByRef(), &column, NULL, NULL ) ) { return -1; } GdkRectangle rect; gtk_tree_view_get_cell_area(GTK_TREE_VIEW(m_treeview), path, column, &rect); if ( !rect.height ) return -1; GdkRectangle vis; gtk_tree_view_get_visible_rect(GTK_TREE_VIEW(m_treeview), &vis); return vis.height / rect.height; } wxDataViewColumn *wxDataViewCtrl::GetCurrentColumn() const { // The tree doesn't have any current item if it hadn't been created yet but // it's arguably not an error to call this function in this case so just // return NULL without asserting. if ( !m_treeview ) return NULL; GtkTreeViewColumn *col; gtk_tree_view_get_cursor(GTK_TREE_VIEW(m_treeview), NULL, &col); return FromGTKColumn(col); } void wxDataViewCtrl::EditItem(const wxDataViewItem& item, const wxDataViewColumn *column) { wxCHECK_RET( m_treeview, "item can't be edited before creating the control." ); wxCHECK_RET( m_internal, "model must be associated before editing an item" ); wxCHECK_RET( item.IsOk(), "invalid item" ); wxCHECK_RET( column, "no column provided" ); // We need to make sure the model knows about this item or the path would // be invalid and gtk_tree_view_set_cursor() would silently do nothing. ExpandAncestors(item); GtkTreeViewColumn *gcolumn = GTK_TREE_VIEW_COLUMN(column->GetGtkHandle()); // We also need to preserve the existing selection from changing. // Unfortunately the only way to do it seems to use our own selection // function and forbid any selection changes during set cursor call. wxGtkTreeSelectionLock lock(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview)), m_internal->m_selectionFuncSet); // Do move the cursor now. GtkTreeIter iter; iter.user_data = item.GetID(); wxGtkTreePath path(m_internal->get_path( &iter )); gtk_tree_view_set_cursor(GTK_TREE_VIEW(m_treeview), path, gcolumn, TRUE); } int wxDataViewCtrl::GetSelectedItemsCount() const { GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); return gtk_tree_selection_count_selected_rows(selection); } int wxDataViewCtrl::GetSelections( wxDataViewItemArray & sel ) const { wxCHECK_MSG( m_internal, 0, "model must be associated before calling GetSelections" ); sel.Clear(); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); if (HasFlag(wxDV_MULTIPLE)) { GtkTreeModel *model; wxGtkTreePathList list(gtk_tree_selection_get_selected_rows(selection, &model)); for ( GList* current = list; current; current = g_list_next(current) ) { GtkTreePath *path = (GtkTreePath*) current->data; sel.Add(GTKPathToItem(path)); } } else { GtkTreeIter iter; if (gtk_tree_selection_get_selected( selection, NULL, &iter )) { sel.Add( wxDataViewItem(iter.user_data) ); } } return sel.size(); } void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel ) { wxCHECK_RET( m_internal, "model must be associated before calling SetSelections" ); SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); gtk_tree_selection_unselect_all( selection ); wxDataViewItem last_parent; size_t i; for (i = 0; i < sel.GetCount(); i++) { wxDataViewItem item = sel[i]; wxDataViewItem parent = GetModel()->GetParent( item ); if (parent) { if (parent != last_parent) ExpandAncestors(item); } last_parent = parent; GtkTreeIter iter; iter.stamp = m_internal->GetGtkModel()->stamp; iter.user_data = (gpointer) item.GetID(); gtk_tree_selection_select_iter( selection, &iter ); } } void wxDataViewCtrl::Select( const wxDataViewItem & item ) { wxCHECK_RET( m_internal, "model must be associated before calling Select" ); ExpandAncestors(item); SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); GtkTreeIter iter; iter.stamp = m_internal->GetGtkModel()->stamp; iter.user_data = (gpointer) item.GetID(); gtk_tree_selection_select_iter( selection, &iter ); } void wxDataViewCtrl::Unselect( const wxDataViewItem & item ) { wxCHECK_RET( m_internal, "model must be associated before calling Unselect" ); SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); GtkTreeIter iter; iter.stamp = m_internal->GetGtkModel()->stamp; iter.user_data = (gpointer) item.GetID(); gtk_tree_selection_unselect_iter( selection, &iter ); } bool wxDataViewCtrl::IsSelected( const wxDataViewItem & item ) const { wxCHECK_MSG( m_internal, false, "model must be associated before calling IsSelected" ); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); GtkTreeIter iter; iter.stamp = m_internal->GetGtkModel()->stamp; iter.user_data = (gpointer) item.GetID(); return gtk_tree_selection_iter_is_selected( selection, &iter ) != 0; } void wxDataViewCtrl::SelectAll() { SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); gtk_tree_selection_select_all( selection ); } void wxDataViewCtrl::UnselectAll() { SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); gtk_tree_selection_unselect_all( selection ); } void wxDataViewCtrl::EnsureVisible(const wxDataViewItem& item, const wxDataViewColumn *WXUNUSED(column)) { wxCHECK_RET( m_internal, "model must be associated before calling EnsureVisible" ); m_ensureVisibleDefered = item; ExpandAncestors(item); GtkTreeIter iter; iter.user_data = (gpointer) item.GetID(); wxGtkTreePath path(m_internal->get_path( &iter )); gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW(m_treeview), path, NULL, false, 0.0, 0.0 ); } void wxDataViewCtrl::HitTest(const wxPoint& point, wxDataViewItem& item, wxDataViewColumn *& column) const { wxCHECK_RET( m_internal, "model must be associated before calling HitTest" ); // gtk_tree_view_get_dest_row_at_pos() is the right one. But it does not tell the column. // gtk_tree_view_get_path_at_pos() is the wrong function. It doesn't mind the header but returns column. // See http://mail.gnome.org/archives/gtkmm-list/2005-January/msg00080.html // So we have to use both of them. item = wxDataViewItem(0); column = NULL; wxGtkTreePath path, pathScratch; GtkTreeViewColumn* GtkColumn = NULL; GtkTreeViewDropPosition pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER; gint cell_x = 0; gint cell_y = 0; // cannot directly call GtkGetTreeView(), HitTest is const and so is this pointer wxDataViewCtrl* self = const_cast(this); // ugly workaround, self is NOT const GtkTreeView* treeView = GTK_TREE_VIEW(self->GtkGetTreeView()); // is there possibly a better suited function to get the column? gtk_tree_view_get_path_at_pos( // and this is the wrong call but it delivers the column treeView, (int) point.x, (int) point.y, pathScratch.ByRef(), &GtkColumn, // here we get the GtkColumn &cell_x, &cell_y ); if ( GtkColumn != NULL ) { // we got GTK column // the right call now which takes the header into account gtk_tree_view_get_dest_row_at_pos( treeView, (int) point.x, (int) point.y, path.ByRef(), &pos); if (path) item = wxDataViewItem(GTKPathToItem(path)); // else we got a GTK column but the position is not over an item, e.g. below last item for ( unsigned int i=0, cols=GetColumnCount(); iGetGtkHandle()) == GtkColumn ) { column = col; // here we get the wx column break; } } } // else no column and thus no item, both null } wxRect wxDataViewCtrl::GetItemRect(const wxDataViewItem& WXUNUSED(item), const wxDataViewColumn *WXUNUSED(column)) const { return wxRect(); } bool wxDataViewCtrl::SetRowHeight(int rowHeight) { m_uniformRowHeight = rowHeight; return true; } void wxDataViewCtrl::DoSetExpanderColumn() { gtk_tree_view_set_expander_column( GTK_TREE_VIEW(m_treeview), GTK_TREE_VIEW_COLUMN( GetExpanderColumn()->GetGtkHandle() ) ); } void wxDataViewCtrl::DoSetIndent() { #if GTK_CHECK_VERSION(2, 12, 0) if ( wx_is_at_least_gtk2(12) ) { gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(m_treeview), GetIndent()); } #endif // GTK+ 2.12+ } void wxDataViewCtrl::GtkDisableSelectionEvents() { GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); g_signal_handlers_block_by_func( selection, (void*)wxdataview_selection_changed_callback, this); } void wxDataViewCtrl::GtkEnableSelectionEvents() { GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); g_signal_handlers_unblock_by_func( selection, (void*)wxdataview_selection_changed_callback, this); } // ---------------------------------------------------------------------------- // visual attributes stuff // ---------------------------------------------------------------------------- // static wxVisualAttributes wxDataViewCtrl::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant)) { return GetDefaultAttributesFromGTKWidget(gtk_tree_view_new()); } void wxDataViewCtrl::DoApplyWidgetStyle(GtkRcStyle *style) { wxDataViewCtrlBase::DoApplyWidgetStyle(style); GTKApplyStyle(m_treeview, style); } #endif // !wxHAS_GENERIC_DATAVIEWCTRL #endif // wxUSE_DATAVIEWCTRL