diff --git a/docs/changes.txt b/docs/changes.txt index c673d9046b..9aa667e0be 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -411,6 +411,7 @@ MSW: All (GUI): - wxAUI: support auto-orientable toolbars (wsu). +- Added wxDataViewCtrl::Set/GetCurrentItem(). - wxHTML: render in RTL order inside RTL window (Richard Bullington-McGuire). - wxRibbon: added EVT_RIBBONGALLERY_CLICKED event (John Roberts). - Add support for CP-866 encoding to wxEncodingConverter (madnut). diff --git a/include/wx/dataview.h b/include/wx/dataview.h index 86218f0d08..2e324dc752 100644 --- a/include/wx/dataview.h +++ b/include/wx/dataview.h @@ -642,6 +642,12 @@ public: int GetIndent() const { return m_indent; } + // Current item is the one used by the keyboard navigation, it is the same + // as the (unique) selected item in single selection mode so these + // functions are mostly useful for controls with wxDV_MULTIPLE style. + wxDataViewItem GetCurrentItem() const; + void SetCurrentItem(const wxDataViewItem& item); + virtual wxDataViewItem GetSelection() const = 0; virtual int GetSelections( wxDataViewItemArray & sel ) const = 0; virtual void SetSelections( const wxDataViewItemArray & sel ) = 0; @@ -688,6 +694,12 @@ protected: virtual void DoSetIndent() = 0; private: + // Implementation of the public Set/GetCurrentItem() methods which are only + // called in multi selection case (for single selection controls their + // implementation is trivial and is done in the base class itself). + virtual wxDataViewItem DoGetCurrentItem() const = 0; + virtual void DoSetCurrentItem(const wxDataViewItem& item) = 0; + wxDataViewModel *m_model; wxDataViewColumn *m_expander_column; int m_indent ; diff --git a/include/wx/generic/dataview.h b/include/wx/generic/dataview.h index 9f5dc741f1..a0e1cf369c 100644 --- a/include/wx/generic/dataview.h +++ b/include/wx/generic/dataview.h @@ -229,6 +229,9 @@ public: // utility functions not part of the API wxDataViewColumn *GetColumnAt(unsigned int pos) const; private: + virtual wxDataViewItem DoGetCurrentItem() const; + virtual void DoSetCurrentItem(const wxDataViewItem& item); + wxDataViewColumnList m_cols; wxDataViewModelNotifier *m_notifier; wxDataViewMainWindow *m_clientArea; diff --git a/include/wx/gtk/dataview.h b/include/wx/gtk/dataview.h index 2c22beb144..06649c507f 100644 --- a/include/wx/gtk/dataview.h +++ b/include/wx/gtk/dataview.h @@ -190,6 +190,9 @@ protected: private: void Init(); + virtual wxDataViewItem DoGetCurrentItem() const; + virtual void DoSetCurrentItem(const wxDataViewItem& item); + friend class wxDataViewCtrlDCImpl; friend class wxDataViewColumn; friend class wxGtkDataViewModelNotifier; diff --git a/include/wx/osx/carbon/dataview.h b/include/wx/osx/carbon/dataview.h index 501c5c95eb..f4d8f93fd6 100644 --- a/include/wx/osx/carbon/dataview.h +++ b/include/wx/osx/carbon/dataview.h @@ -403,6 +403,8 @@ public: // // selection related methods (inherited from wxDataViewWidgetImpl) // + virtual wxDataViewItem GetCurrentItem() const; + virtual void SetCurrentItem(const wxDataViewItem& item); virtual int GetSelections(wxDataViewItemArray& sel) const; virtual bool IsSelected (wxDataViewItem const& item) const; virtual void Select (wxDataViewItem const& item); diff --git a/include/wx/osx/cocoa/dataview.h b/include/wx/osx/cocoa/dataview.h index 09fa842aa7..6e97eb87ed 100644 --- a/include/wx/osx/cocoa/dataview.h +++ b/include/wx/osx/cocoa/dataview.h @@ -471,6 +471,8 @@ public: // // selection related methods (inherited from wxDataViewWidgetImpl) // + virtual wxDataViewItem GetCurrentItem() const; + virtual void SetCurrentItem(const wxDataViewItem& item); virtual int GetSelections(wxDataViewItemArray& sel) const; virtual bool IsSelected(const wxDataViewItem& item) const; virtual void Select(const wxDataViewItem& item); diff --git a/include/wx/osx/core/dataview.h b/include/wx/osx/core/dataview.h index c8af8cd05c..a35f7324d5 100644 --- a/include/wx/osx/core/dataview.h +++ b/include/wx/osx/core/dataview.h @@ -85,6 +85,9 @@ public: // // selection related methods // + virtual wxDataViewItem GetCurrentItem() const = 0; + virtual void SetCurrentItem(const wxDataViewItem& item) = 0; + virtual int GetSelections(wxDataViewItemArray& sel) const = 0; // returns all selected items in the native control virtual bool IsSelected (wxDataViewItem const& item) const = 0; // checks if the passed item is selected in the native control virtual void Select (wxDataViewItem const& item) = 0; // selects the passed item in the native control diff --git a/include/wx/osx/dataview.h b/include/wx/osx/dataview.h index cd97cd038c..34e114a5a2 100644 --- a/include/wx/osx/dataview.h +++ b/include/wx/osx/dataview.h @@ -174,7 +174,6 @@ public: virtual void Expand(const wxDataViewItem& item); virtual bool IsExpanded(const wxDataViewItem & item) const; - virtual unsigned int GetCount() const; virtual wxRect GetItemRect(const wxDataViewItem& item, const wxDataViewColumn* columnPtr) const; virtual wxDataViewItem GetSelection() const; @@ -279,6 +278,9 @@ private: // initializing of local variables: void Init(); + virtual wxDataViewItem DoGetCurrentItem() const; + virtual void DoSetCurrentItem(const wxDataViewItem& item); + // // variables // diff --git a/interface/wx/dataview.h b/interface/wx/dataview.h index 46a623fdb1..f1f7edc84c 100644 --- a/interface/wx/dataview.h +++ b/interface/wx/dataview.h @@ -913,6 +913,26 @@ public: */ wxDataViewColumn* GetExpanderColumn() const; + /** + Returns the currently focused item. + + This is the item that the keyboard commands apply to. It may be invalid + if there is no focus currently. + + This method is mostly useful for the controls with @c wxDV_MULTIPLE + style as in the case of single selection it returns the same thing as + GetSelection(). + + Notice that under all platforms except Mac OS X the currently focused + item may be selected or not but under OS X the current item is always + selected. + + @see SetCurrentItem() + + @since 2.9.2 + */ + wxDataViewItem GetCurrentItem() const; + /** Returns indentation. */ @@ -980,6 +1000,25 @@ public: */ void SetExpanderColumn(wxDataViewColumn* col); + /** + Changes the currently focused item. + + The @a item parameter must be valid, there is no way to remove the + current item from the control. + + In single selection mode, calling this method is the same as calling + Select() and is thus not very useful. In multiple selection mode this + method only moves the current item however without changing the + selection except under OS X where the current item is always selected, + so calling SetCurrentItem() selects @a item if it hadn't been selected + before. + + @see GetCurrentItem() + + @since 2.9.2 + */ + void SetCurrentItem(const wxDataViewItem& item); + /** Sets the indendation. */ diff --git a/samples/dataview/dataview.cpp b/samples/dataview/dataview.cpp index e1d7d70a48..eb750bd33a 100644 --- a/samples/dataview/dataview.cpp +++ b/samples/dataview/dataview.cpp @@ -88,6 +88,8 @@ private: void OnSelectNinth(wxCommandEvent& event); void OnCollapse(wxCommandEvent& event); void OnExpand(wxCommandEvent& event); + void OnShowCurrent(wxCommandEvent& event); + void OnSetNinthCurrent(wxCommandEvent& event); void OnPrependList(wxCommandEvent& event); void OnDeleteList(wxCommandEvent& event); @@ -283,6 +285,8 @@ enum ID_SELECT_NINTH = 103, ID_COLLAPSE = 104, ID_EXPAND = 105, + ID_SHOW_CURRENT, + ID_SET_NINTH_CURRENT, ID_PREPEND_LIST = 200, ID_DELETE_LIST = 201, @@ -315,6 +319,8 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_BUTTON( ID_SELECT_NINTH, MyFrame::OnSelectNinth ) EVT_BUTTON( ID_COLLAPSE, MyFrame::OnCollapse ) EVT_BUTTON( ID_EXPAND, MyFrame::OnExpand ) + EVT_BUTTON( ID_SHOW_CURRENT, MyFrame::OnShowCurrent ) + EVT_BUTTON( ID_SET_NINTH_CURRENT, MyFrame::OnSetNinthCurrent ) EVT_BUTTON( ID_PREPEND_LIST, MyFrame::OnPrependList ) EVT_BUTTON( ID_DELETE_LIST, MyFrame::OnDeleteList ) @@ -411,13 +417,21 @@ MyFrame::MyFrame(wxFrame *frame, const wxString &title, int x, int y, int w, int BuildDataViewCtrl(firstPanel, 0); // sets m_ctrl[0] + const wxSizerFlags border = wxSizerFlags().DoubleBorder(); + wxBoxSizer *button_sizer = new wxBoxSizer( wxHORIZONTAL ); - button_sizer->Add( new wxButton( firstPanel, ID_ADD_MOZART, "Add Mozart"), 0, wxALL, 10 ); - button_sizer->Add( new wxButton( firstPanel, ID_DELETE_SEL, "Delete selected"), 0, wxALL, 10 ); - button_sizer->Add( new wxButton( firstPanel, ID_DELETE_YEAR, "Delete \"Year\" column"), 0, wxALL, 10 ); - button_sizer->Add( new wxButton( firstPanel, ID_SELECT_NINTH,"Select ninth symphony"), 0, wxALL, 10 ); - button_sizer->Add( new wxButton( firstPanel, ID_COLLAPSE, "Collapse"), 0, wxALL, 10 ); - button_sizer->Add( new wxButton( firstPanel, ID_EXPAND, "Expand"), 0, wxALL, 10 ); + button_sizer->Add( new wxButton( firstPanel, ID_ADD_MOZART, "Add Mozart"), border ); + button_sizer->Add( new wxButton( firstPanel, ID_DELETE_SEL, "Delete selected"), border ); + button_sizer->Add( new wxButton( firstPanel, ID_DELETE_YEAR, "Delete \"Year\" column"), border ); + button_sizer->Add( new wxButton( firstPanel, ID_SELECT_NINTH,"Select ninth symphony"), border ); + button_sizer->Add( new wxButton( firstPanel, ID_COLLAPSE, "Collapse"), border ); + button_sizer->Add( new wxButton( firstPanel, ID_EXPAND, "Expand"), border ); + + wxBoxSizer *sizerCurrent = new wxBoxSizer(wxHORIZONTAL); + sizerCurrent->Add(new wxButton(firstPanel, ID_SHOW_CURRENT, + "&Show current"), border); + sizerCurrent->Add(new wxButton(firstPanel, ID_SET_NINTH_CURRENT, + "Make &ninth symphony current"), border); wxSizer *firstPanelSz = new wxBoxSizer( wxVERTICAL ); m_ctrl[0]->SetMinSize(wxSize(-1, 200)); @@ -426,6 +440,7 @@ MyFrame::MyFrame(wxFrame *frame, const wxString &title, int x, int y, int w, int new wxStaticText(firstPanel, wxID_ANY, "Most of the cells above are editable!"), 0, wxGROW|wxALL, 5); firstPanelSz->Add(button_sizer); + firstPanelSz->Add(sizerCurrent); firstPanel->SetSizerAndFit(firstPanelSz); @@ -914,6 +929,33 @@ void MyFrame::OnExpand( wxCommandEvent& WXUNUSED(event) ) m_ctrl[0]->Expand( item ); } +void MyFrame::OnShowCurrent(wxCommandEvent& WXUNUSED(event)) +{ + wxDataViewItem item = m_ctrl[0]->GetCurrentItem(); + if ( item.IsOk() ) + { + wxLogMessage("Current item: \"%s\" by %s", + m_music_model->GetTitle(item), + m_music_model->GetArtist(item)); + } + else + { + wxLogMessage("There is no current item."); + } +} + +void MyFrame::OnSetNinthCurrent(wxCommandEvent& WXUNUSED(event)) +{ + wxDataViewItem item(m_music_model->GetNinthItem()); + if ( !item.IsOk() ) + { + wxLogError( "Cannot make the ninth symphony current: it was removed!" ); + return; + } + + m_ctrl[0]->SetCurrentItem(item); +} + void MyFrame::OnValueChanged( wxDataViewEvent &event ) { if (!m_log) diff --git a/src/common/datavcmn.cpp b/src/common/datavcmn.cpp index b63171d118..779df4727a 100644 --- a/src/common/datavcmn.cpp +++ b/src/common/datavcmn.cpp @@ -994,6 +994,22 @@ void wxDataViewCtrlBase::ExpandAncestors( const wxDataViewItem & item ) } } +wxDataViewItem wxDataViewCtrlBase::GetCurrentItem() const +{ + return HasFlag(wxDV_MULTIPLE) ? DoGetCurrentItem() + : GetSelection(); +} + +void wxDataViewCtrlBase::SetCurrentItem(const wxDataViewItem& item) +{ + wxCHECK_RET( item.IsOk(), "Can't make current an invalid item." ); + + if ( HasFlag(wxDV_MULTIPLE) ) + DoSetCurrentItem(item); + else + Select(item); +} + wxDataViewColumn * wxDataViewCtrlBase::AppendTextColumn( const wxString &label, unsigned int model_column, wxDataViewCellMode mode, int width, wxAlignment align, int flags ) diff --git a/src/generic/datavgen.cpp b/src/generic/datavgen.cpp index b80eeaf622..db9fe4be06 100644 --- a/src/generic/datavgen.cpp +++ b/src/generic/datavgen.cpp @@ -471,6 +471,7 @@ public: void ScrollWindow( int dx, int dy, const wxRect *rect = NULL ); void ScrollTo( int rows, int column ); + unsigned GetCurrentRow() const { return m_currentRow; } bool HasCurrentRow() { return m_currentRow != (unsigned int)-1; } void ChangeCurrentRow( unsigned int row ); @@ -4174,6 +4175,24 @@ wxDataViewColumn *wxDataViewCtrl::GetSortingColumn() const : GetColumn(m_sortingColumnIdx); } +wxDataViewItem wxDataViewCtrl::DoGetCurrentItem() const +{ + return GetItemByRow(m_clientArea->GetCurrentRow()); +} + +void wxDataViewCtrl::DoSetCurrentItem(const wxDataViewItem& item) +{ + const int row = m_clientArea->GetRowByItem(item); + + const unsigned oldCurrent = m_clientArea->GetCurrentRow(); + if ( static_cast(row) != oldCurrent ) + { + m_clientArea->ChangeCurrentRow(row); + m_clientArea->RefreshRow(oldCurrent); + m_clientArea->RefreshRow(row); + } +} + // Selection code with wxDataViewItem as parameters wxDataViewItem wxDataViewCtrl::GetSelection() const { diff --git a/src/gtk/dataview.cpp b/src/gtk/dataview.cpp index 1763e1881e..e3ae840c43 100644 --- a/src/gtk/dataview.cpp +++ b/src/gtk/dataview.cpp @@ -96,6 +96,97 @@ private: wxDECLARE_NO_COPY_CLASS(wxGtkTreePath); }; +// ---------------------------------------------------------------------------- +// wxGtkTreeSelectionLock: prevent selection from changing during the +// lifetime of this object +// ---------------------------------------------------------------------------- + +// Implementation note: it could be expected that setting the selection +// function in this class ctor and resetting it back to the old value in its +// dtor would work. However currently gtk_tree_selection_get_select_function() +// can't be passed NULL (see https://bugzilla.gnome.org/show_bug.cgi?id=626276) +// so we can't do this. Instead, we always use the selection function (which +// imposes extra overhead, albeit minimal one, on all selection operations) and +// just set/reset the flag telling it whether it should allow or forbid the +// selection. +// +// Also notice that currently only a single object of this class may exist at +// any given moment. It's just simpler like this and we don't need anything +// more for now. + +extern "C" +gboolean wxdataview_selection_func(GtkTreeSelection * WXUNUSED(selection), + GtkTreeModel * WXUNUSED(model), + GtkTreePath * WXUNUSED(path), + gboolean WXUNUSED(path_currently_selected), + gpointer data) +{ + return data == NULL; +} + +class wxGtkTreeSelectionLock +{ +public: + wxGtkTreeSelectionLock(GtkTreeSelection *selection) + : m_selection(selection) + { + wxASSERT_MSG( !ms_instance, "this class is not reentrant currently" ); + + ms_instance = this; + + CheckCurrentSelectionFunc(NULL); + + // Pass some non-NULL pointer as "data" for the callback, it doesn't + // matter what it is as long as it's non-NULL. + gtk_tree_selection_set_select_function(selection, + wxdataview_selection_func, + this, + NULL); + } + + ~wxGtkTreeSelectionLock() + { + CheckCurrentSelectionFunc(wxdataview_selection_func); + + gtk_tree_selection_set_select_function(m_selection, + wxdataview_selection_func, + NULL, + NULL); + + ms_instance = NULL; + } + +private: + void CheckCurrentSelectionFunc(GtkTreeSelectionFunc func) + { + // We can only use gtk_tree_selection_get_select_function() with 2.14+ + // so check for its availability both during compile- and run-time. +#if GTK_CHECK_VERSION(2, 14, 0) + if ( gtk_check_version(2, 14, 0) != NULL ) + return; + + // If this assert is triggered, it means the code elsewhere has called + // gtk_tree_selection_set_select_function() but currently doing this + // breaks this class so the code here needs to be changed. + wxASSERT_MSG + ( + gtk_tree_selection_get_select_function(m_selection) == func, + "selection function has changed unexpectedly, review this code!" + ); +#endif // GTK+ 2.14+ + + wxUnusedVar(func); + } + + static wxGtkTreeSelectionLock *ms_instance; + + GtkTreeSelection * const m_selection; + + wxDECLARE_NO_COPY_CLASS(wxGtkTreeSelectionLock); +}; + +wxGtkTreeSelectionLock *wxGtkTreeSelectionLock::ms_instance = NULL; + //----------------------------------------------------------------------------- // wxDataViewCtrlInternal //----------------------------------------------------------------------------- @@ -4571,6 +4662,43 @@ bool wxDataViewCtrl::IsExpanded( const wxDataViewItem & item ) const return gtk_tree_view_row_expanded( GTK_TREE_VIEW(m_treeview), path ); } +wxDataViewItem wxDataViewCtrl::DoGetCurrentItem() const +{ + // The tree doesn't have any current item if it hadn't been created yet but + // it's arguably not an error to call this function in this case so just + // return an invalid item without asserting. + if ( !m_treeview ) + return wxDataViewItem(); + + wxGtkTreePath path; + gtk_tree_view_get_cursor(GTK_TREE_VIEW(m_treeview), path.ByRef(), NULL); + + return GTKPathToItem(path); +} + +void wxDataViewCtrl::DoSetCurrentItem(const wxDataViewItem& item) +{ + wxCHECK_RET( m_treeview, + "Current item can't be set before creating the control." ); + + // We need to make sure the model knows about this item or the path would + // be invalid and gtk_tree_view_set_cursor() would silently do nothing. + ExpandAncestors(item); + + // We also need to preserve the existing selection from changing. + // Unfortunately the only way to do it seems to use our own selection + // function and forbid any selection changes during set cursor call. + wxGtkTreeSelectionLock + lock(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview))); + + // Do move the cursor now. + GtkTreeIter iter; + iter.user_data = item.GetID(); + wxGtkTreePath path(m_internal->get_path( &iter )); + + gtk_tree_view_set_cursor(GTK_TREE_VIEW(m_treeview), path, NULL, FALSE); +} + wxDataViewItem wxDataViewCtrl::GetSelection() const { GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) ); diff --git a/src/osx/carbon/dataview.cpp b/src/osx/carbon/dataview.cpp index ef441c892d..48cb63956e 100644 --- a/src/osx/carbon/dataview.cpp +++ b/src/osx/carbon/dataview.cpp @@ -1095,6 +1095,18 @@ bool wxMacDataViewDataBrowserListViewControl::AssociateModel(wxDataViewModel* WX // // selection related methods (inherited from wxDataViewWidgetImpl) // +wxDataViewItem wxMacDataViewDataBrowserListViewControl::GetCurrentItem() const +{ + wxFAIL_MSG( "unimplemented for Carbon" ); + + return wxDataViewItem(); +} + +void wxMacDataViewDataBrowserListViewControl::SetCurrentItem(const wxDataViewItem& WXUNUSED(item)) +{ + wxFAIL_MSG( "unimplemented for Carbon" ); +} + int wxMacDataViewDataBrowserListViewControl::GetSelections(wxDataViewItemArray& sel) const { size_t noOfSelectedItems; diff --git a/src/osx/cocoa/dataview.mm b/src/osx/cocoa/dataview.mm index f8f8c38b02..87d16daae7 100644 --- a/src/osx/cocoa/dataview.mm +++ b/src/osx/cocoa/dataview.mm @@ -2069,6 +2069,22 @@ bool wxCocoaDataViewControl::AssociateModel(wxDataViewModel* model) // // selection related methods (inherited from wxDataViewWidgetImpl) // + +wxDataViewItem wxCocoaDataViewControl::GetCurrentItem() const +{ + return wxDataViewItem([[m_OutlineView itemAtRow:[m_OutlineView selectedRow]] pointer]); +} + +void wxCocoaDataViewControl::SetCurrentItem(const wxDataViewItem& item) +{ + // We can't have unselected current item in a NSTableView, as the + // documentation of its deselectRow method explains, the control will + // automatically change the current item to the closest still selected item + // if the current item is deselected. So we have no choice but to select + // the item in the same time as making it current. + Select(item); +} + int wxCocoaDataViewControl::GetSelections(wxDataViewItemArray& sel) const { NSIndexSet* selectedRowIndexes([m_OutlineView selectedRowIndexes]); diff --git a/src/osx/dataview_osx.cpp b/src/osx/dataview_osx.cpp index 41d7ca3260..45e2b3ca35 100644 --- a/src/osx/dataview_osx.cpp +++ b/src/osx/dataview_osx.cpp @@ -496,6 +496,16 @@ unsigned int wxDataViewCtrl::GetCount() const return GetDataViewPeer()->GetCount(); } +wxDataViewItem wxDataViewCtrl::DoGetCurrentItem() const +{ + return GetDataViewPeer()->GetCurrentItem(); +} + +void wxDataViewCtrl::DoSetCurrentItem(const wxDataViewItem& item) +{ + GetDataViewPeer()->SetCurrentItem(item); +} + wxRect wxDataViewCtrl::GetItemRect(wxDataViewItem const& item, wxDataViewColumn const* columnPtr) const { if (item.IsOk() && (columnPtr != NULL))