From: Vadim Zeitlin Date: Sun, 14 Dec 2008 00:02:30 +0000 (+0000) Subject: add support for sorting to grid columns X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/11393d2900fdd267f22d653721c6be745d0c4649 add support for sorting to grid columns git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@57323 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index f38566a86b..98427527e8 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -1749,6 +1749,31 @@ public: #endif // wxUSE_DRAG_AND_DROP + // ------- sorting support + + // wxGrid doesn't support sorting on its own but it can indicate the sort + // order in the column header (currently only if native header control is + // used though) + + // return the column currently displaying the sort indicator or wxNOT_FOUND + // if none + int GetSortingColumn() const { return m_sortCol; } + + // return true if this column is currently used for sorting + bool IsSortingBy(int col) const { return GetSortingColumn() == col; } + + // return the current sorting order (on GetSortingColumn()): true for + // ascending sort and false for descending; it doesn't make sense to call + // it if GetSortingColumn() returns wxNOT_FOUND + bool IsSortOrderAscending() const { return m_sortIsAscending; } + + // set the sorting column (or unsets any existing one if wxNOT_FOUND) and + // the order in which to sort + void SetSortingColumn(int col, bool ascending = true); + + // unset any existing sorting column + void UnsetSortingColumn() { SetSortingColumn(wxNOT_FOUND); } + #ifdef WXWIN_COMPATIBILITY_2_8 // ------ For compatibility with previous wxGrid only... // @@ -1998,6 +2023,9 @@ protected: wxArrayInt m_colWidths; wxArrayInt m_colRights; + int m_sortCol; + bool m_sortIsAscending; + bool m_useNativeHeader, m_nativeColumnLabels; @@ -2244,6 +2272,13 @@ private: // common part of Clip{Horz,Vert}GridLines void DoClipGridLines(bool& var, bool clip); + // update the sorting indicator shown in the specified column (whose index + // must be valid) + // + // this will use GetSortingColumn() and IsSortOrderAscending() to determine + // the sorting indicator to effectively show + void UpdateColumnSortingIndicator(int col); + // return the position (not index) of the column at the given logical pixel // position @@ -2289,6 +2324,8 @@ private: void ProcessColLabelMouseEvent(wxMouseEvent& event); void ProcessCornerLabelMouseEvent(wxMouseEvent& event); + void DoColHeaderClick(int col); + void DoStartResizeCol(int col); void DoUpdateResizeCol(int x); void DoUpdateResizeColWidth(int w); @@ -2608,6 +2645,7 @@ extern WXDLLIMPEXP_ADV const wxEventType wxEVT_GRID_EDITOR_HIDDEN; extern WXDLLIMPEXP_ADV const wxEventType wxEVT_GRID_EDITOR_CREATED; extern WXDLLIMPEXP_ADV const wxEventType wxEVT_GRID_CELL_BEGIN_DRAG; extern WXDLLIMPEXP_ADV const wxEventType wxEVT_GRID_COL_MOVE; +extern WXDLLIMPEXP_ADV const wxEventType wxEVT_GRID_COL_SORT; typedef void (wxEvtHandler::*wxGridEventFunction)(wxGridEvent&); @@ -2650,6 +2688,7 @@ typedef void (wxEvtHandler::*wxGridEditorCreatedEventFunction)(wxGridEditorCreat #define EVT_GRID_CMD_ROW_SIZE(id, fn) wx__DECLARE_GRIDSIZEEVT(ROW_SIZE, id, fn) #define EVT_GRID_CMD_COL_SIZE(id, fn) wx__DECLARE_GRIDSIZEEVT(COL_SIZE, id, fn) #define EVT_GRID_CMD_COL_MOVE(id, fn) wx__DECLARE_GRIDEVT(COL_MOVE, id, fn) +#define EVT_GRID_CMD_COL_SORT(id, fn) wx__DECLARE_GRIDEVT(COL_SORT, id, fn) #define EVT_GRID_CMD_RANGE_SELECT(id, fn) wx__DECLARE_GRIDRANGESELEVT(RANGE_SELECT, id, fn) #define EVT_GRID_CMD_CELL_CHANGE(id, fn) wx__DECLARE_GRIDEVT(CELL_CHANGE, id, fn) #define EVT_GRID_CMD_SELECT_CELL(id, fn) wx__DECLARE_GRIDEVT(SELECT_CELL, id, fn) @@ -2671,6 +2710,7 @@ typedef void (wxEvtHandler::*wxGridEditorCreatedEventFunction)(wxGridEditorCreat #define EVT_GRID_ROW_SIZE(fn) EVT_GRID_CMD_ROW_SIZE(wxID_ANY, fn) #define EVT_GRID_COL_SIZE(fn) EVT_GRID_CMD_COL_SIZE(wxID_ANY, fn) #define EVT_GRID_COL_MOVE(fn) EVT_GRID_CMD_COL_MOVE(wxID_ANY, fn) +#define EVT_GRID_COL_SORT(fn) EVT_GRID_CMD_COL_SORT(wxID_ANY, fn) #define EVT_GRID_RANGE_SELECT(fn) EVT_GRID_CMD_RANGE_SELECT(wxID_ANY, fn) #define EVT_GRID_CELL_CHANGE(fn) EVT_GRID_CMD_CELL_CHANGE(wxID_ANY, fn) #define EVT_GRID_SELECT_CELL(fn) EVT_GRID_CMD_SELECT_CELL(wxID_ANY, fn) diff --git a/interface/wx/grid.h b/interface/wx/grid.h index d41a7d485f..bb38bdaeaf 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -3056,6 +3056,77 @@ protected: called for this row. */ int GetRowMinimalHeight(int col) const; + + + /** + @name Sorting support. + + wxGrid doesn't provide any support for sorting the data but it does + generate events allowing the user code to sort it and supports + displaying the sort indicator in the column used for sorting. + + To use wxGrid sorting support you need to handle wxEVT_GRID_COL_SORT + event (and not veto it) and resort the data displayed in the grid. The + grid will automatically update the sorting indicator on the column + which was clicked. + + You can also call the functions in this section directly to update the + sorting indicator. Once again, they don't do anything with the grid + data, it remains your responsibility to actually sort it appropriately. + */ + //@{ + + /** + Return the column in which the sorting indicator is currently + displayed. + + Returns @c wxNOT_FOUND if sorting indicator is not currently displayed + at all. + + @see SetSortingColumn() + */ + int GetSortingColumn() const; + + /** + Return @true if this column is currently used for sorting. + + @see GetSortingColumn() + */ + bool IsSortingBy(int col) const; + + /** + Return @true if the current sorting order is ascending or @false if it + is descending. + + It only makes sense to call this function if GetSortingColumn() returns + a valid column index and not @c wxNOT_FOUND. + + @see SetSortingColumn() + */ + bool IsSortOrderAscending() const; + + /** + Set the column to display the sorting indicator in and its direction. + + @param col + The column to display the sorting indicator in or @c wxNOT_FOUND to + remove any currently displayed sorting indicator. + @param ascending + If @true, display the ascending sort indicator, otherwise display + the descending sort indicator. + + @see GetSortingColumn(), IsSortOrderAscending() + */ + void SetSortingColumn(int col, bool ascending = true); + + /** + Remove any currently shown sorting indicator. + + This is equivalent to calling SetSortingColumn() with @c wxNOT_FOUND + first argument. + */ + void UnsetSortingColumn(); + //@} }; @@ -3183,7 +3254,19 @@ public: proceed in which case wxGrid::SetColPos() is used to reorder the columns display order without affecting the use of the column indices otherwise. + This event macro corresponds to @c wxEVT_GRID_COL_MOVE event type. + @event{EVT_GRID_COL_SORT(func)} + This event is generated when a column is clicked by the user and its + name is explained by the fact that the custom reaction to a click on a + column is to sort the grid contents by this column. However the grid + itself has no special support for sorting and it's up to the handler of + this event to update the associated table. But if the event is handled + (and not vetoed) the grid supposes that the table was indeed resorted + and updates the column to indicate the new sort order and refreshes + itself. + + This event macro corresponds to @c wxEVT_GRID_COL_SORT event type. @endEventTable @library{wxadv} diff --git a/samples/grid/griddemo.cpp b/samples/grid/griddemo.cpp index f8b6f36895..cff3f3b1fa 100644 --- a/samples/grid/griddemo.cpp +++ b/samples/grid/griddemo.cpp @@ -1565,22 +1565,34 @@ public: ROW_MAX = 3 }; + TabularGridTable() { m_sortOrder = NULL; } + virtual int GetNumberRows() { return ROW_MAX; } virtual int GetNumberCols() { return COL_MAX; } virtual wxString GetValue(int row, int col) { - // notice that column parameter here always refers to the internal - // column index, independently of its position on the screen - static const char *filedata[][COL_MAX] = + if ( m_sortOrder ) + row = m_sortOrder[row]; + + switch ( col ) { - { "autoexec", "bat", "412", "Apr 17 2004" }, - { "boot", "ini", "604", "May 27 2006" }, - { "io", "sys", "40774", "May 31 1994" }, - }; - wxCOMPILE_TIME_ASSERT( WXSIZEOF(filedata) == ROW_MAX, Mismatch ); + case COL_NAME: + case COL_EXT: + return GetNameOrExt(row, col); - return filedata[row][col]; + case COL_SIZE: + return wxString::Format("%lu", GetSize(row)); + + case COL_DATE: + return GetDate(row).FormatDate(); + + case COL_MAX: + default: + wxFAIL_MSG( "unknown column" ); + } + + return wxString(); } virtual void SetValue(int, int, const wxString&) @@ -1590,8 +1602,10 @@ public: virtual wxString GetColLabelValue(int col) { + // notice that column parameter here always refers to the internal + // column index, independently of its position on the screen static const char *labels[] = { "Name", "Extension", "Size", "Date" }; - wxCOMPILE_TIME_ASSERT( WXSIZEOF(labels) == COL_MAX, Mismatch ); + wxCOMPILE_TIME_ASSERT( WXSIZEOF(labels) == COL_MAX, LabelsMismatch ); return labels[col]; } @@ -1600,6 +1614,54 @@ public: { wxFAIL_MSG( "shouldn't be called" ); } + + void Sort(int col, bool ascending) + { + // we hardcode all sorting orders for simplicity here + static int sortOrders[COL_MAX][2][ROW_MAX] = + { + // descending ascending + { { 2, 1, 0 }, { 0, 1, 2 } }, + { { 2, 1, 0 }, { 0, 1, 2 } }, + { { 2, 1, 0 }, { 0, 1, 2 } }, + { { 1, 0, 2 }, { 2, 0, 1 } }, + }; + + m_sortOrder = col == wxNOT_FOUND ? NULL : sortOrders[col][ascending]; + } + +private: + wxString GetNameOrExt(int row, int col) const + { + static const char * + names[] = { "autoexec.bat", "boot.ini", "io.sys" }; + wxCOMPILE_TIME_ASSERT( WXSIZEOF(names) == ROW_MAX, NamesMismatch ); + + const wxString s(names[row]); + return col == COL_NAME ? s.BeforeFirst('.') : s.AfterLast('.'); + } + + unsigned long GetSize(int row) const + { + static const unsigned long + sizes[] = { 412, 604, 40774 }; + wxCOMPILE_TIME_ASSERT( WXSIZEOF(sizes) == ROW_MAX, SizesMismatch ); + + return sizes[row]; + } + + wxDateTime GetDate(int row) const + { + static const char * + dates[] = { "2004-04-17", "2006-05-27", "1994-05-31" }; + wxCOMPILE_TIME_ASSERT( WXSIZEOF(dates) == ROW_MAX, DatesMismatch ); + + wxDateTime dt; + dt.ParseISODate(dates[row]); + return dt; + } + + int *m_sortOrder; }; // specialized text control for column indexes entry @@ -1690,6 +1752,13 @@ private: UpdateOrder(); } + void OnGridColSort(wxGridEvent& event) + { + const int col = event.GetCol(); + m_table->Sort(col, !(m_grid->IsSortingBy(col) && + m_grid->IsSortOrderAscending())); + } + void OnGridColMove(wxGridEvent& event) { // can't update it yet as the order hasn't been changed, so do it a bit @@ -1721,6 +1790,7 @@ private: // controls wxGrid *m_grid; + TabularGridTable *m_table; wxCheckBox *m_chkUseNative, *m_chkDrawNative, *m_chkShowRowLabels, @@ -1753,6 +1823,7 @@ BEGIN_EVENT_TABLE(TabularGridFrame, wxFrame) EVT_BUTTON(wxID_APPLY, TabularGridFrame::OnMoveColumn) + EVT_GRID_COL_SORT(TabularGridFrame::OnGridColSort) EVT_GRID_COL_MOVE(TabularGridFrame::OnGridColMove) EVT_IDLE(TabularGridFrame::OnIdle) @@ -1766,10 +1837,11 @@ TabularGridFrame::TabularGridFrame() wxPanel * const panel = new wxPanel(this); // create and initialize the grid with the specified data + m_table = new TabularGridTable; m_grid = new wxGrid(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_STATIC | wxWANTS_CHARS); - m_grid->SetTable(new TabularGridTable, true, wxGrid::wxGridSelectRows); + m_grid->SetTable(m_table, true, wxGrid::wxGridSelectRows); m_grid->EnableDragColMove(); m_grid->UseNativeColHeader(); @@ -1841,3 +1913,10 @@ void GridFrame::OnTabularTable(wxCommandEvent&) { new TabularGridFrame; } + +bool GridApp::OnInit() +{ + new TabularGridFrame(); + + return true; +} diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 3e8c74b0b7..452a7b6d3f 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -146,6 +146,7 @@ DEFINE_EVENT_TYPE(wxEVT_GRID_LABEL_RIGHT_DCLICK) DEFINE_EVENT_TYPE(wxEVT_GRID_ROW_SIZE) DEFINE_EVENT_TYPE(wxEVT_GRID_COL_SIZE) DEFINE_EVENT_TYPE(wxEVT_GRID_COL_MOVE) +DEFINE_EVENT_TYPE(wxEVT_GRID_COL_SORT) DEFINE_EVENT_TYPE(wxEVT_GRID_RANGE_SELECT) DEFINE_EVENT_TYPE(wxEVT_GRID_CELL_CHANGE) DEFINE_EVENT_TYPE(wxEVT_GRID_SELECT_CELL) @@ -192,9 +193,15 @@ public: return flags; } - // TODO: currently there is no support for sorting - virtual bool IsSortKey() const { return false; } - virtual bool IsSortOrderAscending() const { return false; } + virtual bool IsSortKey() const + { + return m_grid->IsSortingBy(m_col); + } + + virtual bool IsSortOrderAscending() const + { + return m_grid->IsSortOrderAscending(); + } private: // these really should be const but are not because the column needs to be @@ -249,6 +256,8 @@ private: // override to implement column auto sizing virtual bool UpdateColumnWidthToFit(unsigned int idx, int widthTitle) { + // TODO: currently grid doesn't support computing the column best width + // from its contents so we just use the best label width as is GetOwner()->SetColSize(idx, widthTitle); return true; @@ -256,6 +265,11 @@ private: // event handlers forwarding wxHeaderCtrl events to wxGrid + void OnClick(wxHeaderCtrlEvent& event) + { + GetOwner()->DoColHeaderClick(event.GetColumn()); + } + void OnBeginResize(wxHeaderCtrlEvent& event) { GetOwner()->DoStartResizeCol(event.GetColumn()); @@ -292,6 +306,8 @@ private: }; BEGIN_EVENT_TABLE(wxGridHeaderCtrl, wxHeaderCtrl) + EVT_HEADER_CLICK(wxID_ANY, wxGridHeaderCtrl::OnClick) + EVT_HEADER_BEGIN_RESIZE(wxID_ANY, wxGridHeaderCtrl::OnBeginResize) EVT_HEADER_RESIZING(wxID_ANY, wxGridHeaderCtrl::OnResizing) EVT_HEADER_END_RESIZE(wxID_ANY, wxGridHeaderCtrl::OnEndResize) @@ -4884,6 +4900,9 @@ void wxGrid::Init() m_isDragging = false; m_startDragPos = wxDefaultPosition; + m_sortCol = wxNOT_FOUND; + m_sortIsAscending = true; + m_useNativeHeader = m_nativeColumnLabels = false; @@ -5849,6 +5868,60 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event ) } } +void wxGrid::UpdateColumnSortingIndicator(int col) +{ + wxCHECK_RET( col != wxNOT_FOUND, "invalid column index" ); + + if ( m_useNativeHeader ) + GetColHeader()->UpdateColumn(col); + else if ( m_nativeColumnLabels ) + m_colWindow->Refresh(); + //else: sorting indicator display not yet implemented in grid version +} + +void wxGrid::SetSortingColumn(int col, bool ascending) +{ + if ( col == m_sortCol ) + { + // we are already using this column for sorting (or not sorting at all) + // but we might still change the sorting order, check for it + if ( m_sortCol != wxNOT_FOUND && ascending != m_sortIsAscending ) + { + m_sortIsAscending = ascending; + + UpdateColumnSortingIndicator(m_sortCol); + } + } + else // we're changing the column used for sorting + { + const int sortColOld = m_sortCol; + + // change it before updating the column as we want GetSortingColumn() + // to return the correct new value + m_sortCol = col; + + if ( sortColOld != wxNOT_FOUND ) + UpdateColumnSortingIndicator(sortColOld); + + if ( m_sortCol != wxNOT_FOUND ) + { + m_sortIsAscending = ascending; + UpdateColumnSortingIndicator(m_sortCol); + } + } +} + +void wxGrid::DoColHeaderClick(int col) +{ + // we consider that the grid was resorted if this event is processed and + // not vetoed + if ( SendEvent(wxEVT_GRID_COL_SORT, -1, col) == 1 ) + { + SetSortingColumn(col, IsSortingBy(col) ? !m_sortIsAscending : true); + Refresh(); + } +} + void wxGrid::DoStartResizeCol(int col) { m_dragRowOrCol = col; @@ -5882,10 +5955,11 @@ void wxGrid::DoUpdateResizeColWidth(int w) void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) { - int x, y, col; + int x, y; wxPoint pos( event.GetPosition() ); CalcUnscrolledPosition( pos.x, pos.y, &x, &y ); + int col = XToCol(x); if ( event.Dragging() ) { if (!m_isDragging) @@ -5893,8 +5967,8 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) m_isDragging = true; GetColLabelWindow()->CaptureMouse(); - if ( m_cursorMode == WXGRID_CURSOR_MOVE_COL ) - DoStartMoveCol(XToCol(x)); + if ( m_cursorMode == WXGRID_CURSOR_MOVE_COL && col != -1 ) + DoStartMoveCol(col); } if ( event.LeftIsDown() ) @@ -5907,7 +5981,7 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) case WXGRID_CURSOR_SELECT_COL: { - if ( (col = XToCol( x )) >= 0 ) + if ( col != -1 ) { if ( m_selection ) m_selection->SelectCol(col, event); @@ -6005,7 +6079,6 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) // if ( XToEdgeOfCol(x) < 0 ) { - col = XToCol(x); if ( col >= 0 && !SendEvent( wxEVT_GRID_LABEL_LEFT_CLICK, -1, col, event ) ) { @@ -6059,10 +6132,9 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) // if ( event.LeftDClick() ) { - col = XToEdgeOfCol(x); - if ( col < 0 ) + const int colEdge = XToEdgeOfCol(x); + if ( colEdge == -1 ) { - col = XToCol(x); if ( col >= 0 && ! SendEvent( wxEVT_GRID_LABEL_LEFT_DCLICK, -1, col, event ) ) { @@ -6072,7 +6144,7 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) else { // adjust column width depending on label text - AutoSizeColLabelSize( col ); + AutoSizeColLabelSize( colEdge ); ChangeCursorMode(WXGRID_CURSOR_SELECT_CELL, GetColLabelWindow()); m_dragLastPos = -1; @@ -6090,9 +6162,11 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) break; case WXGRID_CURSOR_MOVE_COL: - if ( m_dragLastPos == -1 ) + if ( m_dragLastPos == -1 || col == m_dragRowOrCol ) { - // The user clicked on the column but didn't actually drag + // the column didn't actually move anywhere + if ( col != -1 ) + DoColHeaderClick(col); m_colWindow->Refresh(); // "unpress" the column } else @@ -6105,7 +6179,8 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) case WXGRID_CURSOR_SELECT_CELL: case WXGRID_CURSOR_RESIZE_ROW: case WXGRID_CURSOR_SELECT_ROW: - // nothing to do (?) + if ( col != -1 ) + DoColHeaderClick(col); break; } @@ -6117,7 +6192,6 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) // else if ( event.RightDown() ) { - col = XToCol(x); if ( col >= 0 && !SendEvent( wxEVT_GRID_LABEL_RIGHT_CLICK, -1, col, event ) ) { @@ -6129,7 +6203,6 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event ) // else if ( event.RightDClick() ) { - col = XToCol(x); if ( col >= 0 && !SendEvent( wxEVT_GRID_LABEL_RIGHT_DCLICK, -1, col, event ) ) { @@ -8185,7 +8258,18 @@ void wxGrid::DrawColLabel(wxDC& dc, int col) if ( m_nativeColumnLabels ) { - wxRendererNative::Get().DrawHeaderButton(GetColLabelWindow(), dc, rect, 0); + wxRendererNative::Get().DrawHeaderButton + ( + GetColLabelWindow(), + dc, + rect, + 0, + IsSortingBy(col) + ? IsSortOrderAscending() + ? wxHDR_SORT_ICON_UP + : wxHDR_SORT_ICON_DOWN + : wxHDR_SORT_ICON_NONE + ); } else {