diff --git a/include/wx/gtk/dataview.h b/include/wx/gtk/dataview.h index a6d1d45c3b..059f1dc367 100644 --- a/include/wx/gtk/dataview.h +++ b/include/wx/gtk/dataview.h @@ -188,6 +188,25 @@ public: int GTKGetUniformRowHeight() const { return m_uniformRowHeight; } + // Simple RAII helper for disabling selection events during its lifetime. + class SelectionEventsSuppressor + { + public: + explicit SelectionEventsSuppressor(wxDataViewCtrl* ctrl) + : m_ctrl(ctrl) + { + m_ctrl->GtkDisableSelectionEvents(); + } + + ~SelectionEventsSuppressor() + { + m_ctrl->GtkEnableSelectionEvents(); + } + + private: + wxDataViewCtrl* const m_ctrl; + }; + protected: virtual void DoSetExpanderColumn() wxOVERRIDE; virtual void DoSetIndent() wxOVERRIDE; diff --git a/samples/treelist/treelist.cpp b/samples/treelist/treelist.cpp index 2bdc74507a..0614f0ef84 100644 --- a/samples/treelist/treelist.cpp +++ b/samples/treelist/treelist.cpp @@ -65,6 +65,8 @@ enum Id_CheckboxesUser3State, Id_Checkboxes_End, + Id_DeleteAllItems, + Id_DumpSelection, Id_Check_HTMLDocs, Id_Uncheck_HTMLDocs, @@ -187,6 +189,8 @@ private: void OnAbout(wxCommandEvent& event); void OnExit(wxCommandEvent& event); + void OnDeleteAllItems(wxCommandEvent& event); + void OnSelectionChanged(wxTreeListEvent& event); void OnItemExpanding(wxTreeListEvent& event); void OnItemExpanded(wxTreeListEvent& event); @@ -272,6 +276,8 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(wxID_ABOUT, MyFrame::OnAbout) EVT_MENU(wxID_EXIT, MyFrame::OnExit) + EVT_MENU(Id_DeleteAllItems, MyFrame::OnDeleteAllItems) + EVT_TREELIST_SELECTION_CHANGED(wxID_ANY, MyFrame::OnSelectionChanged) EVT_TREELIST_ITEM_EXPANDING(wxID_ANY, MyFrame::OnItemExpanding) EVT_TREELIST_ITEM_EXPANDED(wxID_ANY, MyFrame::OnItemExpanded) @@ -314,6 +320,8 @@ MyFrame::MyFrame() treeOper->Append(Id_Indet_HTMLDocs, "Make Doc/HTML &indeterminate\tCtrl-I"); treeOper->Append(Id_Select_HTMLDocs, "&Select Doc/HTML item\tCtrl-S"); + treeOper->Append(Id_DeleteAllItems, "DeleteAllItems"); + wxMenu* helpMenu = new wxMenu; helpMenu->Append(wxID_ABOUT); @@ -577,6 +585,11 @@ void MyFrame::OnExit(wxCommandEvent& WXUNUSED(event)) Close(true); } +void MyFrame::OnDeleteAllItems(wxCommandEvent& WXUNUSED(event)) +{ + m_treelist->DeleteAllItems(); +} + wxString MyFrame::DumpItem(wxTreeListItem item) const { return item.IsOk() ? m_treelist->GetItemText(item) : wxString("NONE"); diff --git a/src/generic/treelist.cpp b/src/generic/treelist.cpp index 3c6f26d955..6c599100ae 100644 --- a/src/generic/treelist.cpp +++ b/src/generic/treelist.cpp @@ -572,16 +572,12 @@ void wxTreeListModel::DeleteItem(Node* item) void wxTreeListModel::DeleteAllItems() { - // Note that this must be called before actually deleting the items as - // clearing GTK+ wxDataViewCtrl results in SELECTION_CHANGED events being - // sent and these events contain pointers to the model items, so they must - // still be valid. - Cleared(); - while ( m_root->GetChild() ) { m_root->DeleteChild(); } + + Cleared(); } const wxString& wxTreeListModel::GetItemText(Node* item, unsigned col) const diff --git a/src/gtk/dataview.cpp b/src/gtk/dataview.cpp index d78d075559..91b7621fde 100644 --- a/src/gtk/dataview.cpp +++ b/src/gtk/dataview.cpp @@ -680,7 +680,13 @@ wxgtk_tree_model_init(GTypeInstance* instance, void*) { GtkWxTreeModel* tree_model = GTK_WX_TREE_MODEL(instance); tree_model->internal = NULL; - tree_model->stamp = g_random_int(); + + // 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" @@ -746,9 +752,18 @@ static GtkTreePath * wxgtk_tree_model_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter) { - GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model; - g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), NULL); - g_return_val_if_fail (iter->stamp == GTK_WX_TREE_MODEL (wxtree_model)->stamp, NULL); + 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 ); } @@ -1887,15 +1902,25 @@ bool wxGtkDataViewModelNotifier::Cleared() // has been deleted so call row_deleted() for every // child of root... - int count = m_internal->iter_n_children( NULL ); // number of children 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()); - GtkTreePath *path = gtk_tree_path_new_first(); // points to root + // 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; - int i; - for (i = 0; i < count; i++) - gtk_tree_model_row_deleted( GTK_TREE_MODEL(wxgtk_model), path ); + { + 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 ); + } - gtk_tree_path_free( path ); + wxgtk_model->stamp = stampOrig; m_internal->Cleared(); @@ -3059,6 +3084,13 @@ static void wxGtkTreeCellDataFunc( GtkTreeViewColumn *WXUNUSED(column), 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 ); @@ -5068,7 +5100,7 @@ void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel ) { wxCHECK_RET( m_internal, "model must be associated before calling SetSelections" ); - GtkDisableSelectionEvents(); + SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); @@ -5093,8 +5125,6 @@ void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel ) iter.user_data = (gpointer) item.GetID(); gtk_tree_selection_select_iter( selection, &iter ); } - - GtkEnableSelectionEvents(); } void wxDataViewCtrl::Select( const wxDataViewItem & item ) @@ -5103,7 +5133,7 @@ void wxDataViewCtrl::Select( const wxDataViewItem & item ) ExpandAncestors(item); - GtkDisableSelectionEvents(); + SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); @@ -5111,15 +5141,13 @@ void wxDataViewCtrl::Select( const wxDataViewItem & item ) iter.stamp = m_internal->GetGtkModel()->stamp; iter.user_data = (gpointer) item.GetID(); gtk_tree_selection_select_iter( selection, &iter ); - - GtkEnableSelectionEvents(); } void wxDataViewCtrl::Unselect( const wxDataViewItem & item ) { wxCHECK_RET( m_internal, "model must be associated before calling Unselect" ); - GtkDisableSelectionEvents(); + SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); @@ -5127,8 +5155,6 @@ void wxDataViewCtrl::Unselect( const wxDataViewItem & item ) iter.stamp = m_internal->GetGtkModel()->stamp; iter.user_data = (gpointer) item.GetID(); gtk_tree_selection_unselect_iter( selection, &iter ); - - GtkEnableSelectionEvents(); } bool wxDataViewCtrl::IsSelected( const wxDataViewItem & item ) const @@ -5146,24 +5172,20 @@ bool wxDataViewCtrl::IsSelected( const wxDataViewItem & item ) const void wxDataViewCtrl::SelectAll() { - GtkDisableSelectionEvents(); + SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); gtk_tree_selection_select_all( selection ); - - GtkEnableSelectionEvents(); } void wxDataViewCtrl::UnselectAll() { - GtkDisableSelectionEvents(); + SelectionEventsSuppressor noSelection(this); GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); gtk_tree_selection_unselect_all( selection ); - - GtkEnableSelectionEvents(); } void wxDataViewCtrl::EnsureVisible(const wxDataViewItem& item, diff --git a/tests/controls/treelistctrltest.cpp b/tests/controls/treelistctrltest.cpp index 02164ab3a1..d4a3ac96d6 100644 --- a/tests/controls/treelistctrltest.cpp +++ b/tests/controls/treelistctrltest.cpp @@ -39,7 +39,6 @@ private: CPPUNIT_TEST( Traversal ); CPPUNIT_TEST( ItemText ); CPPUNIT_TEST( ItemCheck ); - CPPUNIT_TEST( DeleteAll ); CPPUNIT_TEST_SUITE_END(); // Create the control with the given style. @@ -56,7 +55,6 @@ private: void Traversal(); void ItemText(); void ItemCheck(); - void DeleteAll(); // The control itself. @@ -233,11 +231,4 @@ void TreeListCtrlTestCase::ItemCheck() m_treelist->GetCheckedState(m_code) ); } -void TreeListCtrlTestCase::DeleteAll() -{ - m_treelist->DeleteAllItems(); - - CHECK( !m_treelist->GetFirstChild(m_treelist->GetRootItem()).IsOk() ); -} - #endif // wxUSE_TREELISTCTRL