diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index 948aba4d0b..a0d1dae75c 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -339,6 +339,16 @@ protected: // the dtor is private because only DecRef() can delete us virtual ~wxGridCellEditor(); + // Helper for the derived classes positioning the control according to the + // attribute alignment if the desired control size is smaller than the cell + // size, or centering it vertically if its size is bigger: this looks like + // the best compromise when the editor control doesn't fit into the cell. + void DoPositionEditor(const wxSize& size, + const wxRect& rectCell, + int hAlign = wxALIGN_LEFT, + int vAlign = wxALIGN_CENTRE_VERTICAL); + + // the actual window we show on screen (this variable should actually be // named m_window, but m_control is kept for backward compatibility) wxWindow* m_control; @@ -1675,7 +1685,27 @@ public: void DisableRowResize(int row) { DoDisableLineResize(row, m_setFixedRows); } void DisableColResize(int col) { DoDisableLineResize(col, m_setFixedCols); } - // these functions return whether the given row/column can be + // These function return true if resizing rows/columns by dragging + // their edges inside the grid is enabled. Note that this doesn't cover + // dragging their separators in the label windows (which can be enabled + // for the columns even if dragging inside the grid is not), nor checks + // whether a particular row/column is resizeable or not, so you should + // always check CanDrag{Row,Col}Size() below too. + bool CanDragGridRowEdges() const + { + return m_canDragGridSize && m_canDragRowSize; + } + + bool CanDragGridColEdges() const + { + // When using the native header window we can only resize the columns by + // dragging the dividers in the header itself, but not by dragging them + // in the grid because we can't make the native control enter into the + // column resizing mode programmatically. + return m_canDragGridSize && m_canDragColSize && !m_useNativeHeader; + } + + // These functions return whether the given row/column can be // effectively resized: for this interactive resizing must be enabled // and this index must not have been passed to DisableRow/ColResize() bool CanDragRowSize(int row) const diff --git a/include/wx/generic/grideditors.h b/include/wx/generic/grideditors.h index 0abe429f88..4ffd61ef62 100644 --- a/include/wx/generic/grideditors.h +++ b/include/wx/generic/grideditors.h @@ -60,10 +60,6 @@ public: wxEvtHandler* evtHandler) wxOVERRIDE; virtual void SetSize(const wxRect& rect) wxOVERRIDE; - virtual void PaintBackground(wxDC& dc, - const wxRect& rectCell, - const wxGridCellAttr& attr) wxOVERRIDE; - virtual bool IsAcceptedKey(wxKeyEvent& event) wxOVERRIDE; virtual void BeginEdit(int row, int col, wxGrid* grid) wxOVERRIDE; virtual bool EndEdit(int row, int col, const wxGrid* grid, @@ -307,10 +303,6 @@ public: virtual void SetSize(const wxRect& rect) wxOVERRIDE; - virtual void PaintBackground(wxDC& dc, - const wxRect& rectCell, - const wxGridCellAttr& attr) wxOVERRIDE; - virtual void BeginEdit(int row, int col, wxGrid* grid) wxOVERRIDE; virtual bool EndEdit(int row, int col, const wxGrid* grid, const wxString& oldval, wxString *newval) wxOVERRIDE; diff --git a/include/wx/generic/private/grid.h b/include/wx/generic/private/grid.h index 709cf0c167..8628824814 100644 --- a/include/wx/generic/private/grid.h +++ b/include/wx/generic/private/grid.h @@ -152,6 +152,14 @@ public: (owner->CanHideColumns() ? wxHD_ALLOW_HIDE : 0) | (owner->CanDragColMove() ? wxHD_ALLOW_REORDER : 0)) { + m_inResizing = 0; + } + + // Special method to call from wxGrid::DoSetColSize(), see comments there. + void UpdateIfNotResizing(unsigned int idx) + { + if ( !m_inResizing ) + UpdateColumn(idx); } protected: @@ -259,7 +267,20 @@ private: void OnResizing(wxHeaderCtrlEvent& event) { + // Calling wxGrid method results in a call to our own UpdateColumn() + // because it ends up in wxGrid::SetColSize() which must indeed update + // the column when it's called by the program -- but in the case where + // the size change comes from the column itself, it is useless and, in + // fact, harmful, as it results in extra flicker due to the inefficient + // implementation of UpdateColumn() in wxMSW wxHeaderCtrl, so skip + // calling it from our overridden version by setting this flag for the + // duration of this function execution and checking it in our + // UpdateIfNotResizing(). + m_inResizing++; + GetOwner()->DoHeaderDragResizeCol(event.GetWidth()); + + m_inResizing--; } void OnEndResize(wxHeaderCtrlEvent& event) @@ -281,6 +302,9 @@ private: wxVector m_columns; + // The count of OnResizing() call nesting, 0 if not inside it. + int m_inResizing; + wxDECLARE_EVENT_TABLE(); wxDECLARE_NO_COPY_CLASS(wxGridHeaderCtrl); }; diff --git a/interface/wx/grid.h b/interface/wx/grid.h index 24d2dc8e0e..aa9a6b4a45 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -4322,6 +4322,26 @@ public: */ bool CanDragColSize(int col) const; + /** + Return @true if column edges inside the grid can be dragged to resize + the rows. + + @see CanDragGridSize(), CanDragColSize() + + @since 3.1.4 + */ + bool CanDragGridColEdges() const; + + /** + Return @true if row edges inside the grid can be dragged to resize the + rows. + + @see CanDragGridSize(), CanDragRowSize() + + @since 3.1.4 + */ + bool CanDragGridRowEdges() const; + /** Return @true if the dragging of grid lines to resize rows and columns is enabled or @false otherwise. diff --git a/samples/grid/griddemo.cpp b/samples/grid/griddemo.cpp index 5e314e21b7..282c5c68d7 100644 --- a/samples/grid/griddemo.cpp +++ b/samples/grid/griddemo.cpp @@ -543,12 +543,14 @@ GridFrame::GridFrame() // Some numeric columns with different formatting. grid->SetColFormatFloat(6); + grid->SetReadOnly(0, 6); grid->SetCellValue(0, 6, "Default\nfloat format"); grid->SetCellValue(1, 6, wxString::Format("%g", 3.1415)); grid->SetCellValue(2, 6, wxString::Format("%g", 1415.0)); grid->SetCellValue(3, 6, wxString::Format("%g", 12345.67890)); grid->SetColFormatFloat(7, 6, 2); + grid->SetReadOnly(0, 7); grid->SetCellValue(0, 7, "Width 6\nprecision 2"); grid->SetCellValue(1, 7, wxString::Format("%g", 3.1415)); grid->SetCellValue(2, 7, wxString::Format("%g", 1415.0)); @@ -556,22 +558,28 @@ GridFrame::GridFrame() grid->SetColFormatCustom(8, wxString::Format("%s:%i,%i,%s", wxGRID_VALUE_FLOAT, -1, 4, "g")); + grid->SetReadOnly(0, 8); grid->SetCellValue(0, 8, "Compact\nformat"); grid->SetCellValue(1, 8, "31415e-4"); grid->SetCellValue(2, 8, "1415"); grid->SetCellValue(3, 8, "123456789e-4"); grid->SetColFormatNumber(9); + grid->SetReadOnly(0, 9); grid->SetCellValue(0, 9, "Integer\ncolumn"); grid->SetCellValue(1, 9, "17"); grid->SetCellValue(2, 9, "0"); + grid->SetCellEditor(2, 9, new wxGridCellNumberEditor(0, 100)); + grid->SetCellValue(2, 10, "<- This cell uses [0, 100] range"); grid->SetCellValue(3, 9, "-666"); grid->SetCellAlignment(3, 9, wxALIGN_CENTRE, wxALIGN_TOP); grid->SetCellValue(3, 10, "<- This numeric cell should be centred"); + grid->SetReadOnly(0, 13); grid->SetCellValue(0, 13, "Localized date\ncolumn"); grid->SetColFormatDate(13); // Localized by default. grid->SetCellValue(1, 13, "Today"); + grid->SetReadOnly(0, 14); grid->SetCellValue(0, 14, "ISO 8601 date\ncolumn"); grid->SetColFormatDate(14, "%Y-%m-%d"); // ISO 8601 date format. grid->SetCellValue(1, 14, "Tomorrow"); diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index ec8aeaddbc..4423bfa055 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -3015,22 +3015,17 @@ void wxGrid::CalcDimensions() // take into account editor if shown if ( IsCellEditControlShown() ) { - int w2, h2; int r = m_currentCellCoords.GetRow(); int c = m_currentCellCoords.GetCol(); - int x = GetColLeft(c); - int y = GetRowTop(r); // how big is the editor wxGridCellAttrPtr attr = GetCellAttrPtr(r, c); wxGridCellEditorPtr editor = attr->GetEditorPtr(this, r, c); - editor->GetWindow()->GetSize(&w2, &h2); - w2 += x; - h2 += y; - if ( w2 > w ) - w = w2; - if ( h2 > h ) - h = h2; + const wxRect rect = editor->GetWindow()->GetRect(); + if ( rect.GetRight() > w ) + w = rect.GetRight(); + if ( rect.GetBottom() > h ) + h = rect.GetBottom(); } wxPoint offset = GetGridWindowOffset(m_gridWin); @@ -4577,8 +4572,20 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, MakeCellVisible(coords); } } - else if ( XToEdgeOfCol(pos.x) < 0 && YToEdgeOfRow(pos.y) < 0 ) + else { + // Clicking on (or very near) the separating lines shouldn't change the + // selection when it's used for resizing -- but should still do it if + // resizing is disabled (notice that we intentionally don't check for + // it being disabled for a particular row/column as it would be + // surprising to have different mouse behaviour in different parts of + // the same grid, so we only check for it being globally disabled). + if ( CanDragGridColEdges() && XToEdgeOfCol(pos.x) != wxNOT_FOUND ) + return; + + if ( CanDragGridRowEdges() && YToEdgeOfRow(pos.y) != wxNOT_FOUND ) + return; + DisableCellEditControl(); MakeCellVisible( coords ); @@ -4709,28 +4716,10 @@ wxGrid::DoGridMouseMoveEvent(wxMouseEvent& WXUNUSED(event), int dragRow = YToEdgeOfRow( pos.y ); int dragCol = XToEdgeOfCol( pos.x ); - // Dragging on the corner of a cell to resize in both - // directions is not implemented yet... - // - if ( dragRow >= 0 && dragCol >= 0 ) - { - ChangeCursorMode(WXGRID_CURSOR_RESIZE_ROW, gridWindow, false); - return; - } - - if ( dragRow >= 0 && CanDragGridSize() && CanDragRowSize(dragRow) ) - { - if ( m_cursorMode == WXGRID_CURSOR_SELECT_CELL ) - { - DoStartResizeRowOrCol(dragRow); - ChangeCursorMode(WXGRID_CURSOR_RESIZE_ROW, gridWindow, false); - } - } - // When using the native header window we can only resize the columns by - // dragging the dividers in it because we can't make it enter into the - // column resizing mode programmatically - else if ( dragCol >= 0 && !m_useNativeHeader && - CanDragGridSize() && CanDragColSize(dragCol) ) + // Dragging on the corner of a cell to resize in both directions is not + // implemented, so choose to resize the column when the cursor is over the + // cell corner, as this is a more common operation. + if ( dragCol >= 0 && CanDragGridColEdges() && CanDragColSize(dragCol) ) { if ( m_cursorMode == WXGRID_CURSOR_SELECT_CELL ) { @@ -4738,6 +4727,14 @@ wxGrid::DoGridMouseMoveEvent(wxMouseEvent& WXUNUSED(event), ChangeCursorMode(WXGRID_CURSOR_RESIZE_COL, gridWindow, false); } } + else if ( dragRow >= 0 && CanDragGridRowEdges() && CanDragRowSize(dragRow) ) + { + if ( m_cursorMode == WXGRID_CURSOR_SELECT_CELL ) + { + DoStartResizeRowOrCol(dragRow); + ChangeCursorMode(WXGRID_CURSOR_RESIZE_ROW, gridWindow, false); + } + } else // Neither on a row or col edge { if ( m_cursorMode != WXGRID_CURSOR_SELECT_CELL ) @@ -7262,24 +7259,11 @@ void wxGrid::ShowCellEditControl() m_currentCellCoords.SetCol( col ); } - // erase the highlight and the cell contents because the editor - // might not cover the entire cell - wxClientDC dc( gridWindow ); - PrepareDCFor(dc, gridWindow); - wxGridCellAttrPtr attr = GetCellAttrPtr(row, col); - dc.SetBrush(wxBrush(attr->GetBackgroundColour())); - dc.SetPen(*wxTRANSPARENT_PEN); - dc.DrawRectangle(rect); - rect.Offset(-GetGridWindowOffset(gridWindow)); // convert to scrolled coords CalcGridWindowScrolledPosition( rect.x, rect.y, &rect.x, &rect.y, gridWindow ); - int nXMove = 0; - if (rect.x < 0) - nXMove = rect.x; - #ifdef __WXQT__ // Substract 1 pixel in every dimension to fit in the cell area. // If not, Qt will draw the control outside the cell. @@ -7287,6 +7271,7 @@ void wxGrid::ShowCellEditControl() rect.Deflate(1, 1); #endif + wxGridCellAttrPtr attr = GetCellAttrPtr(row, col); wxGridCellEditorPtr editor = attr->GetEditorPtr(this, row, col); if ( !editor->IsCreated() ) { @@ -7328,10 +7313,6 @@ void wxGrid::ShowCellEditControl() maxWidth = rect.width; } - int client_right = gridWindow->GetClientSize().GetWidth(); - if (rect.x + maxWidth > client_right) - maxWidth = client_right - rect.x; - if ((maxWidth > rect.width) && (col < m_numCols) && m_table) { GetCellSize( row, col, &cell_rows, &cell_cols ); @@ -7350,17 +7331,35 @@ void wxGrid::ShowCellEditControl() else break; } - - if (rect.GetRight() > client_right) - rect.SetRight( client_right - 1 ); } editor->SetCellAttr( attr.get() ); editor->SetSize( rect ); - if (nXMove != 0) - editor->GetWindow()->Move( - editor->GetWindow()->GetPosition().x + nXMove, - editor->GetWindow()->GetPosition().y ); + + // Note that the actual rectangle used by the editor could be + // different from the one we proposed. + rect = editor->GetWindow()->GetRect(); + + // Ensure that the edit control fits into the visible part of the + // window by shifting it if necessary: we don't want to truncate + // any part of it by trying to position it too far to the left or + // top and we definitely don't want to start showing scrollbars by + // positioning it too far to the right or bottom. + const wxSize sizeMax = gridWindow->GetClientSize(); + if ( !wxRect(sizeMax).Contains(rect) ) + { + if ( rect.x < 0 ) + rect.x = 0; + if ( rect.y < 0 ) + rect.y = 0; + if ( rect.x > sizeMax.x - rect.width ) + rect.x = sizeMax.x - rect.width; + if ( rect.y > sizeMax.y - rect.height ) + rect.y = sizeMax.y - rect.height; + + editor->GetWindow()->Move(rect.x, rect.y); + } + editor->Show( true, attr.get() ); // recalc dimensions in case we need to @@ -9757,7 +9756,16 @@ void wxGrid::DoSetColSize( int col, int width ) return; if ( m_useNativeHeader ) - GetGridColHeader()->UpdateColumn(col); + { + // We have to update the native control if we're called from the + // program (directly or indirectly, e.g. via AutoSizeColumn()), but we + // want to avoid doing it when the column is being resized + // interactively, as this is unnecessary and results in very visible + // flicker, so take care to call the special method of our header + // control checking for whether it's being resized interactively + // instead of the usual UpdateColumn(). + static_cast(m_colLabelWin)->UpdateIfNotResizing(col); + } //else: will be refreshed when the header is redrawn for ( int colPos = GetColPos(col); colPos < m_numCols; colPos++ ) @@ -9836,7 +9844,10 @@ void wxGrid::DoSetColSize( int col, int width ) FurtherWindowPartRefresher refreshFurtherPart(x); - refreshFurtherPart(m_colLabelWin); + // Refreshing the native header is unnecessary, as it updates + // itself correctly anyhow, and just results in extra flicker. + if ( !IsUsingNativeHeader() ) + refreshFurtherPart(m_colLabelWin); refreshFurtherPart(m_gridWin); if ( m_frozenRowGridWin ) diff --git a/src/generic/grideditors.cpp b/src/generic/grideditors.cpp index ae7eff24ae..1bc48c46e2 100644 --- a/src/generic/grideditors.cpp +++ b/src/generic/grideditors.cpp @@ -331,6 +331,62 @@ void wxGridCellEditor::SetSize(const wxRect& rect) m_control->SetSize(rect, wxSIZE_ALLOW_MINUS_ONE); } +void wxGridCellEditor::DoPositionEditor(const wxSize& size, + const wxRect& rectCell, + int hAlign, + int vAlign) +{ + wxRect rect(rectCell.GetPosition(), size); + + // We center the control around the cell if it doesn't fit into it in one + // or both of directions, as this seems to look the best (difference is + // typically relatively small and by centering it, we divide it by two on + // each side, making it even smaller). + // + // For now just remember in which direction to do it in this variable and + // then do it at the end. + int centerDir = 0; + + // We're only going to need the alignment if the control is smaller than + // the cell in at least one direction. + if ( size.x < rectCell.width || size.y < rectCell.height ) + { + if ( GetCellAttr() ) + GetCellAttr()->GetNonDefaultAlignment(&hAlign, &vAlign); + } + + if ( size.x < rectCell.width ) + { + if ( hAlign == wxALIGN_CENTER_HORIZONTAL ) + centerDir |= wxHORIZONTAL; + else if ( hAlign == wxALIGN_RIGHT ) + rect.x = rectCell.x + rectCell.width - rect.width; + //else: nothing to do for the left alignment + } + else + { + centerDir |= wxHORIZONTAL; + } + + if ( size.y < rectCell.height ) + { + if ( vAlign == wxALIGN_CENTRE_VERTICAL ) + centerDir |= wxVERTICAL; + else if ( vAlign == wxALIGN_BOTTOM ) + rect.y = rectCell.y + rectCell.height - rect.height; + //else: nothing to do for the top alignment + } + else + { + centerDir |= wxVERTICAL; + } + + if ( centerDir ) + rect = rect.CenterIn(rectCell, centerDir); + + wxGridCellEditor::SetSize(rect); +} + void wxGridCellEditor::HandleReturn(wxKeyEvent& event) { event.Skip(); @@ -426,14 +482,6 @@ void wxGridCellTextEditor::DoCreate(wxWindow* parent, wxGridCellEditor::Create(parent, id, evtHandler); } -void wxGridCellTextEditor::PaintBackground(wxDC& WXUNUSED(dc), - const wxRect& WXUNUSED(rectCell), - const wxGridCellAttr& WXUNUSED(attr)) -{ - // as we fill the entire client area, - // don't do anything here to minimize flicker -} - void wxGridCellTextEditor::SetSize(const wxRect& rectOrig) { wxRect rect(rectOrig); @@ -703,22 +751,7 @@ void wxGridCellNumberEditor::SetSize(const wxRect& rectCell) if ( size.y <= 0 ) size.y = rectCell.GetHeight(); - wxRect rectSpin(rectCell.GetPosition(), size); - - // If possible, i.e. if we're not editing the topmost or leftmost cell, - // center the control rectangle in the cell. - if ( rectCell.GetTop() > 0 ) - { - rectSpin.SetTop(rectCell.GetTop() - - (rectSpin.GetHeight() - rectCell.GetHeight()) / 2); - } - if ( rectCell.GetLeft() > 0 ) - { - rectSpin.SetLeft(rectCell.GetLeft() - - (rectSpin.GetWidth() - rectCell.GetWidth()) / 2); - } - - wxGridCellEditor::SetSize(rectSpin); + DoPositionEditor(size, rectCell); } else #endif // wxUSE_SPINCTRL @@ -836,10 +869,19 @@ bool wxGridCellNumberEditor::IsAcceptedKey(wxKeyEvent& event) if ( wxGridCellEditor::IsAcceptedKey(event) ) { int keycode = event.GetKeyCode(); - if ( (keycode < 128) && - (wxIsdigit(keycode) || keycode == '+' || keycode == '-')) + switch ( keycode ) { - return true; + // Accept +/- because they can be part of the number and space just + // because it's a convenient key to start editing with and is also + // consistent with many (all?) other editors, which allow starting + // editing using it. + case '+': + case '-': + case ' ': + return true; + + default: + return (keycode < 128) && wxIsdigit(keycode); } } @@ -1451,25 +1493,14 @@ void wxGridCellChoiceEditor::SetSize(const wxRect& rect) wxASSERT_MSG(m_control, wxT("The wxGridCellChoiceEditor must be created first!")); - // Check that the rectangle is big enough to fit the combobox, we can't - // afford truncating it. - wxSize size = rect.GetSize(); - size.IncTo(m_control->GetBestSize()); + // Use normal wxChoice size, except for extending it to fill the cell + // width: we can't be smaller because this could make the control unusable + // and we don't want to be taller because this looks unusual and weird. + wxSize size = m_control->GetBestSize(); + if ( size.x < rect.width ) + size.x = rect.width; - wxGridCellEditor::SetSize(wxRect(size).CentreIn(rect)); -} - -void wxGridCellChoiceEditor::PaintBackground(wxDC& dc, - const wxRect& rectCell, - const wxGridCellAttr& attr) -{ - // as we fill the entire client area, don't do anything here to minimize - // flicker - - // TODO: It doesn't actually fill the client area since the height of a - // combo always defaults to the standard. Until someone has time to - // figure out the right rectangle to paint, just do it the normal way. - wxGridCellEditor::PaintBackground(dc, rectCell, attr); + DoPositionEditor(size, rect); } void wxGridCellChoiceEditor::BeginEdit(int row, int col, wxGrid* grid) @@ -1780,18 +1811,18 @@ void wxGridCellDateEditor::SetSize(const wxRect& r) { wxASSERT_MSG(m_control, "The wxGridCellDateEditor must be created first!"); - const wxSize bestSize = DatePicker()->GetBestSize(); + wxSize size = DatePicker()->GetBestSize(); - wxRect rect(r.GetPosition(), bestSize); - - // Allow edit picker to become a bit wider, if necessary, but no more than - // twice as wide as the best width, otherwise they just look ugly. - if ( r.GetWidth() > bestSize.GetWidth() ) + // Allow date picker to become a bit wider, if necessary, but not too wide, + // otherwise it just looks ugly. + if ( r.GetWidth() > size.GetWidth() + && r.GetWidth() < 3*size.GetWidth()/2 ) { - rect.SetWidth(wxMin(r.GetWidth(), 2*bestSize.GetWidth())); + size.x = r.GetWidth(); } - wxGridCellEditor::SetSize(rect); + // Use right alignment by default for consistency with the date renderer. + DoPositionEditor(size, r, wxALIGN_RIGHT); } void wxGridCellDateEditor::BeginEdit(int row, int col, wxGrid* grid) diff --git a/src/msw/toplevel.cpp b/src/msw/toplevel.cpp index 4c552f2604..aeabf46f43 100644 --- a/src/msw/toplevel.cpp +++ b/src/msw/toplevel.cpp @@ -290,6 +290,22 @@ WXLRESULT wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX DoRestoreLastFocus(); } + else if ( id == SC_KEYMENU ) + { + // Alt-Backspace is understood as an accelerator for "Undo" + // by the native EDIT control, but pressing it results in a + // beep by default when the resulting SC_KEYMENU is handled + // by DefWindowProc(), so pretend to handle it ourselves if + // we're editing a text control to avoid the annoying beep. + if ( lParam == VK_BACK ) + { + if ( wxWindow* const focus = FindFocus() ) + { + if ( focus->WXGetTextEntry() ) + processed = true; + } + } + } #ifndef __WXUNIVERSAL__ // We need to generate events for the custom items added to the