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 + -
显示快捷键?