Skip to content
Snippets Groups Projects
basetexteditor.cpp 160 KiB
Newer Older
con's avatar
con committed
#endif
    case Qt::Key_Tab:
Joel Nordell's avatar
Joel Nordell committed
    case Qt::Key_Backtab: {
con's avatar
con committed
        if (ro) break;
Joel Nordell's avatar
Joel Nordell committed
        QTextCursor cursor = textCursor();
        int newPosition;
        if (d->m_document->tabSettings().tabShouldIndent(document(), cursor, &newPosition)) {
            if (newPosition != cursor.position() && !cursor.hasSelection()) {
                cursor.setPosition(newPosition);
                setTextCursor(cursor);
            }
            indent(document(), cursor, QChar::Null);
        } else {
            indentOrUnindent(e->key() == Qt::Key_Tab);
        }
con's avatar
con committed
        e->accept();
        return;
Joel Nordell's avatar
Joel Nordell committed
    } break;
con's avatar
con committed
    case Qt::Key_Backspace:
        if (ro) break;
        if ((e->modifiers() & (Qt::ControlModifier
con's avatar
con committed
                               | Qt::ShiftModifier
                               | Qt::AltModifier
                               | Qt::MetaModifier)) == Qt::NoModifier
            && !textCursor().hasSelection()) {
            handleBackspaceKey();
            e->accept();
            return;
        }
        break;
    case Qt::Key_Up:
    case Qt::Key_Down:
        if (e->modifiers() & Qt::ControlModifier) {
            verticalScrollBar()->triggerAction(
                    e->key() == Qt::Key_Up ? QAbstractSlider::SliderSingleStepSub :
                                             QAbstractSlider::SliderSingleStepAdd);
            e->accept();
            return;
        }
        // fall through
    case Qt::Key_End:
con's avatar
con committed
    case Qt::Key_Right:
    case Qt::Key_Left:
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
#ifndef Q_WS_MAC
con's avatar
con committed
        if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {

            d->m_lastEventWasBlockSelectionEvent = true;

            if (d->m_inBlockSelectionMode) {
                if (e->key() == Qt::Key_Right && textCursor().atBlockEnd()) {
                    d->m_blockSelectionExtraX++;
                    viewport()->update();
                    e->accept();
                    return;
                } else if (e->key() == Qt::Key_Left && d->m_blockSelectionExtraX > 0) {
                    d->m_blockSelectionExtraX--;
                    e->accept();
                    viewport()->update();
                    return;
                }
            }

            e = new QKeyEvent(
                e->type(),
                e->key(),
                e->modifiers() & ~Qt::AltModifier,
                e->text(),
                e->isAutoRepeat(),
                e->count()
                );
        }
#endif
        break;
    case Qt::Key_PageUp:
    case Qt::Key_PageDown:
        if (e->modifiers() == Qt::ControlModifier) {
            verticalScrollBar()->triggerAction(
                    e->key() == Qt::Key_PageUp ? QAbstractSlider::SliderPageStepSub :
                                                 QAbstractSlider::SliderPageStepAdd);
            e->accept();
            return;
        }
        break;

    default:
        break;
    }

    if (d->m_inBlockSelectionMode) {
        QString text = e->text();
        if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) {
            d->removeBlockSelection(text);
            goto skip_event;
        }
    }

    if (ro || e->text().isEmpty() || !e->text().at(0).isPrint()) {
        QPlainTextEdit::keyPressEvent(e);
    } else {
        QTextCursor cursor = textCursor();
        QString text = e->text();
        QString autoText = autoComplete(cursor, text);
        QChar electricChar;
        if (d->m_document->tabSettings().m_autoIndent) {
                if (isElectricCharacter(c)) {
                    electricChar = c;
                    break;
                }
            }
        }

        bool doEditBlock = !(electricChar.isNull() && autoText.isEmpty());
        if (doEditBlock)
            cursor.beginEditBlock();
        cursor.insertText(text);
        if (!autoText.isEmpty()) {
            int pos = cursor.position();
            cursor.insertText(autoText);
            cursor.setPosition(pos);
        }
            indent(document(), cursor, electricChar);
            cursor.endEditBlock();
        setTextCursor(cursor);
    }
con's avatar
con committed

skip_event:
    if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled)
        d->m_parenthesesMatchingTimer->start(50);

con's avatar
con committed

    if (!ro && d->m_contentsChanged && !e->text().isEmpty() && e->text().at(0).isPrint())
        emit requestAutoCompletion(editableInterface(), false);

    if (e != original_e)
        delete e;
}

void BaseTextEditor::setTextCursor(const QTextCursor &cursor)
{
    // workaround for QTextControl bug
    bool selectionChange = cursor.hasSelection() || textCursor().hasSelection();
    QPlainTextEdit::setTextCursor(cursor);
    if (selectionChange)
        slotSelectionChanged();
}

void BaseTextEditor::gotoLine(int line, int column)
con's avatar
con committed
{
    d->m_lastCursorChangeWasInteresting = false; // avoid adding the previous position to history
con's avatar
con committed
    const int blockNumber = line - 1;
    const QTextBlock &block = document()->findBlockByNumber(blockNumber);
    if (block.isValid()) {
        QTextCursor cursor(block);
        if (column > 0) {
            cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
        } else {
            int pos = cursor.position();
            while (characterAt(pos).category() == QChar::Separator_Space) {
                ++pos;
            }
            cursor.setPosition(pos);
        }
        setTextCursor(cursor);
        centerCursor();
    }
    saveCurrentCursorPositionForNavigation();
con's avatar
con committed
}

int BaseTextEditor::position(ITextEditor::PositionOperation posOp, int at) const
{
    QTextCursor tc = textCursor();

    if (at != -1)
        tc.setPosition(at);

    if (posOp == ITextEditor::Current)
        return tc.position();

    switch (posOp) {
    case ITextEditor::EndOfLine:
        tc.movePosition(QTextCursor::EndOfLine);
        return tc.position();
    case ITextEditor::StartOfLine:
        tc.movePosition(QTextCursor::StartOfLine);
        return tc.position();
    case ITextEditor::Anchor:
        if (tc.hasSelection())
            return tc.anchor();
        break;
    case ITextEditor::EndOfDoc:
        tc.movePosition(QTextCursor::End);
        return tc.position();
    default:
        break;
    }

    return -1;
}

void BaseTextEditor::convertPosition(int pos, int *line, int *column) const
{
    QTextBlock block = document()->findBlock(pos);
    if (!block.isValid()) {
        (*line) = -1;
        (*column) = -1;
    } else {
        (*line) = block.blockNumber() + 1;
        (*column) = pos - block.position();
    }
}

QChar BaseTextEditor::characterAt(int pos) const
{
    return document()->characterAt(pos);
}

bool BaseTextEditor::event(QEvent *e)
{
    d->m_contentsChanged = false;
con's avatar
con committed
    switch (e->type()) {
    case QEvent::ShortcutOverride:
        e->ignore(); // we are a really nice citizen
con's avatar
con committed
        return true;
    default:
        break;
    }

    return QPlainTextEdit::event(e);
}

void BaseTextEditor::duplicateFrom(BaseTextEditor *editor)
{
    if (this == editor)
        return;
    setDisplayName(editor->displayName());
    d->m_revisionsVisible = editor->d->m_revisionsVisible;
    if (d->m_document == editor->d->m_document)
        return;
    d->setupDocumentSignals(editor->d->m_document);
    d->m_document = editor->d->m_document;
}

QString BaseTextEditor::displayName() const
{
    return d->m_displayName;
}

void BaseTextEditor::setDisplayName(const QString &title)
{
    d->m_displayName = title;
}

BaseTextDocument *BaseTextEditor::baseTextDocument() const
{
    return d->m_document;
}

void BaseTextEditor::setBaseTextDocument(BaseTextDocument *doc)
{
    if (doc) {
        d->setupDocumentSignals(doc);
        d->m_document = doc;
    }
}

void BaseTextEditor::memorizeCursorPosition()
{
    d->m_tempState = saveState();
}

void BaseTextEditor::restoreCursorPosition()
{
    restoreState(d->m_tempState);
}

QByteArray BaseTextEditor::saveState() const
{
    QByteArray state;
    QDataStream stream(&state, QIODevice::WriteOnly);
    stream << 0; // version number
    stream << verticalScrollBar()->value();
    stream << horizontalScrollBar()->value();
    int line, column;
    convertPosition(textCursor().position(), &line, &column);
    stream << line;
    stream << column;
    return state;
}

bool BaseTextEditor::restoreState(const QByteArray &state)
{
    int version;
    int vval;
    int hval;
    int lval;
    int cval;
    QDataStream stream(state);
    stream >> version;
    stream >> vval;
    stream >> hval;
    stream >> lval;
    stream >> cval;
    d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history
    gotoLine(lval, cval);
con's avatar
con committed
    verticalScrollBar()->setValue(vval);
    horizontalScrollBar()->setValue(hval);
    saveCurrentCursorPositionForNavigation();
con's avatar
con committed
    return true;
}

void BaseTextEditor::setDefaultPath(const QString &defaultPath)
{
    baseTextDocument()->setDefaultPath(defaultPath);
}

void BaseTextEditor::setSuggestedFileName(const QString &suggestedFileName)
{
    baseTextDocument()->setSuggestedFileName(suggestedFileName);
}

void BaseTextEditor::setParenthesesMatchingEnabled(bool b)
{
    d->m_parenthesesMatchingEnabled = b;
}

bool BaseTextEditor::isParenthesesMatchingEnabled() const
{
    return d->m_parenthesesMatchingEnabled;
}

void BaseTextEditor::setHighlightCurrentLine(bool b)
{
    d->m_highlightCurrentLine = b;
con's avatar
con committed
}

bool BaseTextEditor::highlightCurrentLine() const
{
    return d->m_highlightCurrentLine;
}

void BaseTextEditor::setLineNumbersVisible(bool b)
{
    d->m_lineNumbersVisible = b;
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::lineNumbersVisible() const
{
    return d->m_lineNumbersVisible;
}

void BaseTextEditor::setMarksVisible(bool b)
{
    d->m_marksVisible = b;
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::marksVisible() const
{
    return d->m_marksVisible;
}

void BaseTextEditor::setRequestMarkEnabled(bool b)
{
    d->m_requestMarkEnabled = b;
}

bool BaseTextEditor::requestMarkEnabled() const
{
    return d->m_requestMarkEnabled;
}

void BaseTextEditor::setLineSeparatorsAllowed(bool b)
{
    d->m_lineSeparatorsAllowed = b;
}

bool BaseTextEditor::lineSeparatorsAllowed() const
{
    return d->m_lineSeparatorsAllowed;
}

void BaseTextEditor::setCodeFoldingVisible(bool b)
{
    d->m_codeFoldingVisible = b && d->m_codeFoldingSupported;
con's avatar
con committed
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::codeFoldingVisible() const
{
    return d->m_codeFoldingVisible;
}

/**
 * Sets whether code folding is supported by the syntax highlighter. When not
 * supported (the default), this makes sure the code folding is not shown.
 *
 * Needs to be called before calling setCodeFoldingVisible.
 */
void BaseTextEditor::setCodeFoldingSupported(bool b)
{
    d->m_codeFoldingSupported = b;
}

bool BaseTextEditor::codeFoldingSupported() const
{
    return d->m_codeFoldingSupported;
}

void BaseTextEditor::setMouseNavigationEnabled(bool b)
{
    d->m_mouseNavigationEnabled = b;
}

bool BaseTextEditor::mouseNavigationEnabled() const
{
    return d->m_mouseNavigationEnabled;
}

con's avatar
con committed
void BaseTextEditor::setRevisionsVisible(bool b)
{
    d->m_revisionsVisible = b;
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::revisionsVisible() const
{
    return d->m_revisionsVisible;
}

void BaseTextEditor::setVisibleWrapColumn(int column)
{
    d->m_visibleWrapColumn = column;
    viewport()->update();
}

int BaseTextEditor::visibleWrapColumn() const
{
    return d->m_visibleWrapColumn;
}

//--------- BaseTextEditorPrivate -----------

BaseTextEditorPrivate::BaseTextEditorPrivate()
    :
    m_contentsChanged(false),
    m_lastCursorChangeWasInteresting(false),
con's avatar
con committed
    m_document(new BaseTextDocument()),
    m_parenthesesMatchingEnabled(false),
    m_extraArea(0),
    m_mouseOnCollapsedMarker(false),
con's avatar
con committed
    m_marksVisible(false),
    m_codeFoldingVisible(false),
    m_codeFoldingSupported(false),
con's avatar
con committed
    m_revisionsVisible(false),
    m_lineNumbersVisible(true),
    m_highlightCurrentLine(true),
    m_requestMarkEnabled(true),
    m_lineSeparatorsAllowed(false),
    m_visibleWrapColumn(0),
con's avatar
con committed
    m_editable(0),
    m_actionHack(0),
    m_inBlockSelectionMode(false),
    m_lastEventWasBlockSelectionEvent(false),
    m_blockSelectionExtraX(0),
    m_moveLineUndoHack(false)
con's avatar
con committed
{
}

BaseTextEditorPrivate::~BaseTextEditorPrivate()
{
}

void BaseTextEditorPrivate::setupDocumentSignals(BaseTextDocument *document)
{
    BaseTextDocument *oldDocument = q->baseTextDocument();
    if (oldDocument) {
        q->disconnect(oldDocument->document(), 0, q, 0);
        q->disconnect(oldDocument, 0, q, 0);
    }

    QTextDocument *doc = document->document();
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
    if (!documentLayout) {
        QTextOption opt = doc->defaultTextOption();
        opt.setTextDirection(Qt::LeftToRight);
        opt.setFlags(opt.flags() | QTextOption::IncludeTrailingSpaces
                | QTextOption::AddSpaceForLineAndParagraphSeparators
                );
        doc->setDefaultTextOption(opt);
        documentLayout = new TextEditDocumentLayout(doc);
        doc->setDocumentLayout(documentLayout);
    }


    q->setDocument(doc);
    QObject::connect(documentLayout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(slotUpdateBlockNotify(QTextBlock)));
    QObject::connect(q, SIGNAL(requestBlockUpdate(QTextBlock)), documentLayout, SIGNAL(updateBlock(QTextBlock)));
    QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(changed()));
    QObject::connect(doc, SIGNAL(contentsChange(int,int,int)), q,
        SLOT(editorContentsChange(int,int,int)), Qt::DirectConnection);
    QObject::connect(document, SIGNAL(changed()), q, SIGNAL(changed()));
    QObject::connect(document, SIGNAL(titleChanged(QString)), q, SLOT(setDisplayName(const QString &)));
    QObject::connect(document, SIGNAL(aboutToReload()), q, SLOT(memorizeCursorPosition()));
    QObject::connect(document, SIGNAL(reloaded()), q, SLOT(restoreCursorPosition()));
    q->slotUpdateExtraAreaWidth();
}

bool Parenthesis::hasClosingCollapse(const Parentheses &parentheses)
{
    return closeCollapseAtPos(parentheses) >= 0;
}


int Parenthesis::closeCollapseAtPos(const Parentheses &parentheses)
{
    int depth = 0;
    for (int i = 0; i < parentheses.size(); ++i) {
        const Parenthesis &p = parentheses.at(i);
        if (p.chr == QLatin1Char('{')
            || p.chr == QLatin1Char('+')
            || p.chr == QLatin1Char('[')) {
con's avatar
con committed
            ++depth;
        } else if (p.chr == QLatin1Char('}')
            || p.chr == QLatin1Char('-')
            || p.chr == QLatin1Char(']')) {
con's avatar
con committed
            if (--depth < 0)
                return p.pos;
        }
    }
    return -1;
}

int Parenthesis::collapseAtPos(const Parentheses &parentheses, QChar *character)
{
    int result = -1;
    QChar c;

    int depth = 0;
    for (int i = 0; i < parentheses.size(); ++i) {
        const Parenthesis &p = parentheses.at(i);
        if (p.chr == QLatin1Char('{')
            || p.chr == QLatin1Char('+')
            || p.chr == QLatin1Char('[')) {
con's avatar
con committed
            if (depth == 0) {
                result = p.pos;
                c = p.chr;
            }
            ++depth;
        } else if (p.chr == QLatin1Char('}')
            || p.chr == QLatin1Char('-')
            || p.chr == QLatin1Char(']')) {
con's avatar
con committed
            if (--depth < 0)
                depth = 0;
            result = -1;
        }
    }
    if (result >= 0 && character)
        *character = c;
    return result;
}


int TextBlockUserData::collapseAtPos() const
{
    return Parenthesis::collapseAtPos(m_parentheses);
}

int TextBlockUserData::braceDepthDelta() const
{
    int delta = 0;
    for (int i = 0; i < m_parentheses.size(); ++i) {
        switch (m_parentheses.at(i).chr.unicode()) {
        case '{': case '+': case '[': ++delta; break;
        case '}': case '-': case ']': --delta; break;
        default: break;
        }
    }
    return delta;
}
con's avatar
con committed

void TextEditDocumentLayout::setParentheses(const QTextBlock &block, const Parentheses &parentheses)
{
    if (parentheses.isEmpty()) {
        if (TextBlockUserData *userData = testUserData(block))
            userData->clearParentheses();
    } else {
        userData(block)->setParentheses(parentheses);
    }
}

Parentheses TextEditDocumentLayout::parentheses(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->parentheses();
    return Parentheses();
}

bool TextEditDocumentLayout::hasParentheses(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->hasParentheses();
    return false;
}

int TextEditDocumentLayout::braceDepthDelta(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->braceDepthDelta();
    return 0;
}

int TextEditDocumentLayout::braceDepth(const QTextBlock &block)
{
    int state = block.userState();
    if (state == -1)
        return 0;
    return state >> 8;
}

void TextEditDocumentLayout::setBraceDepth(QTextBlock &block, int depth)
{
    int state = block.userState();
    if (state == -1)
        state = 0;
    state = state & 0xff;
    block.setUserState((depth << 8) | state);
}

void TextEditDocumentLayout::changeBraceDepth(QTextBlock &block, int delta)
{
    if (delta)
        setBraceDepth(block, braceDepth(block) + delta);
}
con's avatar
con committed

bool TextEditDocumentLayout::setIfdefedOut(const QTextBlock &block)
{
    return userData(block)->setIfdefedOut();
}

bool TextEditDocumentLayout::clearIfdefedOut(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->clearIfdefedOut();
    return false;
}

bool TextEditDocumentLayout::ifdefedOut(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->ifdefedOut();
    return false;
}


TextEditDocumentLayout::TextEditDocumentLayout(QTextDocument *doc)
    :QPlainTextDocumentLayout(doc) {
    lastSaveRevision = 0;
    hasMarks = 0;
}

TextEditDocumentLayout::~TextEditDocumentLayout()
{
}

QRectF TextEditDocumentLayout::blockBoundingRect(const QTextBlock &block) const
{
    QRectF r = QPlainTextDocumentLayout::blockBoundingRect(block);
    return r;
}


bool BaseTextEditor::viewportEvent(QEvent *event)
{
    d->m_contentsChanged = false;
con's avatar
con committed
    if (event->type() == QEvent::ContextMenu) {
        const QContextMenuEvent *ce = static_cast<QContextMenuEvent*>(event);
        if (ce->reason() == QContextMenuEvent::Mouse && !textCursor().hasSelection())
            setTextCursor(cursorForPosition(ce->pos()));
    } else if (event->type() == QEvent::ToolTip) {
        const QHelpEvent *he = static_cast<QHelpEvent*>(event);
        if (QApplication::keyboardModifiers() & Qt::ControlModifier)
            return true; // eat tooltip event when control is pressed
con's avatar
con committed
        const QPoint &pos = he->pos();

        // Allow plugins to show tooltips
        const QTextCursor &c = cursorForPosition(pos);
        QPoint cursorPos = mapToGlobal(cursorRect(c).bottomRight() + QPoint(1,1));
        cursorPos.setX(cursorPos.x() + d->m_extraArea->width());

        editableInterface(); // create if necessary

        emit d->m_editable->tooltipRequested(editableInterface(), cursorPos, c.position());
        return true;
    }
    return QPlainTextEdit::viewportEvent(event);
}


void BaseTextEditor::resizeEvent(QResizeEvent *e)
{
    QPlainTextEdit::resizeEvent(e);
    QRect cr = viewport()->rect();
    d->m_extraArea->setGeometry(
        QStyle::visualRect(layoutDirection(), cr,
                           QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height())));
}

QRect BaseTextEditor::collapseBox()
con's avatar
con committed
{
    if (d->m_highlightBlocksInfo.isEmpty() || d->extraAreaHighlightCollapseBlockNumber < 0)
        return QRect();
con's avatar
con committed

    QTextBlock begin = document()->findBlockByNumber(d->m_highlightBlocksInfo.open.last());
    if (TextBlockUserData::hasCollapseAfter(begin.previous()))
        begin = begin.previous();
    QTextBlock end = document()->findBlockByNumber(d->m_highlightBlocksInfo.close.first());
    if (!begin.isValid() || !end.isValid())
        return QRect();
    QRectF br = blockBoundingGeometry(begin).translated(contentOffset());
    QRectF er = blockBoundingGeometry(end).translated(contentOffset());

    return QRect(d->m_extraArea->width() - collapseBoxWidth(fontMetrics()),
                 collapseBoxWidth(fontMetrics()),
                 er.bottom() - br.top());
con's avatar
con committed
}

QTextBlock BaseTextEditor::collapsedBlockAt(const QPoint &pos, QRect *box) const {
    QPointF offset(contentOffset());
    QTextBlock block = firstVisibleBlock();
    int top = (int)blockBoundingGeometry(block).translated(offset).top();
    int bottom = top + (int)blockBoundingRect(block).height();

    int viewportHeight = viewport()->height();

    while (block.isValid() && top <= viewportHeight) {
        QTextBlock nextBlock = block.next();
        if (block.isVisible() && bottom >= 0) {
            if (nextBlock.isValid() && !nextBlock.isVisible()) {
                QTextLayout *layout = block.layout();
                QTextLine line = layout->lineAt(layout->lineCount()-1);
                QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                lineRect.adjust(0, 0, -1, -1);

                QRectF collapseRect(lineRect.right() + 12,
                                    lineRect.top(),
                                    fontMetrics().width(QLatin1String(" {...}; ")),
                                    lineRect.height());
                if (collapseRect.contains(pos)) {
                    QTextBlock result = block;
                    if (box)
                        *box = collapseRect.toAlignedRect();
                    return result;
                } else {
                    block = nextBlock;
                    while (nextBlock.isValid() && !nextBlock.isVisible()) {
                        block = nextBlock;
                        nextBlock = block.next();
                    }
                }
            }
        }

        block = nextBlock;
        top = bottom;
        bottom = top + (int)blockBoundingRect(block).height();
    }
    return QTextBlock();
}

void BaseTextEditorPrivate::highlightSearchResults(const QTextBlock &block,
                                                   QVector<QTextLayout::FormatRange> *selections)
{
    if (m_searchExpr.isEmpty())
        return;

    QString text = block.text();
    text.replace(QChar::Nbsp, QLatin1Char(' '));
    int idx = -1;
    while (idx < text.length()) {
        idx = m_searchExpr.indexIn(text, idx + 1);
        if (idx < 0)
            break;
        int l = m_searchExpr.matchedLength();
        if ((m_findFlags & Find::IFindSupport::FindWholeWords)
con's avatar
con committed
            && ((idx && text.at(idx-1).isLetterOrNumber())
                || (idx + l < text.length() && text.at(idx + l).isLetterOrNumber())))
            continue;

        if (m_findScope.isNull()
            || (block.position() + idx >= m_findScope.selectionStart()
                && block.position() + idx + l <= m_findScope.selectionEnd())) {
            QTextLayout::FormatRange selection;
            selection.start = idx;
            selection.length = l;
            selection.format = m_searchResultFormat;
            selections->append(selection);
        }
    }
}


namespace TextEditor {
    namespace Internal {
        struct BlockSelectionData {
            int selectionIndex;
            int selectionStart;
            int selectionEnd;
            int firstColumn;
            int lastColumn;
        };
    }
}

void BaseTextEditorPrivate::clearBlockSelection()
{
    if (m_inBlockSelectionMode) {
        m_inBlockSelectionMode = false;
        QTextCursor cursor = q->textCursor();
        cursor.clearSelection();
        q->setTextCursor(cursor);
    }
}

QString BaseTextEditorPrivate::copyBlockSelection()
{
    QString text;

    QTextCursor cursor = q->textCursor();
    if (!cursor.hasSelection())
        return text;

    QTextDocument *doc = q->document();
    int start = cursor.selectionStart();
    int end = cursor.selectionEnd();
    QTextBlock startBlock = doc->findBlock(start);
    int columnA = start - startBlock.position();
    QTextBlock endBlock = doc->findBlock(end);
    int columnB = end - endBlock.position();
    int firstColumn = qMin(columnA, columnB);
    int lastColumn = qMax(columnA, columnB) + m_blockSelectionExtraX;

    QTextBlock block = startBlock;
    for (;;) {

        cursor.setPosition(block.position() + qMin(block.length()-1, firstColumn));
        cursor.setPosition(block.position() + qMin(block.length()-1, lastColumn), QTextCursor::KeepAnchor);
        text += cursor.selectedText();
        if (block == endBlock)
            break;
        text += QLatin1Char('\n');
        block = block.next();
    }

    return text;
}

void BaseTextEditorPrivate::removeBlockSelection(const QString &text)
{
    QTextCursor cursor = q->textCursor();
    if (!cursor.hasSelection())
        return;

    QTextDocument *doc = q->document();
    int start = cursor.selectionStart();
    int end = cursor.selectionEnd();
    QTextBlock startBlock = doc->findBlock(start);
    int columnA = start - startBlock.position();
    QTextBlock endBlock = doc->findBlock(end);
    int columnB = end - endBlock.position();
    int firstColumn = qMin(columnA, columnB);
    int lastColumn = qMax(columnA, columnB) + m_blockSelectionExtraX;

    cursor.clearSelection();
    cursor.beginEditBlock();

    QTextBlock block = startBlock;
    for (;;) {

        cursor.setPosition(block.position() + qMin(block.length()-1, firstColumn));
        cursor.setPosition(block.position() + qMin(block.length()-1, lastColumn), QTextCursor::KeepAnchor);
        cursor.removeSelectedText();
        if (block == endBlock)
            break;
        block = block.next();
    }

    cursor.setPosition(start);
    if (!text.isEmpty())
        cursor.insertText(text);
    cursor.endEditBlock();
    q->setTextCursor(cursor);
}

void BaseTextEditorPrivate::moveCursorVisible(bool ensureVisible)
{
    QTextCursor cursor = q->textCursor();
    if (!cursor.block().isVisible()) {
        cursor.setVisualNavigation(true);
        cursor.movePosition(QTextCursor::Up);
        q->setTextCursor(cursor);
    }
    if (ensureVisible)
        q->ensureCursorVisible();
}

mae's avatar
mae committed
static QColor calcBlendColor(const QColor &baseColor, int level, int count)
{
    QColor color80;
    QColor color90;

    if (baseColor.value() > 128) {
        const int f90 = 15;
        const int f80 = 30;
        color80.setRgb(qMax(0, baseColor.red() - f80),
                       qMax(0, baseColor.green() - f80),
                       qMax(0, baseColor.blue() - f80));
        color90.setRgb(qMax(0, baseColor.red() - f90),
                       qMax(0, baseColor.green() - f90),
                       qMax(0, baseColor.blue() - f90));
mae's avatar
mae committed
    } else {
        const int f90 = 20;
        const int f80 = 40;
        color80.setRgb(qMin(255, baseColor.red() + f80),
                       qMin(255, baseColor.green() + f80),
                       qMin(255, baseColor.blue() + f80));
        color90.setRgb(qMin(255, baseColor.red() + f90),
                       qMin(255, baseColor.green() + f90),
                       qMin(255, baseColor.blue() + f90));
mae's avatar
mae committed
    }

    if (level == count)
        return baseColor;
    if (level == 0)
        return color80;
    if (level == count - 1)
        return color90;

    const int blendFactor = level * (256 / (count - 2));

    return QColor(
                (color90.red() * blendFactor + color80.red() * (256 - blendFactor)) / 256,
                (color90.green() * blendFactor + color80.green() * (256 - blendFactor)) / 256,
                (color90.blue() * blendFactor + color80.blue() * (256 - blendFactor)) / 256);
}

con's avatar
con committed
void BaseTextEditor::paintEvent(QPaintEvent *e)
{
    /*
      Here comes an almost verbatim copy of
      QPlainTextEdit::paintEvent() so we can adjust the extra
      selections dynamically to indicate all search results.
    */
    //begin QPlainTextEdit::paintEvent()

    QPainter painter(viewport());
    QTextDocument *doc = document();
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
hjk's avatar
hjk committed
    QTC_ASSERT(documentLayout, return);
con's avatar
con committed

    QPointF offset(contentOffset());

    bool hasMainSelection = textCursor().hasSelection();

con's avatar
con committed
    QRect er = e->rect();
    QRect viewportRect = viewport()->rect();

    const QColor baseColor = palette().base().color();
    if (d->m_visibleWrapColumn > 0) {
        lineX = fontMetrics().averageCharWidth() * d->m_visibleWrapColumn + offset.x() + 4;
        painter.fillRect(QRectF(lineX, 0, viewportRect.width() - lineX, viewportRect.height()),
                         d->m_ifdefedOutFormat.background());
//    // keep right margin clean from full-width selection
//    int maxX = offset.x() + qMax((qreal)viewportRect.width(), documentLayout->documentSize().width())
//               - doc->documentMargin();
//    er.setRight(qMin(er.right(), maxX));
//    painter.setClipRect(er);
con's avatar
con committed

    bool editable = !isReadOnly();

    QTextBlock block = firstVisibleBlock();