diff --git a/include/wx/univ/textctrl.h b/include/wx/univ/textctrl.h index 3a5118fc36..9bb08de88a 100644 --- a/include/wx/univ/textctrl.h +++ b/include/wx/univ/textctrl.h @@ -170,7 +170,7 @@ public: virtual void ShowCaret(bool show = TRUE); void HideCaret() { ShowCaret(FALSE); } void CreateCaret(); // for the current font size - wxCoord GetCaretPosition() const; // in pixels + wxCoord GetCaretPosition(long pos = -1) const; // in pixels, def => current // helpers for cursor movement long GetWordStart() const; @@ -256,12 +256,19 @@ protected: // get the extent (width) of the text wxCoord GetTextWidth(const wxString& text) const; + // refresh the text in the given (in logical coords) rect + void RefreshTextRect(wxRect& rect); + // refresh the text in the given range (in logical coords) of this line, if // width is 0, refresh to the end of line void RefreshPixelRange(long line, wxCoord start, wxCoord width); // refresh the text in the given range (in text coords) in this line - void RefreshLineRange(long line, long start, long count); + void RefreshColRange(long line, long start, long count); + + // refresh the text from in the given line range (inclusive), if lineLast + // is -1, refresh all [visible] text after the lineFirst + void RefreshLineRange(long lineFirst, long lineLast = 0); // refresh the text in the given range which can span multiple lines // (this method accepts arguments in any order) @@ -293,11 +300,25 @@ protected: void OnChar(wxKeyEvent& event); void OnSize(wxSizeEvent& event); + // accessors for derived classes (SL stands for single line) + const wxString& GetSLValue() const + { + wxASSERT_MSG( IsSingleLine(), _T("only for single line controls") ); + + return m_value; + } + + void SetSLValue(const wxString& value) + { + wxASSERT_MSG( IsSingleLine(), _T("only for single line controls") ); + m_value = value; + } + private: // the initially specified control size wxSize m_sizeInitial; - // the entire control (only used for single line controls) + // the control text (only used for single line controls) wxString m_value; // the lines of text (only used for multiline controls) diff --git a/src/univ/textctrl.cpp b/src/univ/textctrl.cpp index 3858293b21..833487b9a4 100644 --- a/src/univ/textctrl.cpp +++ b/src/univ/textctrl.cpp @@ -39,6 +39,8 @@ #include "wx/dcclient.h" #include "wx/validate.h" #include "wx/textctrl.h" + + #include "wx/tokenzr.h" #endif #include "wx/clipbrd.h" @@ -154,7 +156,7 @@ bool wxTextCtrl::SetFont(const wxFont& font) void wxTextCtrl::SetValue(const wxString& value) { - if ( IsSingleLine() && (m_value == value) ) + if ( IsSingleLine() && (value == GetSLValue()) ) { // nothing changed return; @@ -168,7 +170,7 @@ wxString wxTextCtrl::GetValue() const wxString value; if ( IsSingleLine() ) { - value = m_value; + value = GetSLValue(); } else // multiline { @@ -219,6 +221,9 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text) wxString lineFirstOrig = GetLineText(lineStart); wxString lineFirst(lineFirstOrig, colStart); + // remember the start of the update region for later use + wxCoord startNewText = GetTextWidth(lineFirst); + // (2) add the text which replaces this part if ( !lines.IsEmpty() ) { @@ -233,24 +238,64 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text) lineFirst += lineFirstOrig.c_str() + (size_t)colEnd; } - // (4) modify the line and refresh the part of the window which changed + // (4) refresh the part of the line which changed + + // we usually refresh till the end of line except of the most common case + // when some text is appended to the end of it in which case we refresh + // just the newly appended text + wxCoord widthNewText; + if ( (lineEnd == lineStart) && + ((size_t)colStart == lineFirstOrig.length()) ) + { + // text appended, not replaced, so refresh only the new text + widthNewText = GetTextWidth(lines[0u]); + } + else + { + // refresh till the end of line as all its tail changed + + // OPT: should only refresh the part occupied by the text, but OTOH + // the line won't be really repainted beyond it anyhow due to the + // checks in DoDrawTextInRect(), so is it really worth it? + + widthNewText = 0; + } + + RefreshPixelRange(lineStart, startNewText, widthNewText); + + // (5) modify the line if ( IsSingleLine() ) { - m_value = lineFirst; + SetSLValue(lineFirst); + + // force m_colLastVisible update + m_colLastVisible = -1; // consistency check: when replacing text in a single line control, we // shouldn't have more than one line wxASSERT_MSG(lines.GetCount() <= 1, _T("can't have more than one line in this wxTextCtrl")); - // nothing more can happen to us + // update the current position + SetInsertionPoint(from + text.length()); + + // and the selection (do it after setting the cursor to have correct value + // for selection anchor) + ClearSelection(); + + // nothing more can happen to us, so bail out + return; } else // multiline { m_lines[lineStart] = lineFirst; } - // now replace all intermediate lines entirely and refresh + // now replace all intermediate lines entirely + + bool refreshAllBelow = FALSE; + + // (1) modify all lines which are really repaced size_t nReplaceLine = 1, nReplaceCount = lines.GetCount(); for ( long line = lineStart + 1; line < lineEnd; line++ ) @@ -260,10 +305,11 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text) // replace line m_lines[line] = lines[nReplaceLine++]; } - else // no more replacement text - remove line + else // no more replacement text - remove line(s) { - // optimization: remove not only this line but all the next ones as - // well + // (2) remove all lines for which there is no more replacement + // text (this is slightly more efficient than continuing to + // run the loop) // adjust the index by the number of lines removed lineEnd -= lineEnd - line; @@ -273,17 +319,32 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text) { m_lines.RemoveAt(line++); } + + // the lines below will scroll up + refreshAllBelow = TRUE; } } - // if there are still lines left in the replacement text, insert them - // before modifying the last line - while ( nReplaceLine < nReplaceCount - 1 ) // OPT: insert all at once? + // (3) if there are still lines left in the replacement text, insert them + // before modifying the last line + if ( nReplaceLine < nReplaceCount - 1 ) { - // insert line and adjust for index change by incrementing lineEnd - m_lines.Insert(lines[nReplaceLine++], lineEnd++); + // the lines below will scroll down + refreshAllBelow = TRUE; + + while ( nReplaceLine < nReplaceCount - 1 ) // OPT: insert all at once? + { + // insert line and adjust for index change by incrementing lineEnd + m_lines.Insert(lines[nReplaceLine++], lineEnd++); + } } + // (4) refresh the lines: if we had replaced exactly the same number of + // lines that we had before, we can just refresh these lines, + // otherwise the lines below will change as well, so we have to + // refresh them too (by passing -1 as RefreshLineRange() argument) + RefreshLineRange(lineStart + 1, refreshAllBelow ? -1 : lineEnd - 1); + // now deal with the last line: (1) replace its beginning with the end of // the replacement text wxString lineLast; @@ -301,56 +362,21 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text) lineLast += lineLastOrig.c_str() + (size_t)colEnd; } + // (3) always refresh the last line entirely if it hadn't been already + // refreshed above + if ( !refreshAllBelow ) + { + RefreshPixelRange(lineEnd, 0, 0); // entire line + } + m_lines[lineEnd] = lineLast; -#if 0 // single line only - // replace the part of the text with the new value - wxString valueNew(m_value, (size_t)from); - - // remember it for later use - wxCoord startNewText = GetTextWidth(valueNew); - - valueNew += text; - if ( (size_t)to < m_value.length() ) - { - valueNew += m_value.c_str() + (size_t)to; - } - - // OPT: is the following really ok? not sure any more now at 2 am... - - // we usually refresh till the end of line except of the most common case - // when some text is appended to the end of the string in which case we - // refresh just it - wxCoord widthNewText; - - if ( (size_t)from < m_value.length() ) - { - // refresh till the end of line - widthNewText = 0; - } - else // text appended, not replaced - { - // refresh only the new text - widthNewText = GetTextWidth(text); - } - - m_value = valueNew; - - // force m_colLastVisible update - m_colLastVisible = -1; - // update the current position - SetInsertionPoint(from + text.length()); + SetInsertionPoint(to); // and the selection (do it after setting the cursor to have correct value // for selection anchor) ClearSelection(); - - // repaint - RefreshPixelRange(0, startNewText, widthNewText); - - // TODO: and the affected parts of the next line(s) -#endif // 0 } void wxTextCtrl::Remove(long from, long to) @@ -403,20 +429,8 @@ void wxTextCtrl::DoSetInsertionPoint(long pos) HideCaret(); m_curPos = pos; - - if ( IsSingleLine() ) - { - m_curLine = 0; - m_curRow = m_curPos; - - // the current position should always be shown, scroll the window - // for this if necessary - ShowHorzPosition(GetCaretPosition()); - } - else // multi line - { - wxFAIL_MSG(_T("unimplemented for multi line")); - } + PositionToXY(m_curPos, &m_curRow, &m_curLine); + ShowPosition(m_curPos); ShowCaret(); } @@ -433,16 +447,31 @@ long wxTextCtrl::GetInsertionPoint() const long wxTextCtrl::GetLastPosition() const { + long pos; if ( IsSingleLine() ) { - return m_value.length(); + pos = GetSLValue().length(); } - else + else // multiline { - wxFAIL_MSG(_T("unimplemented for multi line")); + pos = 0; + size_t nLineCount = m_lines.GetCount(); + for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) + { + // +1 is because the positions at the end of this line and of the + // start of the next one are different + pos += m_lines[nLine].length() + 1; + } - return -1; + if ( pos > 0 ) + { + // the last position is at the end of the last line, not in the + // beginning of the next line after it + pos--; + } } + + return pos; } // ---------------------------------------------------------------------------- @@ -465,12 +494,36 @@ wxString wxTextCtrl::GetSelectionText() const { if ( IsSingleLine() ) { - sel = wxString(m_value.c_str() + m_selStart, - m_value.c_str() + m_selEnd); + sel = GetSLValue().Mid(m_selStart, m_selEnd - m_selStart); } - else + else // multiline { - wxFAIL_MSG(_T("unimplemented for multi line")); + long colStart, lineStart, + colEnd, lineEnd; + PositionToXY(m_selStart, &colStart, &lineStart); + PositionToXY(m_selEnd, &colEnd, &lineEnd); + + // as always, we need to check for the special case when the start + // and end line are the same + if ( lineEnd == lineStart ) + { + sel = m_lines[lineStart].Mid(colStart, colEnd - colStart); + } + else // sel on multiple lines + { + // take the end of the first line + sel = m_lines[lineStart].c_str() + colStart; + sel += _T('\n'); + + // all intermediate ones + for ( long line = lineStart + 1; line < lineEnd; line++ ) + { + sel << m_lines[line] << _T('\n'); + } + + // and the start of the last one + sel += m_lines[lineEnd].Left(colEnd); + } } } @@ -655,13 +708,14 @@ int wxTextCtrl::GetLineLength(long line) const { wxASSERT_MSG( line == 0, _T("invalid GetLineLength() parameter") ); - return m_value.length(); + return GetSLValue().length(); } else // multiline { - wxFAIL_MSG(_T("unimplemented for multi line")); + wxCHECK_MSG( (size_t)line < m_lines.GetCount(), -1, + _T("line index out of range") ); - return 0; + return m_lines[line].length(); } } @@ -675,24 +729,18 @@ wxString wxTextCtrl::GetLineText(long line) const } else // multiline { - wxFAIL_MSG(_T("unimplemented for multi line")); + wxCHECK_MSG( (size_t)line < m_lines.GetCount(), _T(""), + _T("line index out of range") ); - return _T(""); + return m_lines[line]; } } int wxTextCtrl::GetNumberOfLines() const { - if ( IsSingleLine() ) - { - return 1; - } - else // multiline - { - wxFAIL_MSG(_T("unimplemented for multi line")); - - return 0; - } + // for single line controls there is always 1 line, even if the text is + // empty + return IsSingleLine() ? 1 : m_lines.GetCount(); } long wxTextCtrl::XYToPosition(long x, long y) const @@ -705,9 +753,23 @@ long wxTextCtrl::XYToPosition(long x, long y) const } else // multiline { - wxFAIL_MSG(_T("unimplemented for multi line")); + long pos = 0; + for ( size_t nLine = 0; nLine < (size_t)y; nLine++ ) + { + // +1 is because the positions at the end of this line and of the + // start of the next one are different + pos += m_lines[nLine].length() + 1; + } - return -1; + if ( pos > 0 ) + { + // the last position is at the end of the last line, not in the + // beginning of the next line after it + pos--; + } + + // take into account also the position in line + return pos + x; } } @@ -715,7 +777,7 @@ bool wxTextCtrl::PositionToXY(long pos, long *x, long *y) const { if ( IsSingleLine() ) { - if ( pos > GetLastPosition() ) + if ( (size_t)pos > GetSLValue().length() ) return FALSE; if ( x ) @@ -727,15 +789,42 @@ bool wxTextCtrl::PositionToXY(long pos, long *x, long *y) const } else // multiline { - wxFAIL_MSG(_T("unimplemented for multi line")); + size_t nLineCount = m_lines.GetCount(); + for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) + { + long posNew = pos - (m_lines[nLine].length() + 1); + if ( posNew < 0 ) + { + // we've found the line, now just calc the column + if ( x ) + *x = nLine; + if ( y ) + *y = pos; + + return TRUE; + } + else // go further down + { + pos = posNew; + } + } + + // beyond the last line return FALSE; } } void wxTextCtrl::ShowPosition(long pos) { - wxFAIL_MSG(_T("not implemented")); + if ( IsSingleLine() ) + { + ShowHorzPosition(GetCaretPosition(pos)); + } + else + { + wxFAIL_MSG(_T("not implemented for multiline")); + } } // ---------------------------------------------------------------------------- @@ -768,6 +857,9 @@ static inline bool IsWordChar(wxChar ch) long wxTextCtrl::GetWordStart() const { + // TODO + wxCHECK_MSG( IsSingleLine(), -1, _T("not implemented for multline") ); + if ( m_curPos == -1 || m_curPos == 0 ) return 0; @@ -797,6 +889,9 @@ long wxTextCtrl::GetWordStart() const long wxTextCtrl::GetWordEnd() const { + // TODO + wxCHECK_MSG( IsSingleLine(), -1, _T("not implemented for multline") ); + if ( m_curPos == -1 ) return 0; @@ -922,6 +1017,10 @@ void wxTextCtrl::UpdateTextRect() void wxTextCtrl::UpdateLastVisible() { + // this method is only used for horizontal "scrollbarless" scrolling which + // is used only with single line controls + wxASSERT_MSG( IsSingleLine(), _T("shouldn't be called for multiline") ); + // OPT: estimate the correct value first, just adjust it later wxString text; @@ -1184,7 +1283,7 @@ void wxTextCtrl::ShowHorzPosition(wxCoord pos) { // scroll backwards long col; - HitTestLine(GetValue(), pos, &col); + HitTestLine(GetSLValue(), pos, &col); ScrollText(col); } else @@ -1203,7 +1302,7 @@ void wxTextCtrl::ShowHorzPosition(wxCoord pos) { // scroll forward long col; - HitTestLine(GetValue(), pos - width, &col); + HitTestLine(GetSLValue(), pos - width, &col); ScrollText(col + 1); } } @@ -1326,6 +1425,18 @@ void wxTextCtrl::DoPrepareDC(wxDC& dc) // refresh // ---------------------------------------------------------------------------- +void wxTextCtrl::RefreshLineRange(long lineFirst, long lineLast) +{ + wxRect rect; + // rect.x is already 0 + rect.width = m_rectText.width; + wxCoord h = GetCharHeight(); + rect.y = lineFirst*h; + rect.SetBottom(lineLast == -1 ? m_rectText.height : (lineLast + 1)*h); + + RefreshTextRect(rect); +} + void wxTextCtrl::RefreshTextRange(long start, long end) { wxCHECK_RET( start != -1 && end != -1, @@ -1369,11 +1480,11 @@ void wxTextCtrl::RefreshTextRange(long start, long end) posCount = colEnd - posStart; } - RefreshLineRange(line, posStart, posCount); + RefreshColRange(line, posStart, posCount); } } -void wxTextCtrl::RefreshLineRange(long line, long start, long count) +void wxTextCtrl::RefreshColRange(long line, long start, long count) { wxString text = GetLineText(line); @@ -1386,7 +1497,7 @@ void wxTextCtrl::RefreshPixelRange(long line, wxCoord start, wxCoord width) { wxCoord h = GetCharHeight(); wxRect rect; - rect.x = start - m_ofsHorz; + rect.x = start; rect.y = line*h; if ( width == 0 ) @@ -1402,6 +1513,17 @@ void wxTextCtrl::RefreshPixelRange(long line, wxCoord start, wxCoord width) rect.height = h; + RefreshTextRect(rect); +} + +void wxTextCtrl::RefreshTextRect(wxRect& rect) +{ + if ( IsSingleLine() ) + { + // account for horz scrolling + rect.x -= m_ofsHorz; + } + // account for the text area offset rect.Offset(m_rectText.GetPosition()); @@ -1568,7 +1690,7 @@ void wxTextCtrl::DoDrawTextInRect(wxDC& dc, const wxRect& rectUpdate) // extract the part of line we need to redraw wxString textLine = GetLineText(line); - wxString text = textLine.Mid(colStart, colEnd - colStart + 1); + wxString text = textLine.Mid(colStart, colEnd - colStart); // now deal with the selection int selStart, selEnd; @@ -1673,9 +1795,10 @@ void wxTextCtrl::CreateCaret() SetCaret(caret); } -wxCoord wxTextCtrl::GetCaretPosition() const +wxCoord wxTextCtrl::GetCaretPosition(long pos = -1) const { - wxString textBeforeCaret(GetLineText(m_curLine), (size_t)m_curRow); + wxString textBeforeCaret(GetLineText(m_curLine), + (size_t)(pos == -1 ? m_curRow : pos)); return GetTextWidth(textBeforeCaret); }