basic drawing and cursor movement seem to work in multiline textctrl

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/wxUNIVERSAL@8559 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2000-10-14 23:34:47 +00:00
parent b16e3fb5e8
commit 649850e16c
5 changed files with 203 additions and 66 deletions

6
TODO
View File

@@ -6,7 +6,6 @@ wxTextCtrl
* display corrupted when typing text in quickly * display corrupted when typing text in quickly
* text ctrl display pb when text is truncated * text ctrl display pb when text is truncated
* listbox: horz scrollbar doesn't appear
All All
@@ -22,6 +21,8 @@ All
MSW MSW
wxTextCtrl caret is not shown until *next* refresh
GTK GTK
* listbox scrolling leaves unpainted areas * listbox scrolling leaves unpainted areas
@@ -35,7 +36,8 @@ DONE
All All
+ text ctrl horz scrolling + text ctrl horz scrolling
+ DoDraw should iterat over update region instead of using bounding box + DoDraw should iterate over update region instead of using bounding box
+ listbox: horz scrollbar doesn't appear
MSW MSW

View File

@@ -326,7 +326,7 @@ private:
// current position // current position
long m_curPos, long m_curPos,
m_curLine, m_curCol,
m_curRow; m_curRow;
// selection // selection

View File

@@ -53,7 +53,7 @@
#include "wx/univ/theme.h" #include "wx/univ/theme.h"
//#define TEST_TEXT_ONLY #define TEST_TEXT_ONLY
//#define DEBUG_SCROLL //#define DEBUG_SCROLL
//#define DEBUG_LISTBOX //#define DEBUG_LISTBOX
@@ -233,14 +233,14 @@ MyUnivFrame::MyUnivFrame(const wxString& title)
#ifndef TEST_TEXT_ONLY #ifndef TEST_TEXT_ONLY
wxSize(700, 700) wxSize(700, 700)
#else #else
wxSize(240, 200) wxSize(240, 300)
#endif #endif
) )
{ {
SetBackgroundColour(wxGetApp().GetBgColour()); SetBackgroundColour(wxGetApp().GetBgColour());
new wxStaticText(this, _T("Test static text"), wxPoint(10, 10));
#ifndef TEST_TEXT_ONLY #ifndef TEST_TEXT_ONLY
new wxStaticText(this, _T("Test static text"), wxPoint(10, 10));
new wxStaticText(this, new wxStaticText(this,
_T("&Multi line\n(and very very very very long)\nstatic text"), _T("&Multi line\n(and very very very very long)\nstatic text"),
wxPoint(210, 10)); wxPoint(210, 10));
@@ -383,6 +383,7 @@ MyUnivFrame::MyUnivFrame(const wxString& title)
new wxTextCtrl(this, -1, _T("Hello, Universe!"), new wxTextCtrl(this, -1, _T("Hello, Universe!"),
wxPoint(550, 150), wxDefaultSize); wxPoint(550, 150), wxDefaultSize);
#else // TEST_TEXT_ONLY #else // TEST_TEXT_ONLY
#if 0
wxTextCtrl *text = new wxTextCtrl(this, -1, _T("Hello, Universe!"), wxTextCtrl *text = new wxTextCtrl(this, -1, _T("Hello, Universe!"),
wxPoint(10, 40)); wxPoint(10, 40));
text->SetFont(wxFont(24, wxFONTFAMILY_DEFAULT, text->SetFont(wxFont(24, wxFONTFAMILY_DEFAULT,
@@ -390,6 +391,12 @@ MyUnivFrame::MyUnivFrame(const wxString& title)
wxSize sizeText = text->GetBestSize(); wxSize sizeText = text->GetBestSize();
sizeText.x = 200; sizeText.x = 200;
text->SetSize(sizeText); text->SetSize(sizeText);
#else
wxTextCtrl *
#endif
text = new wxTextCtrl(this, -1, _T("Hello,\nMultiverse!"),
wxPoint(10, 10), wxDefaultSize,
wxTE_MULTILINE);
#endif // !TEST_TEXT_ONLY/TEST_TEXT_ONLY #endif // !TEST_TEXT_ONLY/TEST_TEXT_ONLY
} }

View File

@@ -206,10 +206,10 @@ wxArrayString wxStringTokenize(const wxString& str,
wxStringTokenizerMode mode) wxStringTokenizerMode mode)
{ {
wxArrayString tokens; wxArrayString tokens;
wxStringTokenizer tk(str, delimes, mode); wxStringTokenizer tk(str, delims, mode);
while ( tk.HasMoreTokens() ) while ( tk.HasMoreTokens() )
{ {
tokens.Add(GetNextToken()); tokens.Add(tk.GetNextToken());
} }
return tokens; return tokens;

View File

@@ -39,10 +39,10 @@
#include "wx/dcclient.h" #include "wx/dcclient.h"
#include "wx/validate.h" #include "wx/validate.h"
#include "wx/textctrl.h" #include "wx/textctrl.h"
#include "wx/tokenzr.h"
#endif #endif
#include "wx/tokenzr.h"
#include "wx/clipbrd.h" #include "wx/clipbrd.h"
#include "wx/caret.h" #include "wx/caret.h"
@@ -55,6 +55,9 @@
// turn extra wxTextCtrl-specific debugging on/off // turn extra wxTextCtrl-specific debugging on/off
#define WXDEBUG_TEXT #define WXDEBUG_TEXT
// turn wxTextCtrl::Replace() debugging on (very inefficient!)
#define WXDEBUG_TEXT_REPLACE
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// private functions // private functions
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -100,8 +103,8 @@ void wxTextCtrl::Init()
m_posLastVisible = -1; m_posLastVisible = -1;
m_curPos = m_curPos =
m_curRow = m_curCol =
m_curLine = 0; m_curRow = 0;
} }
bool wxTextCtrl::Create(wxWindow *parent, bool wxTextCtrl::Create(wxWindow *parent,
@@ -121,6 +124,12 @@ bool wxTextCtrl::Create(wxWindow *parent,
SetCursor(wxCURSOR_IBEAM); SetCursor(wxCURSOR_IBEAM);
// we should always have at least one line in a multiline control
if ( style & wxTE_MULTILINE )
{
m_lines.Add(wxEmptyString);
}
SetValue(value); SetValue(value);
SetBestSize(size); SetBestSize(size);
@@ -178,7 +187,7 @@ wxString wxTextCtrl::GetValue() const
for ( size_t n = 0; n < count; n++ ) for ( size_t n = 0; n < count; n++ )
{ {
if ( n ) if ( n )
value += _T('\n'); value += _T('\n'); // from preceding line
value += m_lines[n]; value += m_lines[n];
} }
@@ -193,11 +202,28 @@ void wxTextCtrl::Clear()
} }
/* /*
Replace() works line by line: in the range [from, to] we have the starting The algorithm of Replace():
line in which we replace the text in [from, last one ? to : end of line],
the last one (which may be the same as the first one) and some number 1. change the line where replacement starts
(possibly 0) of intermediate ones). a) keep the text in the beginning of it unchanged
b) replace the middle (if lineEnd == lineStart) or everything to the
end with the first line of replacement text
2. delete all lines between lineStart and lineEnd (excluding)
3. insert the lines of the replacement text
4. change the line where replacement ends:
a) remove the part which is in replacement range
b) insert the last line of replacement text
c) insert the end of the first line if lineEnd == lineStart
d) keep the end unchanged
In the code below the steps 2 and 3 are merged and are done in parallel for
efficiency reasons (it is better to change lines in place rather than
remove/insert them from a potentially huge array)
*/ */
void wxTextCtrl::Replace(long from, long to, const wxString& text) void wxTextCtrl::Replace(long from, long to, const wxString& text)
{ {
long colStart, colEnd, long colStart, colEnd,
@@ -212,14 +238,25 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
return; return;
} }
#ifdef WXDEBUG_TEXT_REPLACE
// a straighforward (but very inefficient) way of calculating what the new
// value should be
wxString textTotal = GetValue();
wxString textTotalNew(textTotal, (size_t)from);
textTotalNew += text;
if ( (size_t)to < textTotal.length() )
textTotalNew += textTotal.c_str() + (size_t)to;
#endif // WXDEBUG_TEXT_REPLACE
// break the replacement text into lines // break the replacement text into lines
wxArrayString lines = wxStringTokenize(text, _T("\n"), wxArrayString lines = wxStringTokenize(text, _T("\n"),
wxTOKEN_RET_EMPTY_ALL); wxTOKEN_RET_EMPTY_ALL);
size_t nReplaceCount = lines.GetCount();
// first deal with the starting line: (1) take the part before the text // first deal with the starting line: (1) take the part before the text
// being replaced // being replaced
wxString lineFirstOrig = GetLineText(lineStart); wxString lineFirstOrig = GetLineText(lineStart);
wxString lineFirst(lineFirstOrig, colStart); wxString lineFirst(lineFirstOrig, (size_t)colStart);
// remember the start of the update region for later use // remember the start of the update region for later use
wxCoord startNewText = GetTextWidth(lineFirst); wxCoord startNewText = GetTextWidth(lineFirst);
@@ -231,11 +268,22 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
} }
// (3) and add the text which is left if we replace text only in this line // (3) and add the text which is left if we replace text only in this line
wxString lineFirstEnd;
if ( lineEnd == lineStart ) if ( lineEnd == lineStart )
{ {
wxASSERT_MSG((size_t)colEnd < lineFirstOrig.length(), _T("logic bug")); wxASSERT_MSG((size_t)colEnd <= lineFirstOrig.length(), _T("logic bug"));
lineFirst += lineFirstOrig.c_str() + (size_t)colEnd; lineFirstEnd = lineFirstOrig.c_str() + (size_t)colEnd;
if ( nReplaceCount == 1 )
{
// we keep everything on this line
lineFirst += lineFirstEnd;
}
else
{
lineEnd++;
}
} }
// (4) refresh the part of the line which changed // (4) refresh the part of the line which changed
@@ -296,8 +344,7 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
bool refreshAllBelow = FALSE; bool refreshAllBelow = FALSE;
// (1) modify all lines which are really repaced // (1) modify all lines which are really repaced
size_t nReplaceLine = 1, size_t nReplaceLine = 1;
nReplaceCount = lines.GetCount();
for ( long line = lineStart + 1; line < lineEnd; line++ ) for ( long line = lineStart + 1; line < lineEnd; line++ )
{ {
if ( nReplaceLine < nReplaceCount ) if ( nReplaceLine < nReplaceCount )
@@ -343,40 +390,63 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
// lines that we had before, we can just refresh these lines, // lines that we had before, we can just refresh these lines,
// otherwise the lines below will change as well, so we have to // otherwise the lines below will change as well, so we have to
// refresh them too (by passing -1 as RefreshLineRange() argument) // refresh them too (by passing -1 as RefreshLineRange() argument)
RefreshLineRange(lineStart + 1, refreshAllBelow ? -1 : lineEnd - 1); if ( refreshAllBelow || (lineStart < lineEnd - 1) )
{
RefreshLineRange(lineStart + 1, refreshAllBelow ? -1 : lineEnd - 1);
}
// now deal with the last line: (1) replace its beginning with the end of // now deal with the last line: (1) replace its beginning with the end of
// the replacement text // the replacement text
wxString lineLast; wxString lineLast;
if ( nReplaceLine < nReplaceCount ) if ( nReplaceLine < nReplaceCount )
{ {
wxASSERT_MSG(nReplaceCount == nReplaceCount - 1, _T("logic error")); wxASSERT_MSG(nReplaceLine == nReplaceCount - 1, _T("logic error"));
lineLast = lines[nReplaceLine]; lineLast = lines[nReplaceLine];
} }
// (2) add the tail of the old last line if anything is left // (2) add the text which was at the end of first line if we replaced its
wxString lineLastOrig = GetLineText(lineEnd); // middle with multiline text
if ( (size_t)colEnd < lineLastOrig.length() ) if ( lineEnd == lineStart )
{ {
lineLast += lineLastOrig.c_str() + (size_t)colEnd; lineLast += lineFirstEnd;
} }
// (3) always refresh the last line entirely if it hadn't been already // (3) add the tail of the old last line if anything is left
if ( (size_t)lineEnd < m_lines.GetCount() )
{
wxString lineLastOrig = GetLineText(lineEnd);
if ( (size_t)colEnd < lineLastOrig.length() )
{
lineLast += lineLastOrig.c_str() + (size_t)colEnd;
}
m_lines[lineEnd] = lineLast;
}
else // the number of lines increased, just append the new one
{
m_lines.Add(lineLast);
}
// (4) always refresh the last line entirely if it hadn't been already
// refreshed above // refreshed above
if ( !refreshAllBelow ) if ( !refreshAllBelow )
{ {
RefreshPixelRange(lineEnd, 0, 0); // entire line RefreshPixelRange(lineEnd, 0, 0); // entire line
} }
m_lines[lineEnd] = lineLast;
// update the current position // update the current position
SetInsertionPoint(to); SetInsertionPoint(to);
// and the selection (do it after setting the cursor to have correct value // and the selection (do it after setting the cursor to have correct value
// for selection anchor) // for selection anchor)
ClearSelection(); ClearSelection();
#ifdef WXDEBUG_TEXT_REPLACE
// optimized code above should give the same result as straightforward
// computation in the beginning
wxASSERT_MSG( GetValue() == textTotalNew, _T("error in Replace()") );
#endif // WXDEBUG_TEXT_REPLACE
} }
void wxTextCtrl::Remove(long from, long to) void wxTextCtrl::Remove(long from, long to)
@@ -429,7 +499,7 @@ void wxTextCtrl::DoSetInsertionPoint(long pos)
HideCaret(); HideCaret();
m_curPos = pos; m_curPos = pos;
PositionToXY(m_curPos, &m_curRow, &m_curLine); PositionToXY(m_curPos, &m_curCol, &m_curRow);
ShowPosition(m_curPos); ShowPosition(m_curPos);
ShowCaret(); ShowCaret();
@@ -702,6 +772,14 @@ void wxTextCtrl::SetEditable(bool editable)
// col/lines <-> position correspondence // col/lines <-> position correspondence
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/*
A few remarks about this stuff:
o The numbering of the text control columns/rows starts from 0.
o Start of first line is position 0, its last position is line.length()
o Start of the next line is the last position of the previous line + 1
*/
int wxTextCtrl::GetLineLength(long line) const int wxTextCtrl::GetLineLength(long line) const
{ {
if ( IsSingleLine() ) if ( IsSingleLine() )
@@ -738,8 +816,7 @@ wxString wxTextCtrl::GetLineText(long line) const
int wxTextCtrl::GetNumberOfLines() const int wxTextCtrl::GetNumberOfLines() const
{ {
// for single line controls there is always 1 line, even if the text is // there is always 1 line, even if the text is empty
// empty
return IsSingleLine() ? 1 : m_lines.GetCount(); return IsSingleLine() ? 1 : m_lines.GetCount();
} }
@@ -753,6 +830,12 @@ long wxTextCtrl::XYToPosition(long x, long y) const
} }
else // multiline else // multiline
{ {
if ( (size_t)y >= m_lines.GetCount() )
{
// this position is below the text
return GetLastPosition();
}
long pos = 0; long pos = 0;
for ( size_t nLine = 0; nLine < (size_t)y; nLine++ ) for ( size_t nLine = 0; nLine < (size_t)y; nLine++ )
{ {
@@ -761,14 +844,13 @@ long wxTextCtrl::XYToPosition(long x, long y) const
pos += m_lines[nLine].length() + 1; pos += m_lines[nLine].length() + 1;
} }
if ( pos > 0 ) // take into account also the position in line
if ( (size_t)x > m_lines[y].length() )
{ {
// the last position is at the end of the last line, not in the // don't return position in the next line
// beginning of the next line after it x = m_lines[y].length();
pos--;
} }
// take into account also the position in line
return pos + x; return pos + x;
} }
} }
@@ -789,24 +871,32 @@ bool wxTextCtrl::PositionToXY(long pos, long *x, long *y) const
} }
else // multiline else // multiline
{ {
long posCur = 0;
size_t nLineCount = m_lines.GetCount(); size_t nLineCount = m_lines.GetCount();
for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
{ {
long posNew = pos - (m_lines[nLine].length() + 1); // +1 is because the start the start of the next line is one
if ( posNew < 0 ) // position after the end of this one
long posNew = posCur + m_lines[nLine].length() + 1;
if ( posNew > pos )
{ {
// we've found the line, now just calc the column // we've found the line, now just calc the column
if ( x ) if ( x )
*x = nLine; *x = pos - posCur;
if ( y ) if ( y )
*y = pos; *y = nLine;
#ifdef WXDEBUG_TEXT
wxASSERT_MSG( XYToPosition(pos - posCur, nLine) == pos,
_T("XYToPosition() or PositionToXY() broken") );
#endif // WXDEBUG_TEXT
return TRUE; return TRUE;
} }
else // go further down else // go further down
{ {
pos = posNew; posCur = posNew;
} }
} }
@@ -821,9 +911,9 @@ void wxTextCtrl::ShowPosition(long pos)
{ {
ShowHorzPosition(GetCaretPosition(pos)); ShowHorzPosition(GetCaretPosition(pos));
} }
else else // multiline
{ {
wxFAIL_MSG(_T("not implemented for multiline")); // TODO
} }
} }
@@ -999,6 +1089,17 @@ wxSize wxTextCtrl::DoGetBestClientSize() const
if ( h < hChar ) if ( h < hChar )
h = hChar; h = hChar;
if ( !IsSingleLine() )
{
// let the control have a reasonable number of lines
int lines = GetNumberOfLines();
if ( lines < 5 )
lines = 5;
else if ( lines > 10 )
lines = 10;
h *= 10;
}
wxRect rectText; wxRect rectText;
rectText.width = w; rectText.width = w;
rectText.height = h; rectText.height = h;
@@ -1019,7 +1120,8 @@ void wxTextCtrl::UpdateLastVisible()
{ {
// this method is only used for horizontal "scrollbarless" scrolling which // this method is only used for horizontal "scrollbarless" scrolling which
// is used only with single line controls // is used only with single line controls
wxASSERT_MSG( IsSingleLine(), _T("shouldn't be called for multiline") ); if ( !IsSingleLine() )
return;
// OPT: estimate the correct value first, just adjust it later // OPT: estimate the correct value first, just adjust it later
@@ -1427,6 +1529,9 @@ void wxTextCtrl::DoPrepareDC(wxDC& dc)
void wxTextCtrl::RefreshLineRange(long lineFirst, long lineLast) void wxTextCtrl::RefreshLineRange(long lineFirst, long lineLast)
{ {
wxASSERT_MSG( (lineLast == -1) || (lineFirst <= lineLast),
_T("no lines to refresh") );
wxRect rect; wxRect rect;
// rect.x is already 0 // rect.x is already 0
rect.width = m_rectText.width; rect.width = m_rectText.width;
@@ -1671,22 +1776,27 @@ void wxTextCtrl::DoDrawTextInRect(wxDC& dc, const wxRect& rectUpdate)
if ( colEnd < colStart ) if ( colEnd < colStart )
colEnd = colStart; colEnd = colStart;
// don't draw the chars beyond the rightmost one // for single line controls we may additionally cut off everything
if ( m_colLastVisible == -1 ) // which is to the right of the last visible position
if ( IsSingleLine() )
{ {
// recalculate this rightmost column // don't draw the chars beyond the rightmost one
UpdateLastVisible(); if ( m_colLastVisible == -1 )
} {
// recalculate this rightmost column
UpdateLastVisible();
}
if ( colStart > m_colLastVisible ) if ( colStart > m_colLastVisible )
{ {
// don't bother redrawing something that is beyond the last // don't bother redrawing something that is beyond the last
// visible position // visible position
continue; continue;
} }
if ( colEnd > m_colLastVisible ) if ( colEnd > m_colLastVisible )
colEnd = m_colLastVisible; colEnd = m_colLastVisible;
}
// extract the part of line we need to redraw // extract the part of line we need to redraw
wxString textLine = GetLineText(line); wxString textLine = GetLineText(line);
@@ -1795,10 +1905,10 @@ void wxTextCtrl::CreateCaret()
SetCaret(caret); SetCaret(caret);
} }
wxCoord wxTextCtrl::GetCaretPosition(long pos = -1) const wxCoord wxTextCtrl::GetCaretPosition(long pos) const
{ {
wxString textBeforeCaret(GetLineText(m_curLine), wxString textBeforeCaret(GetLineText(m_curRow),
(size_t)(pos == -1 ? m_curRow : pos)); (size_t)(pos == -1 ? m_curCol : pos));
return GetTextWidth(textBeforeCaret); return GetTextWidth(textBeforeCaret);
} }
@@ -1810,7 +1920,7 @@ void wxTextCtrl::ShowCaret(bool show)
{ {
// position it correctly // position it correctly
caret->Move(m_rectText.x + GetCaretPosition() - m_ofsHorz, caret->Move(m_rectText.x + GetCaretPosition() - m_ofsHorz,
m_rectText.y + m_curLine*GetCharHeight()); m_rectText.y + m_curRow*GetCharHeight());
// and show it there // and show it there
caret->Show(show); caret->Show(show);
@@ -1856,11 +1966,21 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
if ( action == wxACTION_TEXT_HOME ) if ( action == wxACTION_TEXT_HOME )
{ {
newPos = m_curPos - m_curRow; newPos = m_curPos - m_curCol;
} }
else if ( action == wxACTION_TEXT_END ) else if ( action == wxACTION_TEXT_END )
{ {
newPos = m_curPos + GetLineLength(m_curLine) - m_curRow; newPos = m_curPos + GetLineLength(m_curRow) - m_curCol;
}
else if ( action == wxACTION_TEXT_UP )
{
if ( m_curRow > 0 )
newPos = XYToPosition(m_curCol, m_curRow - 1);
}
else if ( action == wxACTION_TEXT_DOWN )
{
if ( (size_t)m_curRow < m_lines.GetCount() )
newPos = XYToPosition(m_curCol, m_curRow + 1);
} }
else if ( action == wxACTION_TEXT_LEFT ) else if ( action == wxACTION_TEXT_LEFT )
{ {
@@ -2050,6 +2170,14 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control,
action << wxACTION_TEXT_END; action << wxACTION_TEXT_END;
break; break;
case WXK_UP:
action << wxACTION_TEXT_UP;
break;
case WXK_DOWN:
action << wxACTION_TEXT_DOWN;
break;
case WXK_LEFT: case WXK_LEFT:
action << (ctrlDown ? wxACTION_TEXT_WORD_LEFT action << (ctrlDown ? wxACTION_TEXT_WORD_LEFT
: wxACTION_TEXT_LEFT); : wxACTION_TEXT_LEFT);