Merge branch 'dvc-virtual-has-value'

Allow overriding wxDataViewModel::HasValue() to specify which cells
should, and should not, show anything.

Closes https://github.com/wxWidgets/wxWidgets/pull/1792

Closes #18724.
This commit is contained in:
Vadim Zeitlin 2020-05-02 18:10:54 +02:00
commit e6ab2391c4
8 changed files with 258 additions and 91 deletions

View File

@ -208,7 +208,7 @@ public:
// return true if the given item has a value to display in the given
// column: this is always true except for container items which by default
// only show their label in the first column (but see HasContainerColumns())
bool HasValue(const wxDataViewItem& item, unsigned col) const
virtual bool HasValue(const wxDataViewItem& item, unsigned col) const
{
return col == 0 || !IsContainer(item) || HasContainerColumns(item);
}

View File

@ -304,14 +304,18 @@ public:
All normal items have values in all columns but the container items
only show their label in the first column (@a col == 0) by default (but
see HasContainerColumns()). So this function always returns true for
see HasContainerColumns()). So this function by default returns true for
the first column while for the other ones it returns true only if the
item is not a container or HasContainerColumns() was overridden to
return true for it.
Since wxWidgets 3.1.4, this method is virtual and can be overridden to
explicitly specify for which columns a given item has, and doesn't
have, values.
@since 2.9.1
*/
bool HasValue(const wxDataViewItem& item, unsigned col) const;
virtual bool HasValue(const wxDataViewItem& item, unsigned col) const;
/**
Override this to indicate of @a item is a container, i.e.\ if

View File

@ -170,6 +170,9 @@ private:
enum Lang { Lang_English, Lang_French };
void FillIndexList(Lang lang);
// HasValue page.
void OnHasValueValueChanged(wxDataViewEvent& event);
wxNotebook* m_notebook;
@ -182,6 +185,7 @@ private:
Page_TreeStore,
Page_VarHeight,
Page_IndexList,
Page_HasValue,
Page_Max
};
@ -713,6 +717,16 @@ MyFrame::MyFrame(wxFrame *frame, const wxString &title, int x, int y, int w, int
sixthPanelSz->Add(button_sizer6);
sixthPanel->SetSizerAndFit(sixthPanelSz);
// page showing that some columns don't have values for some items
// ---------------------------------------------------------------
wxPanel *seventhPanel = new wxPanel( m_notebook, wxID_ANY );
BuildDataViewCtrl(seventhPanel, Page_HasValue);
wxSizer *seventhPanelSz = new wxBoxSizer( wxVERTICAL );
seventhPanelSz->Add(m_ctrl[Page_HasValue], 1, wxGROW|wxALL, 5);
seventhPanel->SetSizerAndFit(seventhPanelSz);
// complete GUI
// ------------
@ -723,6 +737,7 @@ MyFrame::MyFrame(wxFrame *frame, const wxString &title, int x, int y, int w, int
m_notebook->AddPage(fourthPanel, "wxDataViewTreeCtrl");
m_notebook->AddPage(fifthPanel, "Variable line height");
m_notebook->AddPage(sixthPanel, "MyIndexListModel");
m_notebook->AddPage(seventhPanel, "MyDataViewHasValue");
wxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
@ -987,7 +1002,50 @@ void MyFrame::BuildDataViewCtrl(wxPanel* parent, unsigned int nPanel, unsigned l
this);
}
break;
case Page_HasValue:
{
wxDataViewListCtrl* lc =
new wxDataViewListCtrl( parent, wxID_ANY, wxDefaultPosition,
wxDefaultSize, style );
m_ctrl[Page_HasValue] = lc;
MyListStoreDerivedModel* page7_model = new MyListStoreHasValueModel();
lc->AssociateModel(page7_model);
page7_model->DecRef();
lc->AppendToggleColumn( "Toggle" );
// We're not limited to convenience column-appending functions, it
// can also be done fully manually, which allows us to customize
// the renderer being used.
wxDataViewToggleRenderer* const rendererRadio =
new wxDataViewToggleRenderer("bool", wxDATAVIEW_CELL_ACTIVATABLE);
rendererRadio->ShowAsRadio();
wxDataViewColumn* const colRadio =
new wxDataViewColumn("Radio", rendererRadio, 1);
lc->AppendColumn(colRadio, "bool");
lc->AppendTextColumn( "Text" );
lc->AppendProgressColumn( "Progress" )->SetMinWidth(FromDIP(100));
wxVector<wxVariant> data;
for (unsigned int i=0; i<10; i++)
{
data.clear();
data.push_back( (i%3) == 0 );
data.push_back( i == 7 ); // select a single (random) radio item
data.push_back( wxString::Format("row %d", i) );
data.push_back( long(5*i) );
lc->AppendItem( data );
}
lc->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &MyFrame::OnHasValueValueChanged, this);
}
break;
}
}
@ -1717,3 +1775,45 @@ void MyFrame::OnIndexListSelectionChanged(wxDataViewEvent& event)
wxLogMessage("Selected week day: %s", weekday);
}
// ----------------------------------------------------------------------------
// MyFrame - event handlers for the HasValue (wxDataViewListCtrl) page
// ----------------------------------------------------------------------------
void MyFrame::OnHasValueValueChanged(wxDataViewEvent& event)
{
// Ignore changes coming from our own SetToggleValue() calls below.
if ( m_eventFromProgram )
{
m_eventFromProgram = false;
return;
}
wxDataViewListCtrl* const lc = static_cast<wxDataViewListCtrl*>(m_ctrl[Page_HasValue]);
const int columnToggle = 1;
// Handle selecting a radio button by unselecting all the other ones.
if ( event.GetColumn() == columnToggle )
{
const int rowChanged = lc->ItemToRow(event.GetItem());
if ( lc->GetToggleValue(rowChanged, columnToggle) )
{
for ( int row = 0; row < lc->GetItemCount(); ++row )
{
if ( row != rowChanged )
{
m_eventFromProgram = true;
lc->SetToggleValue(false, row, columnToggle);
}
}
}
else // The item was cleared.
{
// Explicitly check it back, we want to always have exactly one
// checked radio item in this column.
m_eventFromProgram = true;
lc->SetToggleValue(true, rowChanged, columnToggle);
}
}
}

View File

@ -626,3 +626,15 @@ bool MyListStoreDerivedModel::IsEnabledByRow(unsigned int row, unsigned int col)
// disabled the last two checkboxes
return !(col == 0 && 8 <= row && row <= 9);
}
// ----------------------------------------------------------------------------
// MyListStoreHasValueModel
// ----------------------------------------------------------------------------
bool MyListStoreHasValueModel::HasValue(const wxDataViewItem &item, unsigned int col) const
{
unsigned int row = GetRow( item );
// the diagonal entries don't have values. This is just a silly example to demonstrate the
// usage of overriding HasValue to specify that some columns don't have values for some items
return row != col;
}

View File

@ -266,6 +266,16 @@ public:
virtual bool IsEnabledByRow(unsigned int row, unsigned int col) const wxOVERRIDE;
};
// ----------------------------------------------------------------------------
// MyListStoreHasValueModel
// ----------------------------------------------------------------------------
class MyListStoreHasValueModel : public MyListStoreDerivedModel
{
public:
virtual bool HasValue(const wxDataViewItem &item, unsigned int col) const wxOVERRIDE;
};
// ----------------------------------------------------------------------------
// MyIndexListModel
// ----------------------------------------------------------------------------

View File

@ -334,8 +334,13 @@ int wxDataViewModel::Compare( const wxDataViewItem &item1, const wxDataViewItem
unsigned int column, bool ascending ) const
{
wxVariant value1,value2;
GetValue( value1, item1, column );
GetValue( value2, item2, column );
// Avoid calling GetValue() for the cells that are not supposed to have any
// value, this might be unexpected.
if ( HasValue(item1, column) )
GetValue( value1, item1, column );
if ( HasValue(item2, column) )
GetValue( value2, item2, column );
if (!ascending)
{

View File

@ -910,6 +910,39 @@ private:
// assumes that all columns were modified, otherwise just this one.
bool DoItemChanged(const wxDataViewItem& item, int view_column);
// Return whether the item has at most one column with a value.
bool IsItemSingleValued(const wxDataViewItem& item) const
{
bool hadColumnWithValue = false;
const unsigned int cols = GetOwner()->GetColumnCount();
const wxDataViewModel* const model = GetModel();
for ( unsigned int i = 0; i < cols; i++ )
{
if ( model->HasValue(item, i) )
{
if ( hadColumnWithValue )
return false;
hadColumnWithValue = true;
}
}
return true;
}
// Find the first column with a value in it.
wxDataViewColumn* FindFirstColumnWithValue(const wxDataViewItem& item) const
{
const unsigned int cols = GetOwner()->GetColumnCount();
const wxDataViewModel* const model = GetModel();
for ( unsigned int i = 0; i < cols; i++ )
{
if ( model->HasValue(item, i) )
return GetOwner()->GetColumnAt(i);
}
return NULL;
}
private:
wxDataViewCtrl *m_owner;
int m_lineHeight;
@ -2396,11 +2429,11 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
{
renderColumnFocus = true;
// If this is container node without columns, render full-row focus:
// If there is just a single value, render full-row focus:
if ( !IsList() )
{
wxDataViewTreeNode *node = GetTreeNodeByRow(item);
if ( node->HasChildren() && !model->HasContainerColumns(node->GetItem()) )
if ( IsItemSingleValued(node->GetItem()) )
renderColumnFocus = false;
}
}
@ -2547,11 +2580,8 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
dataitem = node->GetItem();
// Skip all columns of "container" rows except the expander
// column itself unless HasContainerColumns() overrides this.
if ( col != expander &&
model->IsContainer(dataitem) &&
!model->HasContainerColumns(dataitem) )
// Skip al columns that do not have values
if ( !model->HasValue(dataitem, col->GetModelColumn()) )
{
cell_rect.y += line_height;
continue;
@ -3491,9 +3521,7 @@ int wxDataViewMainWindow::QueryAndCacheLineHeight(unsigned int row, wxDataViewIt
if (column->IsHidden())
continue; // skip it!
if ((col != 0) &&
model->IsContainer(item) &&
!model->HasContainerColumns(item))
if ( !model->HasValue(item, col) )
continue; // skip it!
wxDataViewRenderer *renderer =
@ -4071,20 +4099,25 @@ wxDataViewMainWindow::FindColumnForEditing(const wxDataViewItem& item, wxDataVie
wxDataViewColumn *candidate = m_currentCol;
if ( candidate &&
!IsCellEditableInMode(item, candidate, mode) &&
!m_currentColSetByKeyboard )
if ( candidate && !IsCellEditableInMode(item, candidate, mode) )
{
// If current column was set by mouse to something not editable (in
// 'mode') and the user pressed Space/F2 to edit it, treat the
// situation as if there was whole-row focus, because that's what is
// visually indicated and the mouse click could very well be targeted
// on the row rather than on an individual cell.
//
// But if it was done by keyboard, respect that even if the column
// isn't editable, because focus is visually on that column and editing
// something else would be surprising.
candidate = NULL;
if ( m_currentColSetByKeyboard )
{
// If current column was set by keyboard to something not editable (in
// 'mode') and the user pressed Space/F2 then do not edit anything
// because focus is visually on that column and editing
// something else would be surprising.
return NULL;
}
else
{
// But if the current column was set by mouse to something not editable (in
// 'mode') and the user pressed Space/F2 to edit it, treat the
// situation as if there was whole-row focus, because that's what is
// visually indicated and the mouse click could very well be targeted
// on the row rather than on an individual cell.
candidate = NULL;
}
}
if ( !candidate )
@ -4104,23 +4137,17 @@ wxDataViewMainWindow::FindColumnForEditing(const wxDataViewItem& item, wxDataVie
}
}
// If on container item without columns, only the expander column
// may be directly editable:
if ( candidate &&
GetOwner()->GetExpanderColumn() != candidate &&
GetModel()->IsContainer(item) &&
!GetModel()->HasContainerColumns(item) )
{
candidate = GetOwner()->GetExpanderColumn();
}
// Switch to the first column with value if the current column has no value
if ( candidate && !GetModel()->HasValue(item, candidate->GetModelColumn()) )
candidate = FindFirstColumnWithValue(item);
if ( !candidate )
return NULL;
return NULL;
if ( !IsCellEditableInMode(item, candidate, mode) )
return NULL;
if ( !IsCellEditableInMode(item, candidate, mode) )
return NULL;
return candidate;
return candidate;
}
bool wxDataViewMainWindow::IsCellEditableInMode(const wxDataViewItem& item,
@ -4133,6 +4160,9 @@ bool wxDataViewMainWindow::IsCellEditableInMode(const wxDataViewItem& item,
if ( !GetModel()->IsEnabled(item, col->GetModelColumn()) )
return false;
if ( !GetModel()->HasValue(item, col->GetModelColumn()) )
return false;
return true;
}
@ -4498,18 +4528,26 @@ bool wxDataViewMainWindow::TryAdvanceCurrentColumn(wxDataViewTreeNode *node, wxK
const bool wrapAround = event.GetKeyCode() == WXK_TAB;
if ( node )
{
// navigation shouldn't work in branch nodes without other columns:
if ( node->HasChildren() && !GetModel()->HasContainerColumns(node->GetItem()) )
return false;
}
// navigation shouldn't work in nodes with fewer than two columns
if ( node && IsItemSingleValued(node->GetItem()) )
return false;
if ( m_currentCol == NULL || !m_currentColSetByKeyboard )
{
if ( forward )
{
m_currentCol = GetOwner()->GetColumnAt(0);
if ( node )
{
// find first column with value
m_currentCol = FindFirstColumnWithValue(node->GetItem());
}
else
{
// in the special "list" case, all columns have values, so just
// take the first one
m_currentCol = GetOwner()->GetColumnAt(0);
}
m_currentColSetByKeyboard = true;
RefreshRow(m_currentRow);
return true;
@ -4521,41 +4559,49 @@ bool wxDataViewMainWindow::TryAdvanceCurrentColumn(wxDataViewTreeNode *node, wxK
}
}
int idx = GetOwner()->GetColumnIndex(m_currentCol) + (forward ? +1 : -1);
if ( idx >= (int)GetOwner()->GetColumnCount() )
int idx = GetOwner()->GetColumnIndex(m_currentCol);
const unsigned int cols = GetOwner()->GetColumnCount();
for ( unsigned int i = 0; i < cols; i++ )
{
if ( !wrapAround )
return false;
idx += (forward ? +1 : -1);
if ( idx >= (int)GetOwner()->GetColumnCount() )
{
if ( !wrapAround )
return false;
if ( GetCurrentRow() < GetRowCount() - 1 )
{
// go to the first column of the next row:
idx = 0;
OnVerticalNavigation(wxKeyEvent()/*dummy*/, +1);
if ( GetCurrentRow() < GetRowCount() - 1 )
{
// go to the first column of the next row:
idx = 0;
OnVerticalNavigation(wxKeyEvent()/*dummy*/, +1);
}
else
{
// allow focus change
event.Skip();
return false;
}
}
else
else if ( idx < 0 )
{
// allow focus change
event.Skip();
return false;
}
}
if ( !wrapAround )
return false;
if ( idx < 0 && wrapAround )
{
if ( GetCurrentRow() > 0 )
{
// go to the last column of the previous row:
idx = (int)GetOwner()->GetColumnCount() - 1;
OnVerticalNavigation(wxKeyEvent()/*dummy*/, -1);
}
else
{
// allow focus change
event.Skip();
return false;
if ( GetCurrentRow() > 0 )
{
// go to the last column of the previous row:
idx = (int)GetOwner()->GetColumnCount() - 1;
OnVerticalNavigation(wxKeyEvent()/*dummy*/, -1);
}
else
{
// allow focus change
event.Skip();
return false;
}
}
if ( !node || GetModel()->HasValue(node->GetItem(), i) )
break;
}
GetOwner()->EnsureVisibleRowCol(m_currentRow, idx);
@ -4767,9 +4813,8 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
}
bool ignore_other_columns =
((expander != col) &&
(model->IsContainer(item)) &&
(!model->HasContainerColumns(item)));
(expander != col) &&
(!model->HasValue(item, col->GetModelColumn()));
if (event.LeftDClick())
{
@ -6495,7 +6540,7 @@ wxAccStatus wxDataViewCtrlAccessible::GetDescription(int childId, wxString* desc
const unsigned int numCols = dvCtrl->GetColumnCount();
for ( unsigned int col = 0; col < numCols; col++ )
{
if ( model->IsContainer(item) && !model->HasContainerColumns(item) )
if ( !model->HasValue(item, col) )
continue; // skip it
wxDataViewColumn *dvCol = dvCtrl->GetColumnAt(col);

View File

@ -3146,16 +3146,7 @@ static void wxGtkTreeCellDataFunc( GtkTreeViewColumn *WXUNUSED(column),
if (!wx_model->IsVirtualListModel())
{
gboolean visible;
if (wx_model->IsContainer( item ))
{
visible = wx_model->HasContainerColumns( item ) || (column == 0);
}
else
{
visible = true;
}
gboolean visible = wx_model->HasValue(item, column);
GValue gvalue = G_VALUE_INIT;
g_value_init( &gvalue, G_TYPE_BOOLEAN );
g_value_set_boolean( &gvalue, visible );