Skip to content
Snippets Groups Projects
basetexteditor.cpp 189 KiB
Newer Older
    // ### this code needs to be generalized
    if    ((lookBehind == QLatin1Char('(') && lookAhead == QLatin1Char(')'))
        || (lookBehind == QLatin1Char('[') && lookAhead == QLatin1Char(']'))
        || (lookBehind == QLatin1Char('"') && lookAhead == QLatin1Char('"')
            && lookFurtherBehind != QLatin1Char('\\'))
        || (lookBehind == QLatin1Char('\'') && lookAhead == QLatin1Char('\'')
            && lookFurtherBehind != QLatin1Char('\\'))) {
        if (! isInComment(c)) {
            cursor.beginEditBlock();
            cursor.deleteChar();
            cursor.deletePreviousChar();
            cursor.endEditBlock();
            return true;
        }
    }
int BaseTextEditor::paragraphSeparatorAboutToBeInserted(QTextCursor &cursor)
    if (characterAt(cursor.position()-1) != QLatin1Char('{'))
        return 0;

    if (!contextAllowsAutoParentheses(cursor))
        return 0;

    // verify that we indeed do have an extra opening brace in the document
    int braceDepth = document()->lastBlock().userState();
    if (braceDepth >= 0)
        braceDepth >>= 8;
    else
        braceDepth= 0;

    if (braceDepth <= 0)
        return 0; // braces are all balanced or worse, no need to do anything

    // we have an extra brace , let's see if we should close it


    /* verify that the next block is not further intended compared to the current block.
       This covers the following case:

            if (condition) {|
                statement;
    */
    const TabSettings &ts = tabSettings();
    QTextBlock block = cursor.block();
    int indentation = ts.indentationColumn(block.text());
    if (block.next().isValid()
        && ts.indentationColumn(block.next().text()) > indentation)
        return 0;

    int pos = cursor.position();

    const QString textToInsert = insertParagraphSeparator(cursor);

    cursor.insertText(textToInsert);
    cursor.setPosition(pos);
    if (ts.m_autoIndent) {
        cursor.insertBlock();
        indent(document(), cursor, QChar::Null);
    } else {
        QString previousBlockText = cursor.block().text();
        cursor.insertBlock();
        cursor.insertText(ts.indentationString(previousBlockText));
    }
    cursor.setPosition(pos);
    d->m_allowSkippingOfBlockEnd = true;
    return 1;
con's avatar
con committed
void BaseTextEditor::indentBlock(QTextDocument *, QTextBlock, QChar)
{
}

void BaseTextEditor::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar)
{
    if (cursor.hasSelection()) {
        QTextBlock block = doc->findBlock(cursor.selectionStart());
        const QTextBlock end = doc->findBlock(cursor.selectionEnd()).next();
con's avatar
con committed
        do {
            indentBlock(doc, block, typedChar);
            block = block.next();
        } while (block.isValid() && block != end);
    } else {
        indentBlock(doc, cursor.block(), typedChar);
    }
}

void BaseTextEditor::reindent(QTextDocument *doc, const QTextCursor &cursor)
{
    if (cursor.hasSelection()) {
        QTextBlock block = doc->findBlock(cursor.selectionStart());
        const QTextBlock end = doc->findBlock(cursor.selectionEnd()).next();

        const TabSettings &ts = d->m_document->tabSettings();

        // skip empty blocks
        while (block.isValid() && block != end) {
            QString bt = block.text();
            if (ts.firstNonSpace(bt) < bt.size())
                break;
            indentBlock(doc, block, QChar::Null);
            block = block.next();
        }

        int previousIndentation = ts.indentationColumn(block.text());
        indentBlock(doc, block, QChar::Null);
        int currentIndentation = ts.indentationColumn(block.text());
        int delta = currentIndentation - previousIndentation;

        block = block.next();
        while (block.isValid() && block != end) {
            ts.reindentLine(block, delta);
            block = block.next();
        }
    } else {
        indentBlock(doc, cursor.block(), QChar::Null);
    }
}


BaseTextEditor::Link BaseTextEditor::findLinkAt(const QTextCursor &, bool)
{
    return Link();
}

bool BaseTextEditor::openLink(const Link &link)
{
    if (link.fileName.isEmpty())
        return false;

    if (baseTextDocument()->fileName() == link.fileName) {
        Core::EditorManager *editorManager = Core::EditorManager::instance();
        editorManager->addCurrentPositionToNavigationHistory();
        gotoLine(link.line, link.column);
        setFocus();
        return true;
    }

    return openEditorAt(link.fileName, link.line, link.column);
}

void BaseTextEditor::updateLink(QMouseEvent *e)
{
    bool linkFound = false;

    if (mouseNavigationEnabled() && e->modifiers() & Qt::ControlModifier) {
        // Link emulation behaviour for 'go to definition'
        const QTextCursor cursor = cursorForPosition(e->pos());

        // Check that the mouse was actually on the text somewhere
        bool onText = cursorRect(cursor).right() >= e->x();
        if (!onText) {
            QTextCursor nextPos = cursor;
            nextPos.movePosition(QTextCursor::Right);
            onText = cursorRect(nextPos).right() >= e->x();
        }

        const Link link = findLinkAt(cursor, false);

        if (onText && link.isValid()) {
            showLink(link);
            linkFound = true;
        }
    }

    if (!linkFound)
        clearLink();
}

void BaseTextEditor::showLink(const Link &link)
{
    QTextEdit::ExtraSelection sel;
    sel.cursor = textCursor();
    sel.cursor.setPosition(link.begin);
    sel.cursor.setPosition(link.end, QTextCursor::KeepAnchor);
    sel.format = d->m_linkFormat;
    sel.format.setFontUnderline(true);
    setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>() << sel);
    viewport()->setCursor(Qt::PointingHandCursor);
    d->m_currentLink = link;
    d->m_linkPressed = false;
        return;

    setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
    viewport()->setCursor(Qt::IBeamCursor);
    d->m_currentLink = Link();
    d->m_linkPressed = false;
con's avatar
con committed
void BaseTextEditorPrivate::updateMarksBlock(const QTextBlock &block)
{
    if (const TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
        foreach (ITextMark *mrk, userData->marks())
con's avatar
con committed
            mrk->updateBlock(block);
}

void BaseTextEditorPrivate::updateMarksLineNumber()
{
    QTextDocument *doc = q->document();
    QTextBlock block = doc->begin();
    int blockNumber = 0;
    while (block.isValid()) {
        if (const TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
            foreach (ITextMark *mrk, userData->marks()) {
                mrk->updateLineNumber(blockNumber + 1);
            }
        block = block.next();
        ++blockNumber;
    }
}

void BaseTextEditor::markBlocksAsChanged(QList<int> blockNumbers)
{
con's avatar
con committed
    QTextBlock block = document()->begin();
    while (block.isValid()) {
        if (block.revision() < 0)
            block.setRevision(-block.revision() - 1);
        block = block.next();
    }
    foreach (const int blockNumber, blockNumbers) {
        QTextBlock block = document()->findBlockByNumber(blockNumber);
        if (block.isValid())
            block.setRevision(-block.revision() - 1);
    }
}



TextBlockUserData::MatchType TextBlockUserData::checkOpenParenthesis(QTextCursor *cursor, QChar c)
{
    QTextBlock block = cursor->block();
    if (!TextEditDocumentLayout::hasParentheses(block) || TextEditDocumentLayout::ifdefedOut(block))
con's avatar
con committed
        return NoMatch;

    Parentheses parenList = TextEditDocumentLayout::parentheses(block);
con's avatar
con committed
    Parenthesis openParen, closedParen;
    QTextBlock closedParenParag = block;
con's avatar
con committed

    const int cursorPos = cursor->position() - closedParenParag.position();
    int i = 0;
    int ignore = 0;
    bool foundOpen = false;
    for (;;) {
        if (!foundOpen) {
            if (i >= parenList.count())
                return NoMatch;
            openParen = parenList.at(i);
            if (openParen.pos != cursorPos) {
                ++i;
                continue;
            } else {
                foundOpen = true;
                ++i;
            }
        }

        if (i >= parenList.count()) {
            for (;;) {
                closedParenParag = closedParenParag.next();
                if (!closedParenParag.isValid())
                    return NoMatch;
                if (TextEditDocumentLayout::hasParentheses(closedParenParag)
                    && !TextEditDocumentLayout::ifdefedOut(closedParenParag)) {
con's avatar
con committed
                    parenList = TextEditDocumentLayout::parentheses(closedParenParag);
                    break;
                }
            }
            i = 0;
        }

        closedParen = parenList.at(i);
        if (closedParen.type == Parenthesis::Opened) {
            ignore++;
            ++i;
            continue;
        } else {
            if (ignore > 0) {
                ignore--;
                ++i;
                continue;
            }

            cursor->clearSelection();
            cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor);

            if ((c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}'))
                || (c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')'))
                || (c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']'))
                || (c == QLatin1Char('+') && closedParen.chr != QLatin1Char('-'))
               )
                return Mismatch;

            return Match;
        }
    }
}

TextBlockUserData::MatchType TextBlockUserData::checkClosedParenthesis(QTextCursor *cursor, QChar c)
{
    QTextBlock block = cursor->block();
    if (!TextEditDocumentLayout::hasParentheses(block) || TextEditDocumentLayout::ifdefedOut(block))
con's avatar
con committed
        return NoMatch;

    Parentheses parenList = TextEditDocumentLayout::parentheses(block);
con's avatar
con committed
    Parenthesis openParen, closedParen;
    QTextBlock openParenParag = block;
con's avatar
con committed

    const int cursorPos = cursor->position() - openParenParag.position();
    int i = parenList.count() - 1;
    int ignore = 0;
    bool foundClosed = false;
    for (;;) {
        if (!foundClosed) {
            if (i < 0)
                return NoMatch;
            closedParen = parenList.at(i);
            if (closedParen.pos != cursorPos - 1) {
                --i;
                continue;
            } else {
                foundClosed = true;
                --i;
            }
        }

        if (i < 0) {
            for (;;) {
                openParenParag = openParenParag.previous();
                if (!openParenParag.isValid())
                    return NoMatch;

                if (TextEditDocumentLayout::hasParentheses(openParenParag)
                    && !TextEditDocumentLayout::ifdefedOut(openParenParag)) {
con's avatar
con committed
                    parenList = TextEditDocumentLayout::parentheses(openParenParag);
                    break;
                }
            }
            i = parenList.count() - 1;
        }

        openParen = parenList.at(i);
        if (openParen.type == Parenthesis::Closed) {
            ignore++;
            --i;
            continue;
        } else {
            if (ignore > 0) {
                ignore--;
                --i;
                continue;
            }

            cursor->clearSelection();
            cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor);

            if ((c == '}' && openParen.chr != '{')    ||
                 (c == ')' && openParen.chr != '(')   ||
                 (c == ']' && openParen.chr != '[')   ||
                 (c == '-' && openParen.chr != '+'))
                return Mismatch;

            return Match;
        }
    }
}


bool TextBlockUserData::findPreviousOpenParenthesis(QTextCursor *cursor, bool select)
{
    QTextBlock block = cursor->block();
    int position = cursor->position();
    int ignore = 0;
    while (block.isValid()) {
        Parentheses parenList = TextEditDocumentLayout::parentheses(block);
        if (!parenList.isEmpty() && !TextEditDocumentLayout::ifdefedOut(block)) {
            for (int i = parenList.count()-1; i >= 0; --i) {
                Parenthesis paren = parenList.at(i);
                if (block == cursor->block() &&
                    (position - block.position() <= paren.pos + (paren.type == Parenthesis::Closed ? 1 : 0)))
                        continue;
                if (paren.type == Parenthesis::Closed) {
                    ++ignore;
                } else if (ignore > 0) {
                    --ignore;
                } else {
                    cursor->setPosition(block.position() + paren.pos, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
                    return true;
                }
            }
        }
        block = block.previous();
    }
    return false;
}

mae's avatar
mae committed
bool TextBlockUserData::findPreviousBlockOpenParenthesis(QTextCursor *cursor, bool checkStartPosition)
{
    QTextBlock block = cursor->block();
    int position = cursor->position();
    int ignore = 0;
    while (block.isValid()) {
        Parentheses parenList = TextEditDocumentLayout::parentheses(block);
        if (!parenList.isEmpty() && !TextEditDocumentLayout::ifdefedOut(block)) {
            for (int i = parenList.count()-1; i >= 0; --i) {
                Parenthesis paren = parenList.at(i);
                if (paren.chr != QLatin1Char('{') && paren.chr != QLatin1Char('}')
                    && paren.chr != QLatin1Char('+') && paren.chr != QLatin1Char('-')
                    && paren.chr != QLatin1Char('[') && paren.chr != QLatin1Char(']'))
                if (block == cursor->block()) {
                    if (position - block.position() <= paren.pos + (paren.type == Parenthesis::Closed ? 1 : 0))
mae's avatar
mae committed
                    if (checkStartPosition && paren.type == Parenthesis::Opened && paren.pos== cursor->position()) {
mae's avatar
mae committed
                    }
                if (paren.type == Parenthesis::Closed) {
                    ++ignore;
                } else if (ignore > 0) {
                    --ignore;
                } else {
                    cursor->setPosition(block.position() + paren.pos);
        block = block.previous();
bool TextBlockUserData::findNextClosingParenthesis(QTextCursor *cursor, bool select)
{
    QTextBlock block = cursor->block();
    int position = cursor->position();
    int ignore = 0;
    while (block.isValid()) {
        Parentheses parenList = TextEditDocumentLayout::parentheses(block);
        if (!parenList.isEmpty() && !TextEditDocumentLayout::ifdefedOut(block)) {
            for (int i = 0; i < parenList.count(); ++i) {
                Parenthesis paren = parenList.at(i);
                if (block == cursor->block() &&
                    (position - block.position() > paren.pos - (paren.type == Parenthesis::Opened ? 1 : 0)))
                    continue;
                if (paren.type == Parenthesis::Opened) {
                    ++ignore;
                } else if (ignore > 0) {
                    --ignore;
                } else {
                    cursor->setPosition(block.position() + paren.pos+1, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
        block = block.next();
    }
    return false;
}

bool TextBlockUserData::findNextBlockClosingParenthesis(QTextCursor *cursor)
{
    QTextBlock block = cursor->block();
    int position = cursor->position();
    int ignore = 0;
    while (block.isValid()) {
        Parentheses parenList = TextEditDocumentLayout::parentheses(block);
        if (!parenList.isEmpty() && !TextEditDocumentLayout::ifdefedOut(block)) {
            for (int i = 0; i < parenList.count(); ++i) {
                Parenthesis paren = parenList.at(i);
                if (paren.chr != QLatin1Char('{') && paren.chr != QLatin1Char('}')
                    && paren.chr != QLatin1Char('+') && paren.chr != QLatin1Char('-')
                    && paren.chr != QLatin1Char('[') && paren.chr != QLatin1Char(']'))
                if (block == cursor->block() &&
                    (position - block.position() > paren.pos - (paren.type == Parenthesis::Opened ? 1 : 0)))
                    continue;
                if (paren.type == Parenthesis::Opened) {
                    ++ignore;
                } else if (ignore > 0) {
                    --ignore;
                } else {
                    cursor->setPosition(block.position() + paren.pos+1);
                    return true;
                }
            }
        }
        block = block.next();
    }
    return false;
}

con's avatar
con committed
TextBlockUserData::MatchType TextBlockUserData::matchCursorBackward(QTextCursor *cursor)
{
    cursor->clearSelection();
    const QTextBlock block = cursor->block();

    if (!TextEditDocumentLayout::hasParentheses(block) || TextEditDocumentLayout::ifdefedOut(block))
con's avatar
con committed
        return NoMatch;

    const int relPos = cursor->position() - block.position();

    Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
    const Parentheses::const_iterator cend = parentheses.constEnd();
    for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
        const Parenthesis &paren = *it;
        if (paren.pos == relPos - 1
            && paren.type == Parenthesis::Closed) {
            return checkClosedParenthesis(cursor, paren.chr);
        }
    }
    return NoMatch;
}

TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor *cursor)
{
    cursor->clearSelection();
    const QTextBlock block = cursor->block();

    if (!TextEditDocumentLayout::hasParentheses(block) || TextEditDocumentLayout::ifdefedOut(block))
con's avatar
con committed
        return NoMatch;

    const int relPos = cursor->position() - block.position();

    Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
    const Parentheses::const_iterator cend = parentheses.constEnd();
    for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
        const Parenthesis &paren = *it;
        if (paren.pos == relPos
            && paren.type == Parenthesis::Opened) {
            return checkOpenParenthesis(cursor, paren.chr);
        }
    }
    return NoMatch;
}


void BaseTextEditor::highlightSearchResults(const QString &txt, Find::IFindSupport::FindFlags findFlags)
con's avatar
con committed
{
    QString pattern = txt;
    if (pattern.size() < 2)
        pattern.clear(); // highlighting single characters is a bit pointless

    if (d->m_searchExpr.pattern() == pattern)
con's avatar
con committed
        return;
    d->m_searchExpr.setPattern(pattern);
    d->m_searchExpr.setPatternSyntax((findFlags & Find::IFindSupport::FindRegularExpression) ?
                                     QRegExp::RegExp : QRegExp::FixedString);
    d->m_searchExpr.setCaseSensitivity((findFlags & Find::IFindSupport::FindCaseSensitively) ?
con's avatar
con committed
                                       Qt::CaseSensitive : Qt::CaseInsensitive);
    d->m_findFlags = findFlags;

    d->m_delayedUpdateTimer->start(10);
}
con's avatar
con committed

void BaseTextEditor::setFindScope(const QTextCursor &scope)
{
    if (scope.isNull() != d->m_findScope.isNull()) {
        d->m_findScope = scope;
        viewport()->update();
    }
}

void BaseTextEditor::_q_animateUpdate(int position, QPointF lastPos, QRectF rect)
{
    QTextCursor cursor(textCursor());
    cursor.setPosition(position);
    viewport()->update(QRectF(cursorRect(cursor).topLeft() + rect.topLeft(), rect.size()).toAlignedRect());
    if (!lastPos.isNull())
        viewport()->update(QRectF(lastPos + rect.topLeft(), rect.size()).toAlignedRect());
}


BaseTextEditorAnimator::BaseTextEditorAnimator(QObject *parent)
        :QObject(parent)
{
    m_value = 0;
    m_timeline = new QTimeLine(256, this);
    m_timeline->setCurveShape(QTimeLine::SineCurve);
    connect(m_timeline, SIGNAL(valueChanged(qreal)), this, SLOT(step(qreal)));
    connect(m_timeline, SIGNAL(finished()), this, SLOT(deleteLater()));
    m_timeline->start();
}


void BaseTextEditorAnimator::setData(QFont f, QPalette pal, const QString &text)
{
    m_font = f;
    m_palette = pal;
    m_text = text;
    QFontMetrics fm(m_font);
    m_size = QSizeF(fm.width(m_text), fm.height());
}

void BaseTextEditorAnimator::draw(QPainter *p, const QPointF &pos)
{
    p->setPen(m_palette.text().color());
    QFont f = m_font;
    f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
    QFontMetrics fm(f);
    int width = fm.width(m_text);
    QRectF r((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
    r.translate(pos);
    p->fillRect(r, m_palette.base());
    p->setFont(f);
    p->drawText(r, m_text);
}

mae's avatar
mae committed
bool BaseTextEditorAnimator::isRunning() const
{
    return m_timeline->state() == QTimeLine::Running;
}

QRectF BaseTextEditorAnimator::rect() const
{
    QFont f = m_font;
    f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
    QFontMetrics fm(f);
    int width = fm.width(m_text);
    return QRectF((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
}

void BaseTextEditorAnimator::step(qreal v)
{
    QRectF before = rect();
    m_value = v;
    QRectF after = rect();
    emit updateRequest(m_position, m_lastDrawPos, before.united(after));
con's avatar
con committed
void BaseTextEditor::_q_matchParentheses()
{
    if (isReadOnly())
        return;

    QTextCursor backwardMatch = textCursor();
    QTextCursor forwardMatch = textCursor();
    const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
    const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);

    QList<QTextEdit::ExtraSelection> extraSelections;
con's avatar
con committed

    if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch) {
        setExtraSelections(ParenthesesMatchingSelection, extraSelections); // clear
        return;
    }

con's avatar
con committed
    if (backwardMatch.hasSelection()) {
        QTextEdit::ExtraSelection sel;
        if (backwardMatchType == TextBlockUserData::Mismatch) {
            sel.cursor = backwardMatch;
            sel.format = d->m_mismatchFormat;
        } else {

            if (d->m_displaySettings.m_animateMatchingParentheses) {
                animatePosition = backwardMatch.selectionStart();
            } else if (d->m_formatRange) {
                sel.cursor = backwardMatch;
                sel.format = d->m_rangeFormat;
                extraSelections.append(sel);
            }
con's avatar
con committed

            sel.cursor = backwardMatch;
            sel.format = d->m_matchFormat;

            sel.cursor.setPosition(backwardMatch.selectionStart());
            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
            extraSelections.append(sel);

            sel.cursor.setPosition(backwardMatch.selectionEnd());
            sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
        }
        extraSelections.append(sel);
    }

    if (forwardMatch.hasSelection()) {
        QTextEdit::ExtraSelection sel;
        if (forwardMatchType == TextBlockUserData::Mismatch) {
            sel.cursor = forwardMatch;
            sel.format = d->m_mismatchFormat;
        } else {

            if (d->m_displaySettings.m_animateMatchingParentheses) {
                animatePosition = forwardMatch.selectionEnd()-1;
            } else if (d->m_formatRange) {
                sel.cursor = forwardMatch;
                sel.format = d->m_rangeFormat;
                extraSelections.append(sel);
            }
con's avatar
con committed

            sel.cursor = forwardMatch;
            sel.format = d->m_matchFormat;

            sel.cursor.setPosition(forwardMatch.selectionStart());
            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
            extraSelections.append(sel);

            sel.cursor.setPosition(forwardMatch.selectionEnd());
            sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
        }
        extraSelections.append(sel);
    }
    if (animatePosition >= 0) {
        foreach (const QTextEdit::ExtraSelection &sel, BaseTextEditor::extraSelections(ParenthesesMatchingSelection)) {
            if (sel.cursor.selectionStart() == animatePosition
                || sel.cursor.selectionEnd() - 1 == animatePosition) {
                animatePosition = -1;
                break;
            }
        }
    }

    if (animatePosition >= 0) {
        if (d->m_animator)
            d->m_animator->finish();  // one animation is enough
        d->m_animator = new BaseTextEditorAnimator(this);
        d->m_animator->setPosition(animatePosition);
        QPalette pal;
        pal.setBrush(QPalette::Text, d->m_matchFormat.foreground());
        pal.setBrush(QPalette::Base, d->m_rangeFormat.background());
        d->m_animator->setData(font(), pal, characterAt(d->m_animator->position()));
        connect(d->m_animator, SIGNAL(updateRequest(int,QPointF,QRectF)),
                this, SLOT(_q_animateUpdate(int,QPointF,QRectF)));
    setExtraSelections(ParenthesesMatchingSelection, extraSelections);
con's avatar
con committed
}

void BaseTextEditor::_q_highlightBlocks()
{
    BaseTextEditorPrivateHighlightBlocks highlightBlocksInfo;

    if (d->extraAreaHighlightCollapseBlockNumber >= 0) {
        QTextBlock block = document()->findBlockByNumber(d->extraAreaHighlightCollapseBlockNumber);
        if (block.isValid()) {
            QTextCursor cursor(block);
            if (d->extraAreaHighlightCollapseColumn >= 0)
mae's avatar
mae committed
                cursor.setPosition(cursor.position() + qMin(d->extraAreaHighlightCollapseColumn,
                                                            block.length()-1));
            QTextCursor closeCursor;
mae's avatar
mae committed
            bool firstRun = true;
mae's avatar
mae committed
            while (TextBlockUserData::findPreviousBlockOpenParenthesis(&cursor, firstRun)) {
mae's avatar
mae committed
                firstRun = false;
                highlightBlocksInfo.open.prepend(cursor.blockNumber());
                int visualIndent = d->visualIndent(cursor.block());
                if (closeCursor.isNull())
                    closeCursor = cursor;
                if (TextBlockUserData::findNextBlockClosingParenthesis(&closeCursor)) {
                    highlightBlocksInfo.close.append(closeCursor.blockNumber());
                    visualIndent = qMin(visualIndent, d->visualIndent(closeCursor.block()));
                }
                highlightBlocksInfo.visualIndent.prepend(visualIndent);
    if (d->m_highlightBlocksInfo != highlightBlocksInfo) {
        d->m_highlightBlocksInfo = highlightBlocksInfo;
        viewport()->update();
        d->m_extraArea->update();
con's avatar
con committed
void BaseTextEditor::setActionHack(QObject *hack)
{
    d->m_actionHack = hack;
}

QObject *BaseTextEditor::actionHack() const
{
    return d->m_actionHack;
}

void BaseTextEditor::changeEvent(QEvent *e)
{
    QPlainTextEdit::changeEvent(e);
    if (e->type() == QEvent::ApplicationFontChange
        || e->type() == QEvent::FontChange) {
        if (d->m_extraArea) {
            QFont f = d->m_extraArea->font();
            f.setPointSize(font().pointSize());
            d->m_extraArea->setFont(f);
            slotUpdateExtraAreaWidth();
            d->m_extraArea->update();
        }
    }
}

void BaseTextEditor::focusInEvent(QFocusEvent *e)
{
    QPlainTextEdit::focusInEvent(e);
con's avatar
con committed
    updateHighlights();
mae's avatar
mae committed
void BaseTextEditor::focusOutEvent(QFocusEvent *e)
{
    QPlainTextEdit::focusOutEvent(e);
    if (viewport()->cursor().shape() == Qt::BlankCursor)
        viewport()->setCursor(Qt::IBeamCursor);
}


void BaseTextEditor::maybeSelectLine()
con's avatar
con committed
{
    QTextCursor cursor = textCursor();
    if (!cursor.hasSelection()) {
        const QTextBlock &block = cursor.block();
        if (block.next().isValid()) {
            cursor.setPosition(block.position());
            cursor.setPosition(block.next().position(), QTextCursor::KeepAnchor);
        } else {
            cursor.movePosition(QTextCursor::EndOfBlock);
            cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
            cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
        }
        setTextCursor(cursor);
    }
}

// shift+del
void BaseTextEditor::cutLine()
{
    maybeSelectLine();
con's avatar
con committed
    cut();
}

void BaseTextEditor::deleteLine()
{
    maybeSelectLine();
    textCursor().removeSelectedText();
}

void BaseTextEditor::setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections)
con's avatar
con committed
{
    if (selections.isEmpty() && d->m_extraSelections[kind].isEmpty())
        return;
    d->m_extraSelections[kind] = selections;

    if (kind == CodeSemanticsSelection) {
        d->m_overlay->clear();
        foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
mae's avatar
mae committed
            d->m_overlay->addOverlaySelection(selection.cursor,
                                              selection.format.background().color(),
                                              selection.format.background().color(),
                                              TextEditorOverlay::LockSize);
        }
        d->m_overlay->setVisible(!d->m_overlay->isEmpty());
    } else if (kind == SnippetPlaceholderSelection) {
        d->m_snippetOverlay->clear();
        foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
            d->m_snippetOverlay->addOverlaySelection(selection.cursor,
                                              selection.format.background().color(),
                                              selection.format.background().color(),
                                              TextEditorOverlay::ExpandBegin);
        }
        d->m_snippetOverlay->setVisible(!d->m_snippetOverlay->isEmpty());
    } else {
        QList<QTextEdit::ExtraSelection> all;
        for (int i = 0; i < NExtraSelectionKinds; ++i) {
            if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection)
                continue;
            all += d->m_extraSelections[i];
        }
        QPlainTextEdit::setExtraSelections(all);
    }
con's avatar
con committed
}

QList<QTextEdit::ExtraSelection> BaseTextEditor::extraSelections(ExtraSelectionKind kind) const
con's avatar
con committed
{
    return d->m_extraSelections[kind];
con's avatar
con committed
}

QString BaseTextEditor::extraSelectionTooltip(int pos) const
{
    QList<QTextEdit::ExtraSelection> all;
    for (int i = 0; i < NExtraSelectionKinds; ++i) {
        const QList<QTextEdit::ExtraSelection> &sel = d->m_extraSelections[i];
        for (int j = 0; j < sel.size(); ++j) {
            const QTextEdit::ExtraSelection &s = sel.at(j);
            if (s.cursor.selectionStart() <= pos
                && s.cursor.selectionEnd() >= pos
                && !s.format.toolTip().isEmpty())
                return s.format.toolTip();
        }
    }
    return QString();
}
con's avatar
con committed

// the blocks list must be sorted
void BaseTextEditor::setIfdefedOutBlocks(const QList<BaseTextEditor::BlockRange> &blocks)
{
    QTextDocument *doc = document();
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
hjk's avatar
hjk committed
    QTC_ASSERT(documentLayout, return);
con's avatar
con committed

    bool needUpdate = false;

    QTextBlock block = doc->firstBlock();

    int rangeNumber = 0;
    int braceDepthDelta = 0;
con's avatar
con committed
    while (block.isValid()) {
        bool cleared = false;
        bool set = false;
con's avatar
con committed
        if (rangeNumber < blocks.size()) {
            const BlockRange &range = blocks.at(rangeNumber);
            if (block.position() >= range.first && ((block.position() + block.length() - 1) <= range.last || !range.last)) {
                set = TextEditDocumentLayout::setIfdefedOut(block);
con's avatar
con committed
            } else {
                cleared = TextEditDocumentLayout::clearIfdefedOut(block);
con's avatar
con committed
            }
            if (block.contains(range.last))
                ++rangeNumber;
        } else {
            cleared = TextEditDocumentLayout::clearIfdefedOut(block);
con's avatar
con committed
        }

        if (cleared || set) {
            needUpdate = true;
            int delta = TextEditDocumentLayout::braceDepthDelta(block);
            if (cleared)
                braceDepthDelta += delta;
            else if (set)
                braceDepthDelta -= delta;
        }

        if (braceDepthDelta)
            TextEditDocumentLayout::changeBraceDepth(block,braceDepthDelta);

con's avatar
con committed
        block = block.next();
    }

    if (needUpdate)
        documentLayout->requestUpdate();
}

void BaseTextEditor::format()
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    indent(document(), cursor, QChar::Null);
    cursor.endEditBlock();
}
con's avatar
con committed

void BaseTextEditor::rewrapParagraph()
{
    const int paragraphWidth = displaySettings().m_wrapColumn;
    const QRegExp anyLettersOrNumbers = QRegExp("\\w");
    const int tabSize = tabSettings().m_tabSize;

    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();

    // Find start of paragraph.

    while (cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor)) {
        QTextBlock block = cursor.block();
        QString text = block.text();

        // If this block is empty, move marker back to previous and terminate.
        if (!text.contains(anyLettersOrNumbers)) {
            cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
            break;
        }
    }

    cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);

    // Find indent level of current block.

    int indentLevel = 0;
    QString text = cursor.block().text();

    for (int i = 0; i < text.length(); i++) {
        const QChar ch = text.at(i);

        if (ch == QLatin1Char(' '))
            indentLevel++;
        else if (ch == QLatin1Char('\t'))
            indentLevel += tabSize - (indentLevel % tabSize);