Merge branch 'dvc-add-item-error' of https://github.com/thesiv/wxWidgets

Fix adding items to collapsed nodes of wxDataViewCtrl in wxGTK too.

See #22228.
This commit is contained in:
Vadim Zeitlin
2022-03-24 21:37:15 +01:00
3 changed files with 300 additions and 91 deletions

View File

@@ -959,7 +959,13 @@ private:
// Return false only if the event was vetoed by its handler.
bool SendExpanderEvent(wxEventType type, const wxDataViewItem& item);
wxDataViewTreeNode * FindNode( const wxDataViewItem & item );
struct FindNodeResult
{
wxDataViewTreeNode * m_node;
bool m_subtreeRealized;
};
FindNodeResult FindNode( const wxDataViewItem & item );
wxDataViewColumn *FindColumnForEditing(const wxDataViewItem& item, wxDataViewCellMode mode) const;
@@ -3089,8 +3095,15 @@ bool wxDataViewMainWindow::ItemAdded(const wxDataViewItem & parent, const wxData
// specific position (row) is unclear, so clear whole height cache
ClearRowHeightCache();
wxDataViewTreeNode *parentNode = FindNode(parent);
const FindNodeResult findResult = FindNode(parent);
wxDataViewTreeNode *parentNode = findResult.m_node;
// If one of parents is not realized yet (has children but was never
// expanded). Return as nodes will be initialized in Expand().
if ( !findResult.m_subtreeRealized )
return true;
// The parent node was not found.
if ( !parentNode )
return false;
@@ -3197,7 +3210,13 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
}
else // general case
{
wxDataViewTreeNode *parentNode = FindNode(parent);
const FindNodeResult findResult = FindNode(parent);
wxDataViewTreeNode *parentNode = findResult.m_node;
// One of parents of the parent node has children but was never
// expanded, so the tree was not built and we have nothing to delete.
if ( !findResult.m_subtreeRealized )
return true;
// Notice that it is possible that the item being deleted is not in the
// tree at all, for example we could be deleting a never shown (because
@@ -3316,7 +3335,10 @@ bool wxDataViewMainWindow::DoItemChanged(const wxDataViewItem & item, int view_c
// column but also falls back to other values for comparison. To ensure
// consistency it is better to treat a value change as if it was an item
// change.
wxDataViewTreeNode* const node = FindNode(item);
const FindNodeResult findResult = FindNode(item);
wxDataViewTreeNode* const node = findResult.m_node;
if ( !findResult.m_subtreeRealized )
return true;
wxCHECK_MSG( node, false, "invalid item" );
node->PutInSortOrder(this);
}
@@ -4084,14 +4106,22 @@ void wxDataViewMainWindow::Collapse(unsigned int row)
}
}
wxDataViewTreeNode * wxDataViewMainWindow::FindNode( const wxDataViewItem & item )
wxDataViewMainWindow::FindNodeResult
wxDataViewMainWindow::FindNode( const wxDataViewItem & item )
{
FindNodeResult result;
result.m_node = NULL;
result.m_subtreeRealized = true;
const wxDataViewModel * model = GetModel();
if( model == NULL )
return NULL;
return result;
if (!item.IsOk())
return m_root;
{
result.m_node = m_root;
return result;
}
// Compose the parent-chain for the item we are looking for
wxVector<wxDataViewItem> parentChain;
@@ -4112,9 +4142,9 @@ wxDataViewTreeNode * wxDataViewMainWindow::FindNode( const wxDataViewItem & item
if( node->GetChildNodes().empty() )
{
// Even though the item is a container, it doesn't have any
// child nodes in the control's representation yet. We have
// to realize its subtree now.
::BuildTreeHelper(this, model, node->GetItem(), node);
// child nodes in the control's representation yet.
result.m_subtreeRealized = false;
return result;
}
const wxDataViewTreeNodes& nodes = node->GetChildNodes();
@@ -4126,7 +4156,10 @@ wxDataViewTreeNode * wxDataViewMainWindow::FindNode( const wxDataViewItem & item
if (currentNode->GetItem() == parentChain[iter])
{
if (currentNode->GetItem() == item)
return currentNode;
{
result.m_node = currentNode;
return result;
}
node = currentNode;
found = true;
@@ -4134,15 +4167,15 @@ wxDataViewTreeNode * wxDataViewMainWindow::FindNode( const wxDataViewItem & item
}
}
if (!found)
return NULL;
return result;
}
else
return NULL;
return result;
if ( !iter )
break;
}
return NULL;
return result;
}
void wxDataViewMainWindow::HitTest( const wxPoint & point, wxDataViewItem & item,

View File

@@ -275,11 +275,22 @@ public:
void OnInternalIdle();
enum wxFindNodeMode
{
// return NULL from FindNode() of a subtree was not realized
wxFIND_NODE_RETURN_IF_SUBTREE_NOT_REALIZED,
// call BuildBranch() from FindNode() to realize a subtree
wxFIND_NODE_BUILD_SUBTREE,
};
protected:
void InitTree();
void ScheduleRefresh();
wxGtkTreeModelNode *FindNode( const wxDataViewItem &item );
wxGtkTreeModelNode *FindNode( const wxDataViewItem &item,
wxFindNodeMode mode =
wxFIND_NODE_RETURN_IF_SUBTREE_NOT_REALIZED );
wxGtkTreeModelNode *FindNode( GtkTreeIter *iter );
wxGtkTreeModelNode *FindParentNode( const wxDataViewItem &item );
wxGtkTreeModelNode *FindParentNode( GtkTreeIter *iter );
@@ -3959,7 +3970,7 @@ bool wxDataViewCtrlInternal::ItemAdded( const wxDataViewItem &parent, const wxDa
{
if (!m_wx_model->IsVirtualListModel())
{
wxGtkTreeModelNode *parent_node = FindNode( parent );
wxGtkTreeModelNode *parent_node = FindNode( parent, wxFIND_NODE_BUILD_SUBTREE );
wxCHECK_MSG(parent_node, false,
"Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel");
@@ -3973,6 +3984,17 @@ bool wxDataViewCtrlInternal::ItemAdded( const wxDataViewItem &parent, const wxDa
const wxGtkTreeModelChildren& nodeSiblings = parent_node->GetChildren();
const int nodeSiblingsSize = nodeSiblings.size();
if ( nodeSiblingsSize == 0 )
{
BuildBranch( parent_node );
return true;
}
else
{
if ( parent_node->FindChildByItem(item) != wxNOT_FOUND )
return true;
}
int nodePos = 0;
if ( posInModel == modelSiblingsSize - 1 )
@@ -4398,10 +4420,13 @@ int wxDataViewCtrlInternal::GetIndexOf( const wxDataViewItem &parent, const wxDa
}
static wxGtkTreeModelNode*
wxDataViewCtrlInternal_FindNode( wxDataViewModel * model, wxGtkTreeModelNode *treeNode, const wxDataViewItem &item )
wxGtkTreeModelNode *wxDataViewCtrlInternal::FindNode( const wxDataViewItem &item,
wxFindNodeMode mode )
{
if( model == NULL )
if ( !item.IsOk() )
return m_root;
if( m_wx_model == NULL )
return NULL;
ItemList list;
@@ -4412,14 +4437,25 @@ wxDataViewCtrlInternal_FindNode( wxDataViewModel * model, wxGtkTreeModelNode *tr
{
wxDataViewItem * pItem = new wxDataViewItem( it );
list.Insert( pItem );
it = model->GetParent( it );
it = m_wx_model->GetParent( it );
}
wxGtkTreeModelNode * node = treeNode;
wxGtkTreeModelNode * node = m_root;
for( ItemList::compatibility_iterator n = list.GetFirst(); n; n = n->GetNext() )
{
if( node && node->GetNodes().GetCount() != 0 )
{
switch ( mode )
{
case wxFIND_NODE_RETURN_IF_SUBTREE_NOT_REALIZED:
// Do nothing and a subtree will not built.
break;
case wxFIND_NODE_BUILD_SUBTREE:
BuildBranch( node );
break;
}
int len = node->GetNodes().GetCount();
wxGtkTreeModelNodes &nodes = node->GetNodes();
int j = 0;
@@ -4450,10 +4486,9 @@ wxGtkTreeModelNode *wxDataViewCtrlInternal::FindNode( GtkTreeIter *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 );
const wxFindNodeMode mode = wxFIND_NODE_RETURN_IF_SUBTREE_NOT_REALIZED;
wxGtkTreeModelNode *result = FindNode( item, mode );
/*
if (!result)
@@ -4468,26 +4503,6 @@ wxGtkTreeModelNode *wxDataViewCtrlInternal::FindNode( GtkTreeIter *iter )
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 )
{

View File

@@ -102,6 +102,10 @@ public:
// |-- wxTEST_ITEM_CHILD
// |
// |-- wxTEST_ITEM_GRANDCHILD
// | |
// | |-- wxTEST_ITEM_LEAF
// | |
// | |-- wxTEST_ITEM_LEAF_HIDDEN
// |
// |-- wxTEST_ITEM_GRANDCHILD_HIDDEN
//
@@ -111,15 +115,19 @@ public:
wxTEST_ITEM_ROOT,
wxTEST_ITEM_CHILD,
wxTEST_ITEM_GRANDCHILD,
wxTEST_ITEM_GRANDCHILD_HIDDEN,
wxTEST_ITEM_LEAF,
wxTEST_ITEM_LEAF_HIDDEN,
wxTEST_ITEM_GRANDCHILD_HIDDEN
};
DataViewCtrlTestModel()
: m_root(wxTEST_ITEM_ROOT),
m_child(wxTEST_ITEM_CHILD),
m_grandChild(wxTEST_ITEM_GRANDCHILD),
m_grandChildHidden(wxTEST_ITEM_GRANDCHILD_HIDDEN),
m_secondGrandchildVisible(false)
m_leaf(wxTEST_ITEM_LEAF),
m_leafHidden(wxTEST_ITEM_LEAF_HIDDEN),
m_grandchildHidden(wxTEST_ITEM_GRANDCHILD_HIDDEN),
m_allItemsVisible(false)
{
}
@@ -139,8 +147,14 @@ public:
case wxTEST_ITEM_GRANDCHILD:
return wxDataViewItem(const_cast<wxTestItem*>(&m_grandChild));
case wxTEST_ITEM_LEAF:
return wxDataViewItem(const_cast<wxTestItem*>(&m_leaf));
case wxTEST_ITEM_LEAF_HIDDEN:
return wxDataViewItem(const_cast<wxTestItem*>(&m_leafHidden));
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
return wxDataViewItem(const_cast<wxTestItem*>(&m_grandChildHidden));
return wxDataViewItem(const_cast<wxTestItem*>(&m_grandchildHidden));
}
return wxDataViewItem();
}
@@ -177,6 +191,14 @@ public:
variant = "grand child";
break;
case wxTEST_ITEM_LEAF:
variant = "leaf";
break;
case wxTEST_ITEM_LEAF_HIDDEN:
variant = "initially hidden leaf";
break;
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
variant = "initially hidden";
break;
@@ -213,6 +235,10 @@ public:
case wxTEST_ITEM_GRANDCHILD:
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
return GetDataViewItem(m_child);
case wxTEST_ITEM_LEAF:
case wxTEST_ITEM_LEAF_HIDDEN:
return GetDataViewItem(m_grandChild);
}
return wxDataViewItem();
}
@@ -224,9 +250,11 @@ public:
case wxTEST_ITEM_NULL:
case wxTEST_ITEM_ROOT:
case wxTEST_ITEM_CHILD:
case wxTEST_ITEM_GRANDCHILD:
return true;
case wxTEST_ITEM_GRANDCHILD:
case wxTEST_ITEM_LEAF:
case wxTEST_ITEM_LEAF_HIDDEN:
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
return false;
}
@@ -249,15 +277,27 @@ public:
case wxTEST_ITEM_CHILD:
children.push_back(GetDataViewItem(m_grandChild));
if ( m_secondGrandchildVisible )
if ( m_allItemsVisible )
{
children.push_back(GetDataViewItem(m_grandChildHidden));
children.push_back(GetDataViewItem(m_grandchildHidden));
return 2;
}
return 1;
case wxTEST_ITEM_GRANDCHILD:
children.push_back(GetDataViewItem(m_leaf));
if ( m_allItemsVisible )
{
children.push_back(GetDataViewItem(m_leafHidden));
return 2;
}
return 1;
case wxTEST_ITEM_LEAF:
case wxTEST_ITEM_LEAF_HIDDEN:
case wxTEST_ITEM_GRANDCHILD_HIDDEN:
FAIL( "The item is not a container" );
return 0;
@@ -265,10 +305,34 @@ public:
return 0;
}
void ShowSecondGrandChild()
enum wxItemsOrder
{
m_secondGrandchildVisible = true;
ItemAdded(GetDataViewItem(m_child), GetDataViewItem(m_grandChildHidden));
wxORDER_LEAF_THEN_GRANCHILD,
wxORDER_GRANCHILD_THEN_LEAF
};
void ShowChildren(wxItemsOrder order)
{
m_allItemsVisible = true;
switch ( order )
{
case wxORDER_LEAF_THEN_GRANCHILD:
ItemAdded(GetDataViewItem(m_grandChild), GetDataViewItem(m_leafHidden));
ItemAdded(GetDataViewItem(m_child), GetDataViewItem(m_grandchildHidden));
break;
case wxORDER_GRANCHILD_THEN_LEAF:
ItemAdded(GetDataViewItem(m_child), GetDataViewItem(m_grandchildHidden));
ItemAdded(GetDataViewItem(m_grandChild), GetDataViewItem(m_leafHidden));
break;
}
}
void HideChildren()
{
m_allItemsVisible = false;
ItemDeleted(GetDataViewItem(m_grandChild), GetDataViewItem(m_leafHidden));
ItemDeleted(GetDataViewItem(m_child), GetDataViewItem(m_grandchildHidden));
}
private:
@@ -282,10 +346,12 @@ private:
wxTestItem m_root;
wxTestItem m_child;
wxTestItem m_grandChild;
wxTestItem m_grandChildHidden;
wxTestItem m_leaf;
wxTestItem m_leafHidden;
wxTestItem m_grandchildHidden;
// Whether wxTEST_ITEM_GRANDCHILD_HIDDEN item should be visible or not.
bool m_secondGrandchildVisible;
bool m_allItemsVisible;
};
@@ -296,6 +362,51 @@ public:
~DataViewCtrlWithCustomModelTestCase();
protected:
enum wxItemExistence
{
wxITEM_APPEAR,
wxITEM_DISAPPEAR
};
void UpdateAndWaitForItem(const wxDataViewItem& item, wxItemExistence existence)
{
m_dvc->Refresh();
m_dvc->Update();
#ifdef __WXGTK__
// Unfortunately it's not enough to call wxYield() once, so wait up to
// 0.5 sec.
wxStopWatch sw;
while ( true )
{
wxYield();
const bool isItemRectEmpty = m_dvc->GetItemRect(item).IsEmpty();
switch ( existence )
{
case wxITEM_APPEAR:
if ( !isItemRectEmpty )
return;
break;
case wxITEM_DISAPPEAR:
if ( isItemRectEmpty )
return;
break;
}
if ( sw.Time() > 500 )
{
WARN("Timed out waiting for wxDataViewCtrl");
break;
}
}
#else // !__WXGTK__
wxUnusedVar(item);
wxUnusedVar(existence);
#endif // __WXGTK__
}
// The dataview control.
wxDataViewCtrl *m_dvc;
@@ -303,7 +414,12 @@ protected:
DataViewCtrlTestModel *m_model;
// Its items.
wxDataViewItem m_root, m_child, m_grandchild, m_grandchildHidden;
wxDataViewItem m_root,
m_child,
m_grandchild,
m_leaf,
m_leafHidden,
m_grandchildHidden;
wxDECLARE_NO_COPY_CLASS(DataViewCtrlWithCustomModelTestCase);
};
@@ -384,6 +500,10 @@ DataViewCtrlWithCustomModelTestCase::DataViewCtrlWithCustomModelTestCase()
m_child = m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_CHILD);
m_grandchild =
m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_GRANDCHILD);
m_leaf =
m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_LEAF);
m_leafHidden =
m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_LEAF_HIDDEN);
m_grandchildHidden =
m_model->GetDataViewItem(DataViewCtrlTestModel::wxTEST_ITEM_GRANDCHILD_HIDDEN);
@@ -566,53 +686,94 @@ TEST_CASE_METHOD(DataViewCtrlWithCustomModelTestCase,
wxYield();
#endif // __WXGTK__
SECTION( "Was Expanded" )
// Unfortunately we can't combine test options with SECTION() so use
// the additional enum variable.
enum
{
CHECK( m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
wxOPTIONS_EXPAND_ADD_LEAF_THEN_GRANCHILD,
wxOPTIONS_DONT_EXPAND_ADD_LEAF_THEN_GRANCHILD,
wxOPTIONS_EXPAND_ADD_GRANCHILD_THEN_LEAF,
wxOPTIONS_DONT_EXPAND_ADD_GRANCHILD_THEN_LEAF
} options;
m_dvc->Expand(m_child);
#ifdef __WXGTK__
wxYield();
#endif // __WXGTK__
CHECK( !m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
m_dvc->Collapse(m_child);
SECTION( "Was Expanded, Add The Leaf Then The Grandchild" )
{
options = wxOPTIONS_EXPAND_ADD_LEAF_THEN_GRANCHILD;
}
SECTION( "Was Not Expanded" )
SECTION( "Was Not Expanded, Add The Leaf Then The Grandchild" )
{
// Do nothing.
options = wxOPTIONS_DONT_EXPAND_ADD_LEAF_THEN_GRANCHILD;
}
SECTION( "Was Expanded, Add The Grandchild Then The Leaf" )
{
options = wxOPTIONS_EXPAND_ADD_GRANCHILD_THEN_LEAF;
}
SECTION( "Was Not Expanded, Add The Grandchild Then The Leaf" )
{
options = wxOPTIONS_DONT_EXPAND_ADD_GRANCHILD_THEN_LEAF;
}
switch ( options )
{
case wxOPTIONS_EXPAND_ADD_LEAF_THEN_GRANCHILD:
case wxOPTIONS_EXPAND_ADD_GRANCHILD_THEN_LEAF:
CHECK( m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_leafHidden).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
m_dvc->Expand(m_child);
m_dvc->Expand(m_grandchild);
UpdateAndWaitForItem(m_grandchild, wxITEM_APPEAR);
CHECK( !m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( !m_dvc->GetItemRect(m_leaf).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_leafHidden).IsEmpty() );
CHECK( m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
m_dvc->Collapse(m_grandchild);
m_dvc->Collapse(m_child);
break;
case wxOPTIONS_DONT_EXPAND_ADD_LEAF_THEN_GRANCHILD:
case wxOPTIONS_DONT_EXPAND_ADD_GRANCHILD_THEN_LEAF:
// Do nothing.
break;
}
// Check wxDataViewModel::ItemAdded().
m_model->ShowSecondGrandChild();
switch ( options )
{
case wxOPTIONS_EXPAND_ADD_LEAF_THEN_GRANCHILD:
case wxOPTIONS_DONT_EXPAND_ADD_LEAF_THEN_GRANCHILD:
m_model->ShowChildren(DataViewCtrlTestModel::wxORDER_LEAF_THEN_GRANCHILD);
break;
case wxOPTIONS_EXPAND_ADD_GRANCHILD_THEN_LEAF:
case wxOPTIONS_DONT_EXPAND_ADD_GRANCHILD_THEN_LEAF:
m_model->ShowChildren(DataViewCtrlTestModel::wxORDER_GRANCHILD_THEN_LEAF);
break;
}
m_dvc->Expand(m_child);
m_dvc->Refresh();
m_dvc->Update();
m_dvc->Expand(m_grandchild);
UpdateAndWaitForItem(m_leaf, wxITEM_APPEAR);
CHECK( m_dvc->IsExpanded(m_child) );
#ifdef __WXGTK__
// Unfortunately it's not enough to call wxYield() once, so wait up to
// 0.5 sec.
wxStopWatch sw;
while ( m_dvc->GetItemRect(m_grandchild).IsEmpty() )
{
if ( sw.Time() > 500 )
{
WARN("Timed out waiting for wxDataViewCtrl");
break;
}
wxYield();
}
#endif // __WXGTK__
CHECK( m_dvc->IsExpanded(m_grandchild) );
CHECK( !m_dvc->GetItemRect(m_grandchild).IsEmpty() );
CHECK( !m_dvc->GetItemRect(m_leaf).IsEmpty() );
CHECK( !m_dvc->GetItemRect(m_leafHidden).IsEmpty() );
CHECK( !m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
m_model->HideChildren();
UpdateAndWaitForItem(m_leafHidden, wxITEM_DISAPPEAR);
CHECK( m_dvc->GetItemRect(m_leafHidden).IsEmpty() );
// Check that the problem with nodes duplication in ItemAdded() fixed.
CHECK( m_dvc->GetItemRect(m_grandchildHidden).IsEmpty() );
}
TEST_CASE_METHOD(SingleSelectDataViewCtrlTestCase,