diff --git a/include/wx/dataview.h b/include/wx/dataview.h index 99b620d84d..e796ab46c7 100644 --- a/include/wx/dataview.h +++ b/include/wx/dataview.h @@ -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); } diff --git a/interface/wx/dataview.h b/interface/wx/dataview.h index f9decd1a4c..d106567b76 100644 --- a/interface/wx/dataview.h +++ b/interface/wx/dataview.h @@ -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 diff --git a/samples/dataview/dataview.cpp b/samples/dataview/dataview.cpp index e7a00ab617..3b840491ce 100644 --- a/samples/dataview/dataview.cpp +++ b/samples/dataview/dataview.cpp @@ -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 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(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); + } + } +} diff --git a/samples/dataview/mymodels.cpp b/samples/dataview/mymodels.cpp index 9c5ee3112b..fc60e8bb4f 100644 --- a/samples/dataview/mymodels.cpp +++ b/samples/dataview/mymodels.cpp @@ -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; +} diff --git a/samples/dataview/mymodels.h b/samples/dataview/mymodels.h index 9d113d0ba7..d2a1963448 100644 --- a/samples/dataview/mymodels.h +++ b/samples/dataview/mymodels.h @@ -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 // ---------------------------------------------------------------------------- diff --git a/src/common/datavcmn.cpp b/src/common/datavcmn.cpp index 76073485e1..cc0b60c1c6 100644 --- a/src/common/datavcmn.cpp +++ b/src/common/datavcmn.cpp @@ -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) { diff --git a/src/generic/datavgen.cpp b/src/generic/datavgen.cpp index 7cf49aefa4..8f1368d17f 100644 --- a/src/generic/datavgen.cpp +++ b/src/generic/datavgen.cpp @@ -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); diff --git a/src/gtk/dataview.cpp b/src/gtk/dataview.cpp index 17c68517b5..b8ed3c8f3d 100644 --- a/src/gtk/dataview.cpp +++ b/src/gtk/dataview.cpp @@ -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 );