diff --git a/include/wx/treelist.h b/include/wx/treelist.h index 9b315c57de..1b33b5abcc 100644 --- a/include/wx/treelist.h +++ b/include/wx/treelist.h @@ -28,6 +28,7 @@ class WXDLLIMPEXP_FWD_ADV wxDataViewEvent; extern WXDLLIMPEXP_DATA_CORE(const char) wxTreeListCtrlNameStr[]; +class wxTreeListCtrl; class wxTreeListModel; class wxTreeListModelNode; @@ -77,6 +78,33 @@ typedef wxVector wxTreeListItems; extern WXDLLIMPEXP_DATA_ADV(const wxTreeListItem) wxTLI_FIRST; extern WXDLLIMPEXP_DATA_ADV(const wxTreeListItem) wxTLI_LAST; +// ---------------------------------------------------------------------------- +// wxTreeListItemComparator: defines order of wxTreeListCtrl items. +// ---------------------------------------------------------------------------- + +class wxTreeListItemComparator +{ +public: + wxTreeListItemComparator() { } + + // The comparison function should return negative, null or positive value + // depending on whether the first item is less than, equal to or greater + // than the second one. The items should be compared using their values for + // the given column. + virtual int + Compare(wxTreeListCtrl* treelist, + unsigned column, + wxTreeListItem first, + wxTreeListItem second) = 0; + + // Although this class is not used polymorphically by wxWidgets itself, + // provide virtual dtor in case it's used like this in the user code. + virtual ~wxTreeListItemComparator() { } + +private: + wxDECLARE_NO_COPY_CLASS(wxTreeListItemComparator); +}; + // ---------------------------------------------------------------------------- // wxTreeListCtrl: a control combining wxTree- and wxListCtrl features. // ---------------------------------------------------------------------------- @@ -323,6 +351,34 @@ public: + // Sorting. + // -------- + + // Sort by the given column, either in ascending (default) or descending + // sort order. + // + // By default, simple alphabetical sorting is done by this column contents + // but SetItemComparator() may be called to perform comparison in some + // other way. + void SetSortColumn(unsigned col, bool ascendingOrder = true); + + // If the control contents is sorted, return true and fill the output + // parameters with the column which is currently used for sorting and + // whether we sort using ascending or descending order. Otherwise, i.e. if + // the control contents is unsorted, simply return false. + bool GetSortColumn(unsigned* col, bool* ascendingOrder = NULL); + + // Set the object to use for comparing the items. It will be called when + // the control is being sorted because the user clicked on a sortable + // column. + // + // The provided pointer is stored by the control so the object it points to + // must have a life-time equal or greater to that of the control itself. In + // addition, the pointer can be NULL to stop using custom comparator and + // revert to the default alphabetical comparison. + void SetItemComparator(wxTreeListItemComparator* comparator); + + // View window functions. // ---------------------- @@ -356,10 +412,14 @@ private: int imageOpened, wxClientData* data); - // Send wxTreeListEvent corresponding to the given wxDataViewEvent. + // Send wxTreeListEvent corresponding to the given wxDataViewEvent for an + // item (as opposed for column-oriented events). // // Also updates the original event "skipped" and "vetoed" flags. - void SendEvent(wxEventType evt, wxDataViewEvent& event); + void SendItemEvent(wxEventType evt, wxDataViewEvent& event); + + // Send wxTreeListEvent corresponding to the given column wxDataViewEvent. + void SendColumnEvent(wxEventType evt, wxDataViewEvent& event); // Called by wxTreeListModel when an item is toggled by the user. @@ -371,6 +431,7 @@ private: void OnItemExpanded(wxDataViewEvent& event); void OnItemActivated(wxDataViewEvent& event); void OnItemContextMenu(wxDataViewEvent& event); + void OnColumnSorted(wxDataViewEvent& event); void OnSize(wxSizeEvent& event); wxDECLARE_EVENT_TABLE(); @@ -379,6 +440,8 @@ private: wxDataViewCtrl* m_view; wxTreeListModel* m_model; + wxTreeListItemComparator* m_comparator; + // It calls our inherited protected wxWithImages::GetImage() method. friend class wxTreeListModel; @@ -393,12 +456,16 @@ private: class wxTreeListEvent : public wxNotifyEvent { public: - // The item affected by the event. + // The item affected by the event. Valid for all events except + // column-specific ones such as COLUMN_SORTED. wxTreeListItem GetItem() const { return m_item; } // The previous state of the item checkbox for ITEM_CHECKED events only. wxCheckBoxState GetOldCheckedState() const { return m_oldCheckedState; } + // The index of the column affected by the event. Currently only used by + // COLUMN_SORTED event. + unsigned GetColumn() const { return m_column; } virtual wxEvent* Clone() const { return new wxTreeListEvent(*this); } @@ -411,6 +478,10 @@ private: m_item(item) { SetEventObject(treelist); + + m_column = static_cast(-1); + + m_oldCheckedState = wxCHK_UNDETERMINED; } // Set the checkbox state before this event for ITEM_CHECKED events. @@ -419,11 +490,19 @@ private: m_oldCheckedState = state; } + // Set the column affected by this event for COLUMN_SORTED events. + void SetColumn(unsigned column) + { + m_column = column; + } + const wxTreeListItem m_item; wxCheckBoxState m_oldCheckedState; + unsigned m_column; + friend class wxTreeListCtrl; wxDECLARE_ABSTRACT_CLASS(wxTreeListEvent); @@ -468,6 +547,10 @@ wxDECLARE_TREELIST_EVENT(ITEM_CONTEXT_MENU); #define EVT_TREELIST_ITEM_CONTEXT_MENU(id, fn) \ wxEVT_TREELIST_GENERIC(ITEM_CONTEXT_MENU, id, fn) +wxDECLARE_TREELIST_EVENT(COLUMN_SORTED); +#define EVT_TREELIST_COLUMN_SORTED(id, fn) \ + wxEVT_TREELIST_GENERIC(COLUMN_SORTED, id, fn) + #undef wxDECLARE_TREELIST_EVENT #endif // wxUSE_TREELISTCTRL diff --git a/interface/wx/treelist.h b/interface/wx/treelist.h index 5a70301d0a..dc8f4cc675 100644 --- a/interface/wx/treelist.h +++ b/interface/wx/treelist.h @@ -38,6 +38,64 @@ public: bool IsOk() const; }; +/** + Class defining sort order for the items in wxTreeListCtrl. + + @see wxTreeListCtrl + + @library{wxadv} + @category{ctrl} + + @since 2.9.3 + */ +class wxTreeListItemComparator +{ +public: + /** + Default constructor. + + Notice that this class is not copyable, comparators are not passed by + value. + */ + wxTreeListItemComparator(); + + /** + Pure virtual function which must be overridden to define sort order. + + The comparison function should return negative, null or positive value + depending on whether the first item is less than, equal to or greater + than the second one. The items should be compared using their values + for the given column. + + @param treelist + The control whose contents is being sorted. + @param column + The column of this control used for sorting. + @param first + First item to compare. + @param second + Second item to compare. + @return + A negative value if the first item is less than (i.e. should appear + above) the second one, zero if the two items are equal or a + positive value if the first item is greater than (i.e. should + appear below) the second one. + */ + virtual int + Compare(wxTreeListCtrl* treelist, + unsigned column, + wxTreeListItem first, + wxTreeListItem second) = 0; + + /** + Trivial but virtual destructor. + + Although this class is not used polymorphically by wxWidgets itself, + provide virtual dtor in case it's used like this in the user code. + */ + virtual ~wxTreeListItemComparator(); +}; + /** Container of multiple items. */ @@ -81,6 +139,19 @@ extern const wxTreeListItem wxTLI_LAST; the other columns. + Unlike wxTreeCtrl or wxListCtrl this control can sort its items on its own. + To allow user to sort the control contents by clicking on some column you + should use wxCOL_SORTABLE flag when adding that column to the control. When + a column with this flag is clicked, the control resorts itself using the + values in this column. By default the sort is done using alphabetical order + comparison of the items text, which is not always correct (e.g. this + doesn't work for the numeric columns). To change this you may use + SetItemComparator() method to provide a custom comparator, i.e. simply an + object that implements comparison between the two items. The treelist + sample shows an example of doing this. And if you need to sort the control + programmatically, you can call SetSortColumn() method. + + Here are the styles supported by this control. Notice that using wxTL_USER_3STATE implies wxTL_3STATE and wxTL_3STATE in turn implies wxTL_CHECKBOX. @@ -132,6 +203,11 @@ extern const wxTreeListItem wxTLI_LAST; @event{EVT_TREELIST_ITEM_CONTEXT_MENU(id, func)} Process @c wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU event indicating that the popup menu for the given item should be displayed. + @event{EVT_TREELIST_COLUMN_SORTED(id, func)} + Process @c wxEVT_COMMAND_TREELIST_COLUMN_SORTED event indicating that + the control contents has just been resorted using the specified column. + The event doesn't carry the sort direction, use GetSortColumn() method + if you need to know it. @endEventTable @library{wxadv} @@ -247,8 +323,9 @@ public: @param align Alignment of both the column header and its items. @param flags - Column flags, currently can only include wxCOL_RESIZABLE to allow - the user to resize the column. + Column flags, currently can include wxCOL_RESIZABLE to allow the + user to resize the column and wxCOL_SORTABLE to allow the user to + resort the control contents by clicking on this column. @return Index of the new column or -1 on failure. */ @@ -665,6 +742,71 @@ public: //@} + /** + Sorting. + + If some control columns were added with wxCOL_SORTABLE flag, clicking + on them will automatically resort the control using the custom + comparator set by SetItemComparator() or by doing alphabetical + comparison by default. + + In any case, i.e. even if the user can't sort the control by clicking + on its header, you may call SetSortColumn() to sort it programmatically + and call GetSortColumn() to determine whether it's sorted now and, if + so, by which column and in which order. + */ + //@{ + + /** + Set the column to use for sorting and the order in which to sort. + + Calling this method resorts the control contents using the values of + the items in the specified column. Sorting uses custom comparator set + with SetItemComparator() or alphabetical comparison of items texts if + none was specified. + + Notice that currently there is no way to reset sort order. + + @param col + A valid column index. + @param ascendingOrder + Indicates whether the items should be sorted in ascending (A to Z) + or descending (Z to A) order. + */ + void SetSortColumn(unsigned col, bool ascendingOrder = true); + + /** + Return the column currently used for sorting, if any. + + If the control is currently unsorted, the function simply returns + @false and doesn't modify any of its output parameters. + + @param col + Receives the index of the column used for sorting if non-@NULL. + @param ascendingOrder + Receives @true or @false depending on whether the items are sorted + in ascending or descending order. + @return + @true if the control is sorted or @false if it isn't sorted at all. + */ + bool GetSortColumn(unsigned* col, bool* ascendingOrder = NULL); + + /** + Set the object to use for comparing the items. + + This object will be used when the control is being sorted because the + user clicked on a sortable column or SetSortColumn() was called. + + The provided pointer is stored by the control so the object it points + to must have a life-time equal or greater to that of the control + itself. In addition, the pointer can be @NULL to stop using custom + comparator and revert to the default alphabetical comparison. + */ + void SetItemComparator(wxTreeListItemComparator* comparator); + + //@} + + /** View window. @@ -722,6 +864,14 @@ public: wxTreeListCtrl::GetCheckedState(). */ wxCheckBoxState GetOldCheckedState() const; + + /** + Return the column affected by the event. + + This is currently only used with @c wxEVT_COMMAND_TREELIST_COLUMN_SORTED + event. + */ + unsigned GetColumn() const; }; diff --git a/samples/treelist/treelist.cpp b/samples/treelist/treelist.cpp index 13894529af..d7b5dc9527 100644 --- a/samples/treelist/treelist.cpp +++ b/samples/treelist/treelist.cpp @@ -50,9 +50,10 @@ #endif // ---------------------------------------------------------------------------- -// Constants for menu items +// Constants // ---------------------------------------------------------------------------- +// Menu items. enum { Id_MultiSelect = 100, @@ -72,6 +73,89 @@ enum Id_Select_HTMLDocs }; +// Tree list columns. +enum +{ + Col_Component, + Col_Files, + Col_Size +}; + +// ---------------------------------------------------------------------------- +// Custom comparator for tree list items comparison +// ---------------------------------------------------------------------------- + +// This is a toy class as in a real program you would have the original numeric +// data somewhere and wouldn't need to parse it back from strings presumably. +// Nevertheless it shows how to implement a custom comparator which is needed +// if you want to sort by a column with non-textual contents. +class MyComparator : public wxTreeListItemComparator +{ +public: + virtual int + Compare(wxTreeListCtrl* treelist, + unsigned column, + wxTreeListItem item1, + wxTreeListItem item2) + { + wxString text1 = treelist->GetItemText(item1, column), + text2 = treelist->GetItemText(item2, column); + + switch ( column ) + { + case Col_Component: + // Simple alphabetical comparison is fine for those. + return text1.CmpNoCase(text2); + + case Col_Files: + // Compare strings as numbers. + return GetNumFilesFromText(text1) - GetNumFilesFromText(text2); + + case Col_Size: + // Compare strings as numbers but also take care of "KiB" and + // "MiB" suffixes. + return GetSizeFromText(text1) - GetSizeFromText(text2); + } + + wxFAIL_MSG( "Sorting on unknown column?" ); + + return 0; + } + +private: + // Return the number of files handling special value "many". Notice that + // the returned value is signed to allow using it in subtraction above. + int GetNumFilesFromText(const wxString& text) const + { + unsigned long n; + if ( !text.ToULong(&n) ) + { + if ( text == "many" ) + n = 9999; + else + n = 0; + } + + return n; + } + + // Return the size in KiB from a string with either KiB or MiB suffix. + int GetSizeFromText(const wxString& text) const + { + wxString size; + unsigned factor = 1; + if ( text.EndsWith(" MiB", &size) ) + factor = 1024; + else if ( !text.EndsWith(" KiB", &size) ) + return 0; + + unsigned long n = 0; + size.ToULong(&n); + + return n*factor; + } +}; + // ---------------------------------------------------------------------------- // Application class // ---------------------------------------------------------------------------- @@ -140,6 +224,8 @@ private: wxTreeListCtrl* m_treelist; + MyComparator m_comparator; + wxTreeListItem m_itemHTMLDocs; wxLog* m_oldLogTarget; @@ -304,20 +390,18 @@ wxTreeListCtrl* MyFrame::CreateTreeListCtrl(long style) style); tree->SetImageList(m_imageList); - enum - { - Col_Component, - Col_Files, - Col_Size - }; - - tree->AppendColumn("Component"); + tree->AppendColumn("Component", + wxCOL_WIDTH_AUTOSIZE, + wxALIGN_LEFT, + wxCOL_RESIZABLE | wxCOL_SORTABLE); tree->AppendColumn("# Files", tree->WidthFor("1,000,000"), - wxALIGN_RIGHT); + wxALIGN_RIGHT, + wxCOL_RESIZABLE | wxCOL_SORTABLE); tree->AppendColumn("Size", tree->WidthFor("1,000,000 KiB"), - wxALIGN_RIGHT); + wxALIGN_RIGHT, + wxCOL_RESIZABLE | wxCOL_SORTABLE); // Define a shortcut to save on typing here. #define ADD_ITEM(item, parent, files, size) \ @@ -351,6 +435,9 @@ wxTreeListCtrl* MyFrame::CreateTreeListCtrl(long style) // Remember this one for subsequent tests. m_itemHTMLDocs = HTML; + // Set a custom comparator to compare strings containing numbers correctly. + tree->SetItemComparator(&m_comparator); + return tree; } diff --git a/src/generic/treelist.cpp b/src/generic/treelist.cpp index 69245fd31d..68ee06f333 100644 --- a/src/generic/treelist.cpp +++ b/src/generic/treelist.cpp @@ -378,6 +378,10 @@ public: virtual unsigned GetChildren(const wxDataViewItem& item, wxDataViewItemArray& children) const; virtual bool IsListModel() const { return m_isFlat; } + virtual int Compare(const wxDataViewItem& item1, + const wxDataViewItem& item2, + unsigned col, + bool ascending) const; private: // The control we're associated with. @@ -953,6 +957,27 @@ wxTreeListModel::GetChildren(const wxDataViewItem& item, return numChildren; } +int +wxTreeListModel::Compare(const wxDataViewItem& item1, + const wxDataViewItem& item2, + unsigned col, + bool ascending) const +{ + // Compare using default alphabetical order if no custom comparator. + wxTreeListItemComparator* const comp = m_treelist->m_comparator; + if ( !comp ) + return wxDataViewModel::Compare(item1, item2, col, ascending); + + // Forward comparison to the comparator: + int result = comp->Compare(m_treelist, col, FromDVI(item1), FromDVI(item2)); + + // And adjust by the sort order if necessary. + if ( !ascending ) + result = -result; + + return result; +} + // ============================================================================ // wxTreeListCtrl implementation // ============================================================================ @@ -963,6 +988,7 @@ BEGIN_EVENT_TABLE(wxTreeListCtrl, wxWindow) EVT_DATAVIEW_ITEM_EXPANDED(wxID_ANY, wxTreeListCtrl::OnItemExpanded) EVT_DATAVIEW_ITEM_ACTIVATED(wxID_ANY, wxTreeListCtrl::OnItemActivated) EVT_DATAVIEW_ITEM_CONTEXT_MENU(wxID_ANY, wxTreeListCtrl::OnItemContextMenu) + EVT_DATAVIEW_COLUMN_SORTED(wxID_ANY, wxTreeListCtrl::OnColumnSorted) EVT_SIZE(wxTreeListCtrl::OnSize) END_EVENT_TABLE() @@ -975,6 +1001,7 @@ void wxTreeListCtrl::Init() { m_view = NULL; m_model = NULL; + m_comparator = NULL; } bool wxTreeListCtrl::Create(wxWindow* parent, @@ -1454,11 +1481,48 @@ wxTreeListCtrl::AreAllChildrenInState(wxTreeListItem item, return true; } +// ---------------------------------------------------------------------------- +// Sorting +// ---------------------------------------------------------------------------- + +void wxTreeListCtrl::SetSortColumn(unsigned col, bool ascendingOrder) +{ + wxCHECK_RET( col < m_view->GetColumnCount(), "Invalid column index" ); + + m_view->GetColumn(col)->SetSortOrder(ascendingOrder); +} + +bool wxTreeListCtrl::GetSortColumn(unsigned* col, bool* ascendingOrder) +{ + const unsigned numColumns = m_view->GetColumnCount(); + for ( unsigned n = 0; n < numColumns; n++ ) + { + wxDataViewColumn* const column = m_view->GetColumn(n); + if ( column->IsSortKey() ) + { + if ( col ) + *col = n; + + if ( ascendingOrder ) + *ascendingOrder = column->IsSortOrderAscending(); + + return true; + } + } + + return false; +} + +void wxTreeListCtrl::SetItemComparator(wxTreeListItemComparator* comparator) +{ + m_comparator = comparator; +} + // ---------------------------------------------------------------------------- // Events // ---------------------------------------------------------------------------- -void wxTreeListCtrl::SendEvent(wxEventType evt, wxDataViewEvent& eventDV) +void wxTreeListCtrl::SendItemEvent(wxEventType evt, wxDataViewEvent& eventDV) { wxTreeListEvent eventTL(evt, this, m_model->FromDVI(eventDV.GetItem())); @@ -1474,6 +1538,23 @@ void wxTreeListCtrl::SendEvent(wxEventType evt, wxDataViewEvent& eventDV) } } +void wxTreeListCtrl::SendColumnEvent(wxEventType evt, wxDataViewEvent& eventDV) +{ + wxTreeListEvent eventTL(evt, this, wxTreeListItem()); + eventTL.SetColumn(eventDV.GetColumn()); + + if ( !ProcessWindowEvent(eventTL) ) + { + eventDV.Skip(); + return; + } + + if ( !eventTL.IsAllowed() ) + { + eventDV.Veto(); + } +} + void wxTreeListCtrl::OnItemToggled(wxTreeListItem item, wxCheckBoxState stateOld) { @@ -1485,27 +1566,32 @@ wxTreeListCtrl::OnItemToggled(wxTreeListItem item, wxCheckBoxState stateOld) void wxTreeListCtrl::OnSelectionChanged(wxDataViewEvent& event) { - SendEvent(wxEVT_COMMAND_TREELIST_SELECTION_CHANGED, event); + SendItemEvent(wxEVT_COMMAND_TREELIST_SELECTION_CHANGED, event); } void wxTreeListCtrl::OnItemExpanding(wxDataViewEvent& event) { - SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDING, event); + SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDING, event); } void wxTreeListCtrl::OnItemExpanded(wxDataViewEvent& event) { - SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDED, event); + SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDED, event); } void wxTreeListCtrl::OnItemActivated(wxDataViewEvent& event) { - SendEvent(wxEVT_COMMAND_TREELIST_ITEM_ACTIVATED, event); + SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_ACTIVATED, event); } void wxTreeListCtrl::OnItemContextMenu(wxDataViewEvent& event) { - SendEvent(wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU, event); + SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU, event); +} + +void wxTreeListCtrl::OnColumnSorted(wxDataViewEvent& event) +{ + SendColumnEvent(wxEVT_COMMAND_TREELIST_COLUMN_SORTED, event); } // ---------------------------------------------------------------------------- @@ -1572,6 +1658,7 @@ wxDEFINE_TREELIST_EVENT(ITEM_EXPANDED); wxDEFINE_TREELIST_EVENT(ITEM_CHECKED); wxDEFINE_TREELIST_EVENT(ITEM_ACTIVATED); wxDEFINE_TREELIST_EVENT(ITEM_CONTEXT_MENU); +wxDEFINE_TREELIST_EVENT(COLUMN_SORTED); #undef wxDEFINE_TREELIST_EVENT