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:
commit
e6ab2391c4
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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 );
|
||||
|
Loading…
Reference in New Issue
Block a user