textctrl.cpp
来自「A*算法 A*算法 A*算法 A*算法A*算法A*算法」· C++ 代码 · 共 2,131 行 · 第 1/5 页
CPP
2,131 行
int rowsNew = lineData.GetExtraRowCount();
if ( rowsNew != rowsOld )
{
// we have to update the line wrap marks as this is normally done
// by LayoutLines() which we bypassed by calling LayoutLine()
// directly
wxTextCoord rowFirst = lineData.GetFirstRow(),
rowCount = wxMax(rowsOld, rowsNew);
RefreshLineWrapMarks(rowFirst, rowFirst + rowCount);
// next, if this is not the last line, as the number of rows in it
// changed, we need to shift all the lines below it
if ( (size_t)line < WData().m_linesData.GetCount() )
{
// number of rows changed shifting all lines below
WData().InvalidateLinesBelow(line + 1);
}
// the number of rows changed
return true;
}
}
else // no line wrap
{
MData().m_lines[line] = text;
}
// the number of rows didn't change
return false;
}
void wxTextCtrl::RemoveLine(wxTextCoord line)
{
MData().m_lines.RemoveAt(line);
if ( WrapLines() )
{
// we need to recalculate all the starting rows from this line, but we
// can avoid doing it if this line was never calculated: this means
// that we will recalculate all lines below it anyhow later if needed
if ( WData().IsValidLine(line) )
{
WData().InvalidateLinesBelow(line);
}
WData().m_linesData.RemoveAt(line);
}
}
void wxTextCtrl::InsertLine(wxTextCoord line, const wxString& text)
{
MData().m_lines.Insert(text, line);
if ( WrapLines() )
{
WData().m_linesData.Insert(new wxWrappedLineData, line);
// invalidate everything below it
WData().InvalidateLinesBelow(line);
}
}
void wxTextCtrl::Replace(wxTextPos from, wxTextPos to, const wxString& text)
{
wxTextCoord colStart, colEnd,
lineStart, lineEnd;
if ( (from > to) ||
!PositionToXY(from, &colStart, &lineStart) ||
!PositionToXY(to, &colEnd, &lineEnd) )
{
wxFAIL_MSG(_T("invalid range in wxTextCtrl::Replace"));
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
// remember the old selection and reset it immediately: we must do it
// before calling Refresh(anything) as, at least under GTK, this leads to
// an _immediate_ repaint (under MSW it is delayed) and hence parts of
// text would be redrawn as selected if we didn't reset the selection
int selStartOld = m_selStart,
selEndOld = m_selEnd;
m_selStart =
m_selEnd = -1;
if ( IsSingleLine() )
{
// 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;
}
// 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 SData().m_colLastVisible update
SData().m_colLastVisible = -1;
// repaint
RefreshPixelRange(0, startNewText, widthNewText);
}
else // multiline
{
//OPT: special case for replacements inside single line?
/*
Join all the lines in the replacement range into one string, then
replace a part of it with the new text and break it into lines again.
*/
// (0) we want to know if this replacement changes the number of rows
// as if it does we need to refresh everything below the changed
// text (it will be shifted...) and we can avoid it if there is no
// row relayout
bool rowsNumberChanged = false;
// (1) join lines
const wxArrayString& linesOld = GetLines();
wxString textOrig;
wxTextCoord line;
for ( line = lineStart; line <= lineEnd; line++ )
{
if ( line > lineStart )
{
// from the previous line
textOrig += _T('\n');
}
textOrig += linesOld[line];
}
// we need to append the '\n' for the last line unless there is no
// following line
size_t countOld = linesOld.GetCount();
// (2) replace text in the combined string
// (2a) leave the part before replaced area unchanged
wxString textNew(textOrig, colStart);
// these values will be used to refresh the changed area below
wxCoord widthNewText,
startNewText = GetTextWidth(textNew);
if ( (size_t)colStart == linesOld[lineStart].length() )
{
// text appended, refresh just enough to show the new text
widthNewText = GetTextWidth(text.BeforeFirst(_T('\n')));
}
else // text inserted, refresh till the end of line
{
widthNewText = 0;
}
// (2b) insert new text
textNew += text;
// (2c) and append the end of the old text
// adjust for index shift: to is relative to colStart, not 0
size_t toRel = (size_t)((to - from) + colStart);
if ( toRel < textOrig.length() )
{
textNew += textOrig.c_str() + toRel;
}
// (3) break it into lines
wxArrayString lines;
const wxChar *curLineStart = textNew.c_str();
for ( const wxChar *p = textNew.c_str(); ; p++ )
{
// end of line/text?
if ( !*p || *p == _T('\n') )
{
lines.Add(wxString(curLineStart, p));
if ( !*p )
break;
curLineStart = p + 1;
}
}
#ifdef WXDEBUG_TEXT_REPLACE
// (3a) all empty tokens should be counted as replacing with "foo" and
// with "foo\n" should have different effects
wxArrayString lines2 = wxStringTokenize(textNew, _T("\n"),
wxTOKEN_RET_EMPTY_ALL);
if ( lines2.IsEmpty() )
{
lines2.Add(wxEmptyString);
}
wxASSERT_MSG( lines.GetCount() == lines2.GetCount(),
_T("Replace() broken") );
for ( size_t n = 0; n < lines.GetCount(); n++ )
{
wxASSERT_MSG( lines[n] == lines2[n], _T("Replace() broken") );
}
#endif // WXDEBUG_TEXT_REPLACE
// (3b) special case: if we replace everything till the end we need to
// keep an empty line or the lines would disappear completely
// (this also takes care of never leaving m_lines empty)
if ( ((size_t)lineEnd == countOld - 1) && lines.IsEmpty() )
{
lines.Add(wxEmptyString);
}
size_t nReplaceCount = lines.GetCount(),
nReplaceLine = 0;
// (4) merge into the array
// (4a) replace
for ( line = lineStart; line <= lineEnd; line++, nReplaceLine++ )
{
if ( nReplaceLine < nReplaceCount )
{
// we have the replacement line for this one
if ( ReplaceLine(line, lines[nReplaceLine]) )
{
rowsNumberChanged = true;
}
UpdateMaxWidth(line);
}
else // no more replacement lines
{
// (4b) delete all extra lines (note that we need to delete
// them backwards because indices shift while we do it)
bool deletedLongestLine = false;
for ( wxTextCoord lineDel = lineEnd; lineDel >= line; lineDel-- )
{
if ( lineDel == MData().m_lineLongest )
{
// we will need to recalc the max line width
deletedLongestLine = true;
}
RemoveLine(lineDel);
}
if ( deletedLongestLine )
{
RecalcMaxWidth();
}
// even the line number changed
rowsNumberChanged = true;
// update line to exit the loop
line = lineEnd + 1;
}
}
// (4c) insert the new lines
if ( nReplaceLine < nReplaceCount )
{
// even the line number changed
rowsNumberChanged = true;
do
{
InsertLine(++lineEnd, lines[nReplaceLine++]);
UpdateMaxWidth(lineEnd);
}
while ( nReplaceLine < nReplaceCount );
}
// (5) now refresh the changed area
// update the (cached) last position first as refresh functions use it
m_posLast += text.length() - to + from;
// we may optimize refresh if the number of rows didn't change - but if
// it did we have to refresh everything below the part we chanegd as
// well as it might have moved
if ( !rowsNumberChanged )
{
// refresh the line we changed
if ( !WrapLines() )
{
RefreshPixelRange(lineStart++, startNewText, widthNewText);
}
else
{
//OPT: we shouldn't refresh the unchanged part of the line in
// this case, but instead just refresh the tail of it - the
// trouble is that we don't know here where does this tail
// start
}
// number of rows didn't change, refresh the updated rows and the
// last one
if ( lineStart <= lineEnd )
RefreshLineRange(lineStart, lineEnd);
}
else // rows number did change
{
if ( !WrapLines() )
{
// refresh only part of the first line
RefreshPixelRange(lineStart++, startNewText, widthNewText);
}
//else: we have to refresh everything as some part of the text
// could be in the previous row before but moved to the next
// one now (due to word wrap)
wxTextCoord lineEnd = GetLines().GetCount() - 1;
if ( lineStart <= lineEnd )
RefreshLineRange(lineStart, lineEnd);
// refresh text rect left below
RefreshLineRange(lineEnd + 1, 0);
// the vert scrollbar might [dis]appear
MData().m_updateScrollbarY = true;
}
// must recalculate it - will do later
m_value.clear();
}
#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
// update the current position: note that we always put the cursor at the
// end of the replacement text
DoSetInsertionPoint(from + text.length());
// and the selection: this is complicated by the fact that selection coords
// must be first updated to reflect change in text coords, i.e. if we had
// selection from 17 to 19 and we just removed this range, we don't have to
// refresh anything, so we can't just use ClearSelection() here
if ( selStartOld != -1 )
{
// refresh the parst of the selection outside the changed text (which
// we already refreshed)
if ( selStartOld < from )
RefreshTextRange(selStartOld, from);
if ( to < selEndOld )
RefreshTextRange(to, selEndOld);
}
// now call it to do the rest (not related to refreshing)
ClearSelection();
}
void wxTextCtrl::Remove(wxTextPos from, wxTextPos to)
{
// Replace() only works with correctly ordered arguments, so exchange them
// if necessary
OrderPositions(from, to);
Replace(from, to, wxEmptyString);
}
void wxTextCtrl::WriteText(const wxString& text)
{
// replace the selection with the new text
RemoveSelection();
Replace(m_curPos, m_curPos, text);
}
void wxTextCtrl::AppendText(const wxString& text)
{
SetInsertionPointEnd();
WriteText(text);
}
// ----------------------------------------------------------------------------
// current position
// ----------------------------------------------------------------------------
void wxTextCtrl::SetInsertionPoint(wxTextPos pos)
{
wxCHECK_RET( pos >= 0 && pos <= GetLastPosition(),
_T("insertion point position out of range") );
// don't do anything if it didn't change
if ( pos != m_curPos )
{
DoSetInsertionPoint(pos);
}
if ( !IsSingleLine() )
{
// moving cursor should reset the stored abscissa (even if the cursor
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?