From 0ec98fd6c98c9605cbe7f5cf3c31230ff7f236b7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 16 Jun 2013 10:50:54 +0000 Subject: [PATCH] Improve wxGrid cell wrapping in wxGridCellAutoWrapStringRenderer. Wrap the words too long to be shown on one line on several lines. Also take the line breaks and TABs into account. Closes #15249. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@74245 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 1 + include/wx/generic/gridctrl.h | 22 +++++ samples/grid/griddemo.cpp | 15 ++++ src/generic/gridctrl.cpp | 151 ++++++++++++++++++++++++++-------- 4 files changed, 153 insertions(+), 36 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 4cd8f39acb..5050dd6154 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -671,6 +671,7 @@ All (GUI): - Fix wxStyledTextCtrl::SetInsertionPointEnd() (troelsk). - Add wxFileDialog::GetCurrentlySelectedFilename() (Carl Godkin). - Add wxMouseEvent::GetColumnsPerAction() (toiffel). +- Improve wrapping of cell contents in wxGrid (nmset). wxGTK: diff --git a/include/wx/generic/gridctrl.h b/include/wx/generic/gridctrl.h index b9ee0a1a0f..d1e82b40eb 100644 --- a/include/wx/generic/gridctrl.h +++ b/include/wx/generic/gridctrl.h @@ -249,6 +249,28 @@ private: const wxRect& rect, int row, int col); + // Helper methods of GetTextLines() + + // Break a single logical line of text into several physical lines, all of + // which are added to the lines array. The lines are broken at maxWidth and + // the dc is used for measuring text extent only. + void BreakLine(wxDC& dc, + const wxString& logicalLine, + wxCoord maxWidth, + wxArrayString& lines); + + // Break a word, which is supposed to be wider than maxWidth, into several + // lines, which are added to lines array and the last, incomplete, of which + // is returned in line output parameter. + // + // Returns the width of the last line. + wxCoord BreakWord(wxDC& dc, + const wxString& word, + wxCoord maxWidth, + wxArrayString& lines, + wxString& line); + + }; #endif // wxUSE_GRID diff --git a/samples/grid/griddemo.cpp b/samples/grid/griddemo.cpp index 9674b3ef79..31e27af240 100644 --- a/samples/grid/griddemo.cpp +++ b/samples/grid/griddemo.cpp @@ -450,6 +450,21 @@ GridFrame::GridFrame() grid->SetCellValue( 0, 4, wxT("Can veto edit this cell") ); + grid->SetColSize(10, 150); + wxString longtext = wxT("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n\n"); + longtext += wxT("With tabs :\n"); + longtext += wxT("Home,\t\thome\t\t\tagain\n"); + longtext += wxT("Long word at start :\n"); + longtext += wxT("ILikeToBeHereWhen I can\n"); + longtext += wxT("Long word in the middle :\n"); + longtext += wxT("When IComeHome,ColdAnd tired\n"); + longtext += wxT("Long last word :\n"); + longtext += wxT("It's GoodToWarmMyBonesBesideTheFire"); + grid->SetCellValue( 0, 10, longtext ); + grid->SetCellRenderer(0 , 10, new wxGridCellAutoWrapStringRenderer); + grid->SetCellEditor( 0, 10 , new wxGridCellAutoWrapStringEditor); + grid->SetCellValue( 0, 11, wxT("K1 cell editor blocker") ); + grid->SetCellValue( 0, 5, wxT("Press\nCtrl+arrow\nto skip over\ncells") ); grid->SetRowSize( 99, 60 ); diff --git a/src/generic/gridctrl.cpp b/src/generic/gridctrl.cpp index 65b7edd192..734775d15c 100644 --- a/src/generic/gridctrl.cpp +++ b/src/generic/gridctrl.cpp @@ -295,58 +295,137 @@ wxGridCellAutoWrapStringRenderer::GetTextLines(wxGrid& grid, const wxRect& rect, int row, int col) { - wxString data = grid.GetCellValue(row, col); - - wxArrayString lines; dc.SetFont(attr.GetFont()); + const wxCoord maxWidth = rect.GetWidth(); - //Taken from wxGrid again! - wxCoord x = 0, y = 0, curr_x = 0; - wxCoord max_x = rect.GetWidth(); - - dc.SetFont(attr.GetFont()); - wxStringTokenizer tk(data , wxT(" \n\t\r")); - wxString thisline = wxEmptyString; + // Transform logical lines into physical ones, wrapping the longer ones. + const wxArrayString + logicalLines = wxSplit(grid.GetCellValue(row, col), '\n', '\0'); - while ( tk.HasMoreTokens() ) + wxArrayString physicalLines; + for ( wxArrayString::const_iterator it = logicalLines.begin(); + it != logicalLines.end(); + ++it ) { - wxString tok = tk.GetNextToken(); - //FIXME: this causes us to print an extra unnecessary - // space at the end of the line. But it - // is invisible , simplifies the size calculation - // and ensures tokens are separated in the display - tok += wxT(" "); + const wxString& line = *it; + + if ( dc.GetTextExtent(line).x > maxWidth ) + { + // Line does not fit, break it up. + BreakLine(dc, line, maxWidth, physicalLines); + } + else // The entire line fits as is + { + physicalLines.push_back(line); + } + } - dc.GetTextExtent(tok, &x, &y); - if ( curr_x + x > max_x) + return physicalLines; +} + +void +wxGridCellAutoWrapStringRenderer::BreakLine(wxDC& dc, + const wxString& logicalLine, + wxCoord maxWidth, + wxArrayString& lines) +{ + wxCoord lineWidth = 0; + wxString line; + + // For each word + wxStringTokenizer wordTokenizer(logicalLine, wxS(" \t"), wxTOKEN_RET_DELIMS); + while ( wordTokenizer.HasMoreTokens() ) + { + const wxString word = wordTokenizer.GetNextToken(); + const wxCoord wordWidth = dc.GetTextExtent(word).x; + if ( lineWidth + wordWidth < maxWidth ) { - if ( curr_x == 0 ) + // Word fits, just add it to this line. + line += word; + lineWidth += wordWidth; + } + else + { + // Word does not fit, check whether the word is itself wider that + // available width + if ( wordWidth < maxWidth ) { - // this means that a single token is wider than the maximal - // width -- still use it as is as we need to show at least the - // part of it which fits - lines.Add(tok); + // Word can fit in a new line, put it at the beginning + // of the new line. + lines.push_back(line); + line = word; + lineWidth = wordWidth; } - else + else // Word cannot fit in available width at all. { - lines.Add(thisline); - thisline = tok; - curr_x = x; + if ( !line.empty() ) + { + lines.push_back(line); + line.clear(); + lineWidth = 0; + } + + // Break it up in several lines. + lineWidth = BreakWord(dc, word, maxWidth, lines, line); } } - else - { - thisline+= tok; - curr_x += x; - } } - //Add last line - lines.Add( wxString(thisline) ); - return lines; + if ( !line.empty() ) + lines.push_back(line); } +wxCoord +wxGridCellAutoWrapStringRenderer::BreakWord(wxDC& dc, + const wxString& word, + wxCoord maxWidth, + wxArrayString& lines, + wxString& line) +{ + wxArrayInt widths; + dc.GetPartialTextExtents(word, widths); + + // TODO: Use binary search to find the first element > maxWidth. + const unsigned count = widths.size(); + unsigned n; + for ( n = 0; n < count; n++ ) + { + if ( widths[n] > maxWidth ) + break; + } + + if ( n == 0 ) + { + // This is a degenerate case: the first character of the word is + // already wider than the available space, so we just can't show it + // completely and have to put the first character in this line. + n = 1; + } + + lines.push_back(word.substr(0, n)); + + // Check if the remainder of the string fits in one line. + // + // Unfortunately we can't use the existing partial text extents as the + // extent of the remainder may be different when it's rendered in a + // separate line instead of as part of the same one, so we have to + // recompute it. + const wxString rest = word.substr(n); + const wxCoord restWidth = dc.GetTextExtent(rest).x; + if ( restWidth <= maxWidth ) + { + line = rest; + return restWidth; + } + + // Break the rest of the word into lines. + // + // TODO: Perhaps avoid recursion? The code is simpler like this but using a + // loop in this function would probably be more efficient. + return BreakWord(dc, rest, maxWidth, lines, line); +} + wxSize wxGridCellAutoWrapStringRenderer::GetBestSize(wxGrid& grid, wxGridCellAttr& attr, -- 2.47.2