Skip to content
Snippets Groups Projects
basetexteditor.cpp 133 KiB
Newer Older
con's avatar
con committed
                                         static_cast<int>(lineRect.top() + line.ascent()), visualArrow);
                    }
                    if (!nextBlock.isValid()) { // paint EOF symbol
                        QTextLine line = layout->lineAt(lineCount-1);
                        QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                        int h = 4;
                        lineRect.adjust(0, 0, -1, -1);
                        QPainterPath path;
                        QPointF pos(lineRect.topRight() + QPointF(h+4, line.ascent()));
                        path.moveTo(pos);
                        path.lineTo(pos + QPointF(-h, -h));
                        path.lineTo(pos + QPointF(0, -2*h));
                        path.lineTo(pos + QPointF(h, -h));
                        path.closeSubpath();
                        painter.setBrush(painter.pen().color());
                        painter.drawPath(path);
                    }
                    painter.restore();
                }
            }

            if (nextBlock.isValid() && !nextBlock.isVisible()) {

                bool selectThis = (hasSelection
                                   && nextBlock.position() >= selectionStart
                                   && nextBlock.position() < selectionEnd);
                if (selectThis) {
                    painter.save();
                    painter.setBrush(palette().highlight());
                }

                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());
                painter.setRenderHint(QPainter::Antialiasing, true);
                painter.translate(.5, .5);
                painter.drawRoundedRect(collapseRect.adjusted(0, 0, 0, -1), 3, 3);
                painter.setRenderHint(QPainter::Antialiasing, false);
                painter.translate(-.5, -.5);

                QString replacement = QLatin1String("...");

                QTextBlock info = block;
                if (block.userData()
                    && static_cast<TextBlockUserData*>(block.userData())->collapseMode() == TextBlockUserData::CollapseAfter)
                    ;
                else if (block.next().userData()
                         && static_cast<TextBlockUserData*>(block.next().userData())->collapseMode()
                         == TextBlockUserData::CollapseThis) {
                    replacement.prepend(nextBlock.text().trimmed().left(1));
                    info = nextBlock;
                }


                block = nextVisibleBlock.previous();
                if (!block.isValid())
                    block = doc->lastBlock();

                if (info.userData()
                    && static_cast<TextBlockUserData*>(info.userData())->collapseIncludesClosure()) {
                    QString right = block.text().trimmed();
                    if (right.endsWith(QLatin1Char(';'))) {
                        right.chop(1);
                        right = right.trimmed();
                        replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
                        replacement.append(QLatin1Char(';'));
                    } else {
                        replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
                    }
                }
                if (selectThis)
                    painter.setPen(palette().highlightedText().color());
                painter.drawText(collapseRect, Qt::AlignCenter, replacement);
                if (selectThis)
                    painter.restore();
            }
        }

        block = nextVisibleBlock;
        top = bottom;
        bottom = top + (int)blockBoundingRect(block).height();
    }

    if (visibleCollapsedBlock.isValid() ) {
        int margin = doc->documentMargin();
        qreal maxWidth = 0;
        qreal blockHeight = 0;
        QTextBlock b = visibleCollapsedBlock;

        while (!b.isVisible() && visibleCollapsedBlockOffset.y() + blockHeight <= e->rect().bottom()) {
            b.setVisible(true); // make sure block bounding rect works
            QRectF r = blockBoundingRect(b).translated(visibleCollapsedBlockOffset);

            QTextLayout *layout = b.layout();
            for (int i = layout->lineCount()-1; i >= 0; --i)
                maxWidth = qMax(maxWidth, layout->lineAt(i).naturalTextWidth() + margin);

            blockHeight += r.height();

            b.setVisible(false); // restore previous state
            b = b.next();
        }

        painter.save();
        painter.setRenderHint(QPainter::Antialiasing, true);
        painter.translate(.5, .5);
        QColor color = blendColor;
//        color.setAlpha(240); // someone thinks alpha blending looks messy
        painter.setBrush(color);
        painter.drawRoundedRect(QRectF(visibleCollapsedBlockOffset.x(),
                                       visibleCollapsedBlockOffset.y(),
mae's avatar
mae committed
                                       maxWidth, blockHeight).adjusted(0, 0, 0, 0), 3, 3);
con's avatar
con committed
        painter.restore();

        QTextBlock end = b;
        b = visibleCollapsedBlock;
        while (b != end) {
            b.setVisible(true); // make sure block bounding rect works
            QRectF r = blockBoundingRect(b).translated(visibleCollapsedBlockOffset);
            QTextLayout *layout = b.layout();
            QVector<QTextLayout::FormatRange> selections;
            d->highlightSearchResults(b, &selections);
            layout->draw(&painter, visibleCollapsedBlockOffset, selections, er);

            b.setVisible(false); // restore previous state
            visibleCollapsedBlockOffset.ry() += r.height();
            b = b.next();
        }
    }


    if (d->m_visibleWrapColumn > 0) {
        qreal lineX = fontMetrics().width('x') * d->m_visibleWrapColumn + offset.x() + 4;
        const QColor bg = palette().base().color();
        QColor col = (bg.value() > 128) ? Qt::black : Qt::white;
        col.setAlpha(32);
        painter.setPen(QPen(col, 0));
        painter.drawLine(QPointF(lineX, 0), QPointF(lineX, viewport()->height()));
    }
}

QWidget *BaseTextEditor::extraArea() const
{
    return d->m_extraArea;
}

int BaseTextEditor::extraAreaWidth(int *markWidthPtr) const
{
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
    if (!documentLayout)
        return 0;

    if (!d->m_marksVisible && documentLayout->hasMarks)
        d->m_marksVisible = true;

    int space = 0;
    const QFontMetrics fm(d->m_extraArea->fontMetrics());

    if (d->m_lineNumbersVisible) {
        int digits = 2;
        int max = qMax(1, blockCount());
        while (max >= 100) {
            max /= 10;
            ++digits;
        }
        space += fm.width(QLatin1Char('9')) * digits;
    }
    int markWidth = 0;

    if (d->m_marksVisible) {
        markWidth += fm.lineSpacing();
//     if (documentLayout->doubleMarkCount)
//         markWidth += fm.lineSpacing() / 3;
        space += markWidth;
    } else {
        space += 2;
    }

    if (markWidthPtr)
        *markWidthPtr = markWidth;

    space += 4;

    if (d->m_codeFoldingVisible)
        space += fm.lineSpacing();
    return space;
}

void BaseTextEditor::slotUpdateExtraAreaWidth()
con's avatar
con committed
{
    if (isLeftToRight())
        setViewportMargins(extraAreaWidth(), 0, 0, 0);
    else
        setViewportMargins(0, 0, extraAreaWidth(), 0);
con's avatar
con committed
}

void BaseTextEditor::extraAreaPaintEvent(QPaintEvent *e)
{
    QTextDocument *doc = document();
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
hjk's avatar
hjk committed
    QTC_ASSERT(documentLayout, return);
con's avatar
con committed

mae's avatar
mae committed
    int selStart = textCursor().selectionStart();
    int selEnd = textCursor().selectionEnd();

    const QColor baseColor = palette().base().color();
con's avatar
con committed
    QPalette pal = d->m_extraArea->palette();
    pal.setCurrentColorGroup(QPalette::Active);
    QPainter painter(d->m_extraArea);
    QFontMetrics fm(painter.fontMetrics());
    int fmLineSpacing = fm.lineSpacing();

    int markWidth = 0;
    if (d->m_marksVisible)
        markWidth += fm.lineSpacing();
//     if (documentLayout->doubleMarkCount)
//         markWidth += fm.lineSpacing() / 3;

    const int collapseBoxWidth = d->m_codeFoldingVisible ? fmLineSpacing + 1: 0;
    const int extraAreaWidth = d->m_extraArea->width() - collapseBoxWidth;

    painter.fillRect(e->rect(), pal.color(QPalette::Base));
    painter.fillRect(e->rect().intersected(QRect(0, 0, extraAreaWidth, INT_MAX)),
                     pal.color(QPalette::Background));

    QTextBlock block = firstVisibleBlock();
    int blockNumber = block.blockNumber();
    int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top();
    int bottom = top;

con's avatar
con committed
    int extraAreaHighlightCollapseEndBlockNumber = -1;

    int extraAreaHighlightCollapseBlockNumber = d->extraAreaHighlightCollapseBlockNumber;
    if (extraAreaHighlightCollapseBlockNumber < 0) {
        extraAreaHighlightCollapseBlockNumber = d->extraAreaHighlightFadingBlockNumber;
    }

    if (extraAreaHighlightCollapseBlockNumber >= 0 ) {
        QTextBlock highlightBlock = doc->findBlockByNumber(extraAreaHighlightCollapseBlockNumber);
        if (highlightBlock.isValid() && highlightBlock.next().isValid() && highlightBlock.next().isVisible())
            extraAreaHighlightCollapseEndBlockNumber = TextBlockUserData::testCollapse(highlightBlock).blockNumber();
        else
            extraAreaHighlightCollapseEndBlockNumber = extraAreaHighlightCollapseBlockNumber;
    }
con's avatar
con committed

    while (block.isValid() && top <= e->rect().bottom()) {

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

        QTextBlock nextVisibleBlock = nextBlock;
        int nextVisibleBlockNumber = blockNumber + 1;

        if (!nextVisibleBlock.isVisible()) {
            // invisible blocks do have zero line count
            nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
            nextVisibleBlockNumber = nextVisibleBlock.blockNumber();
        }

        painter.setPen(pal.color(QPalette::Dark));

        if (d->m_codeFoldingVisible || d->m_marksVisible) {
            painter.save();
            painter.setRenderHint(QPainter::Antialiasing, false);

            int previousBraceDepth = block.previous().userState();
            if (previousBraceDepth >= 0)
                previousBraceDepth >>= 8;
            else
                previousBraceDepth = 0;

            int braceDepth = block.userState();
            if (!nextBlock.isVisible()) {
                QTextBlock lastInvisibleBlock = nextVisibleBlock.previous();
                if (!lastInvisibleBlock.isValid())
                    lastInvisibleBlock = doc->lastBlock();
                braceDepth = lastInvisibleBlock.userState();
            }
            if (braceDepth >= 0)
                braceDepth >>= 8;
            else
                braceDepth = 0;

            if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) {
                if (d->m_marksVisible) {
                    int xoffset = 0;
                    foreach (ITextMark *mrk, userData->marks()) {
                        int x = 0;
                        int radius = fmLineSpacing - 1;
                        QRect r(x + xoffset, top, radius, radius);
                        mrk->icon().paint(&painter, r, Qt::AlignCenter);
                        xoffset += 2;
                    }
                }
            }

            if (d->m_codeFoldingVisible) {
mae's avatar
mae committed
                QRect r(extraAreaWidth+2, top, collapseBoxWidth-4, bottom - top);
                bool drawBox = !nextBlock.isVisible();

                int minBraceDepth = qMax(braceDepth, previousBraceDepth);
                if (minBraceDepth > 0) {
                    QColor color = calcBlendColor(baseColor, minBraceDepth);
                    if (!d->m_highlightBlocksInfo.isEmpty()
                        && blockNumber >= d->m_highlightBlocksInfo.open.last()
                        && blockNumber <= d->m_highlightBlocksInfo.close.first())
mae's avatar
mae committed
                        color = calcMixColor(pal.highlight().color(), color);
                    painter.fillRect(r, color);
con's avatar
con committed
                }
mae's avatar
mae committed

                bool drawDown = !d->m_highlightBlocksInfo.isEmpty()
                              && blockNumber == d->m_highlightBlocksInfo.open.last();
                bool drawUp = !d->m_highlightBlocksInfo.isEmpty()
                              && blockNumber == d->m_highlightBlocksInfo.close.first();

                if (drawBox || drawDown || drawUp) {
                    painter.setRenderHint(QPainter::Antialiasing, true);
                    painter.translate(.5, .5);
                    painter.setPen(pal.text().color());
                    painter.setBrush(pal.text().color());

                    if (drawBox) {
                        QPointF points1[3] = { QPointF(r.left(), r.center().y()-1),
                                               QPointF(r.center().x(), r.top()),
                                               QPointF(r.right(), r.center().y()-1) };
                        QPointF points2[3] = { QPointF(r.left(), r.center().y()+1),
                                               QPointF(r.center().x(), r.bottom()-1),
                                               QPointF(r.right(), r.center().y()+1) };
                        painter.drawPolygon(points1, 3);
                        painter.drawPolygon(points2, 3);
                    } else if (drawUp) {
mae's avatar
mae committed

                        // check that we are not collapsed
                        QTextBlock open = doc->findBlockByNumber(d->m_highlightBlocksInfo.open.last());
                        if (open.next().isVisible()) {

                            QPointF points[3] = { QPointF(r.left(), r.bottom()-1),
                                                  QPointF(r.center().x(), r.center().y()),
                                                  QPointF(r.right(), r.bottom()-1) };
                            painter.drawPolygon(points, 3);
                        }
                    } else if(drawDown) {
                        QPointF points[3] = { QPointF(r.left(), r.top()),
                                               QPointF(r.center().x(), r.center().y()),
                                               QPointF(r.right(), r.top()) };
                        painter.drawPolygon(points, 3);
                    }
                    painter.translate(-.5, -.5);
                    painter.setRenderHint(QPainter::Antialiasing, false);
con's avatar
con committed
            painter.restore();
        }


        if (d->m_revisionsVisible && block.revision() != documentLayout->lastSaveRevision) {
            painter.save();
            painter.setRenderHint(QPainter::Antialiasing, false);
            if (block.revision() < 0)
                painter.setPen(QPen(Qt::darkGreen, 2));
            else
                painter.setPen(QPen(Qt::red, 2));
            painter.drawLine(extraAreaWidth-1, top, extraAreaWidth-1, bottom-1);
            painter.restore();
        }

        if (d->m_lineNumbersVisible) {
            const QString &number = QString::number(blockNumber + 1);
mae's avatar
mae committed
            bool selected = (
                    selStart < block.position() + block.length()
                    && selEnd > block.position()
                    );
            if (selected) {
                QFont f = painter.font();
                f.setBold(d->m_currentLineNumberFormat.font().bold());
                f.setItalic(d->m_currentLineNumberFormat.font().italic());
                painter.setFont(f);
                painter.setPen(d->m_currentLineNumberFormat.foreground().color());
con's avatar
con committed
            painter.drawText(markWidth, top, extraAreaWidth - markWidth - 4, fm.height(), Qt::AlignRight, number);
mae's avatar
mae committed
            if (selected)
                painter.restore();
con's avatar
con committed
        }

        block = nextVisibleBlock;
        blockNumber = nextVisibleBlockNumber;
    }

    if (d->m_codeFoldingVisible) {
        painter.drawLine(extraAreaWidth, 0,
                         extraAreaWidth, viewport()->height());
        painter.drawLine(extraAreaWidth + collapseBoxWidth - 1, 0,
                         extraAreaWidth + collapseBoxWidth - 1, viewport()->height());
//        if (!cb.isEmpty()) {
//            QPen pen(baseColor.value() < 128 ? Qt::white : Qt::black);
//            pen.setWidth(2);
//            painter.setPen(pen);
//            painter.setRenderHint(QPainter::Antialiasing, true);
//            painter.translate(.5, .5);
//            painter.drawRoundedRect(QRect(cb.adjusted(0, 0,-2, -2)), 4, 4);
//        }
    }
con's avatar
con committed
}

void BaseTextEditor::slotModificationChanged(bool m)
{
    if (m)
        return;

    QTextDocument *doc = document();
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
    QTC_ASSERT(documentLayout, return);
    int oldLastSaveRevision = documentLayout->lastSaveRevision;
    documentLayout->lastSaveRevision = doc->revision();

    if (oldLastSaveRevision != documentLayout->lastSaveRevision) {
        QTextBlock block = doc->begin();
        while (block.isValid()) {
            if (block.revision() < 0 || block.revision() != oldLastSaveRevision) {
                block.setRevision(-documentLayout->lastSaveRevision - 1);
            } else {
                block.setRevision(documentLayout->lastSaveRevision);
            }
            block = block.next();
        }
    }
    d->m_extraArea->update();
}

void BaseTextEditor::slotUpdateRequest(const QRect &r, int dy)
{
    if (dy)
        d->m_extraArea->scroll(0, dy);
    else if (r.width() > 4) { // wider than cursor width, not just cursor blinking
        d->m_extraArea->update(0, r.y(), d->m_extraArea->width(), r.height());
    }

    if (r.contains(viewport()->rect()))
        slotUpdateExtraAreaWidth();
}

void BaseTextEditor::saveCurrentCursorPositionForNavigation()
{
    d->m_lastCursorChangeWasInteresting = true;
    d->m_tempNavigationState = saveState();
void BaseTextEditor::slotCursorPositionChanged()
{
    if (!d->m_contentsChanged && d->m_lastCursorChangeWasInteresting) {
        Core::EditorManager::instance()->addCurrentPositionToNavigationHistory(d->m_tempNavigationState);
        d->m_lastCursorChangeWasInteresting = false;
    } else if (d->m_contentsChanged) {
        saveCurrentCursorPositionForNavigation();
    }

    if (d->m_parenthesesMatchingEnabled) {
        // Delay update when no matching is displayed yet, to avoid flicker
        if (extraSelections(ParenthesesMatchingSelection).isEmpty()) {
            d->m_parenthesesMatchingTimer->start(50);
        } else {
            _q_matchParentheses();
        }
    }

    QList<QTextEdit::ExtraSelection> extraSelections;

    if (d->m_highlightCurrentLine) {
        QTextEdit::ExtraSelection sel;
        sel.format.setBackground(d->m_currentLineFormat.background());
        sel.format.setProperty(QTextFormat::FullWidthSelection, true);
        sel.cursor = textCursor();
        sel.cursor.clearSelection();
        extraSelections.append(sel);
    }

    setExtraSelections(CurrentLineSelection, extraSelections);
        QTextCursor cursor = textCursor();
        d->extraAreaHighlightCollapseBlockNumber = cursor.blockNumber();
        d->extraAreaHighlightCollapseColumn = cursor.position() - cursor.block().position();
        d->m_highlightBlocksTimer->start(100);
    }
}

void BaseTextEditor::slotUpdateBlockNotify(const QTextBlock &block)
{
    static bool blockRecursion = false;
    if (blockRecursion)
        return;
    if (block.previous().isValid() && block.userState() != block.previous().userState()) {
        /* The syntax highlighting state changes. This opens up for
           the possibility that the paragraph has braces that support
           code folding. In this case, do the save thing and also
           update the previous block, which might contain a collapse
           box which now is invalid.*/
        blockRecursion = true;
        emit requestBlockUpdate(block.previous());
        blockRecursion = false;
    }
}

con's avatar
con committed
void BaseTextEditor::timerEvent(QTimerEvent *e)
{
    if (e->timerId() == d->autoScrollTimer.timerId()) {
        const QPoint globalPos = QCursor::pos();
        const QPoint pos = d->m_extraArea->mapFromGlobal(globalPos);
        QRect visible = d->m_extraArea->rect();
        verticalScrollBar()->triggerAction( pos.y() < visible.center().y() ?
                                            QAbstractSlider::SliderSingleStepSub
                                            : QAbstractSlider::SliderSingleStepAdd);
        QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
        extraAreaMouseEvent(&ev);
        int delta = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height();
        if (delta < 7)
            delta = 7;
        int timeout = 4900 / (delta * delta);
        d->autoScrollTimer.start(timeout, this);

    } else if (e->timerId() == d->collapsedBlockTimer.timerId()) {
        d->visibleCollapsedBlockNumber = d->suggestedVisibleCollapsedBlockNumber;
        d->suggestedVisibleCollapsedBlockNumber = -1;
        d->collapsedBlockTimer.stop();
        viewport()->update();
    }
    QPlainTextEdit::timerEvent(e);
}


void BaseTextEditorPrivate::clearVisibleCollapsedBlock()
{
    if (suggestedVisibleCollapsedBlockNumber) {
        suggestedVisibleCollapsedBlockNumber = -1;
        collapsedBlockTimer.stop();
    }
    if (visibleCollapsedBlockNumber >= 0) {
        visibleCollapsedBlockNumber = -1;
        q->viewport()->update();
    }
}


void BaseTextEditor::mouseMoveEvent(QMouseEvent *e)
{
    d->m_lastEventWasBlockSelectionEvent = (e->modifiers() & Qt::AltModifier);
    if (e->buttons() == Qt::NoButton) {
        const QTextBlock collapsedBlock = collapsedBlockAt(e->pos());
        const int blockNumber = collapsedBlock.next().blockNumber();
con's avatar
con committed
        if (blockNumber < 0) {
            d->clearVisibleCollapsedBlock();
        } else if (blockNumber != d->visibleCollapsedBlockNumber) {
            d->suggestedVisibleCollapsedBlockNumber = blockNumber;
            d->collapsedBlockTimer.start(40, this);
        }

        // Update the mouse cursor
        if (collapsedBlock.isValid() && !d->m_mouseOnCollapsedMarker) {
            d->m_mouseOnCollapsedMarker = true;
            viewport()->setCursor(Qt::PointingHandCursor);
        } else if (!collapsedBlock.isValid() && d->m_mouseOnCollapsedMarker) {
            d->m_mouseOnCollapsedMarker = false;
            viewport()->setCursor(Qt::IBeamCursor);
        }
con's avatar
con committed
    } else {
        QPlainTextEdit::mouseMoveEvent(e);
    }
    if (d->m_lastEventWasBlockSelectionEvent && d->m_inBlockSelectionMode) {
        if (textCursor().atBlockEnd()) {
            d->m_blockSelectionExtraX = qMax(0, e->pos().x() - cursorRect().center().x()) / fontMetrics().averageCharWidth();
con's avatar
con committed
        } else {
            d->m_blockSelectionExtraX = 0;
        }
    }
}

void BaseTextEditor::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton) {
        d->clearBlockSelection(); // just in case, otherwise we might get strange drag and drop

        QTextBlock collapsedBlock = collapsedBlockAt(e->pos());
        if (collapsedBlock.isValid()) {
            toggleBlockVisible(collapsedBlock);
            viewport()->setCursor(Qt::IBeamCursor);
        }
    }
    QPlainTextEdit::mousePressEvent(e);
}

void BaseTextEditor::extraAreaLeaveEvent(QEvent *)
{
    // fake missing mouse move event from Qt
    QMouseEvent me(QEvent::MouseMove, QPoint(-1, -1), Qt::NoButton, 0, 0);
    extraAreaMouseEvent(&me);
con's avatar
con committed
}

void BaseTextEditor::extraAreaMouseEvent(QMouseEvent *e)
{
    QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
    cursor.setPosition(cursor.block().position());

    int markWidth;
    extraAreaWidth(&markWidth);

    if (d->m_codeFoldingVisible
        && e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking
        // Update which folder marker is highlighted
        const int highlightBlockNumber = d->extraAreaHighlightCollapseBlockNumber;
        const int highlightColumn = d->extraAreaHighlightCollapseColumn;
con's avatar
con committed
        d->extraAreaHighlightCollapseBlockNumber = -1;
        d->extraAreaHighlightCollapseColumn = -1;
        if (d->m_highlightBlocks) {
            QTextCursor cursor  = textCursor();
con's avatar
con committed
            d->extraAreaHighlightCollapseBlockNumber = cursor.blockNumber();
            d->extraAreaHighlightCollapseColumn = cursor.position() - cursor.block().position();
        }
con's avatar
con committed

        int collapseBoxWidth = fontMetrics().lineSpacing() + 1;
        if (e->pos().x() > extraArea()->width() - collapseBoxWidth) {
            d->extraAreaHighlightCollapseBlockNumber = cursor.blockNumber();
            if (!TextBlockUserData::hasClosingCollapse(cursor.block()))
                d->extraAreaHighlightCollapseColumn = cursor.block().length()-1;
con's avatar
con committed
        }
        if (highlightBlockNumber != d->extraAreaHighlightCollapseBlockNumber
            || highlightColumn != d->extraAreaHighlightCollapseColumn)
mae's avatar
mae committed
            d->m_highlightBlocksTimer->start(40);
con's avatar
con committed
    }

    if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
        if (e->button() == Qt::LeftButton) {
            int collapseBoxWidth = fontMetrics().lineSpacing() + 1;
            if (d->m_codeFoldingVisible && e->pos().x() > extraArea()->width() - collapseBoxWidth) {
                if (!cursor.block().next().isVisible()) {
                    toggleBlockVisible(cursor.block());
                    d->moveCursorVisible(false);
                } else if (collapseBox().contains(e->pos())) {
                    cursor.setPosition(
                            document()->findBlockByNumber(d->m_highlightBlocksInfo.open.last()).position()
                            );
                    QTextBlock c = cursor.block();
                    if (!TextBlockUserData::canCollapse(c))
                        c = c.previous();
                    toggleBlockVisible(c);
                    d->moveCursorVisible(false);
                }
            } else if (d->m_marksVisible && e->pos().x() > markWidth) {
con's avatar
con committed
                QTextCursor selection = cursor;
                selection.setVisualNavigation(true);
                d->extraAreaSelectionAnchorBlockNumber = selection.blockNumber();
                selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
                selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
                setTextCursor(selection);
            } else {
                d->extraAreaToggleMarkBlockNumber = cursor.blockNumber();
            }
        } else if (d->m_marksVisible && e->button() == Qt::RightButton) {
            QMenu * contextMenu = new QMenu(this);
mae's avatar
mae committed
            emit d->m_editable->markContextMenuRequested(editableInterface(), cursor.blockNumber() + 1, contextMenu);
            if (!contextMenu->isEmpty())
                contextMenu->exec(e->globalPos());
            delete contextMenu;
con's avatar
con committed
        }
    } else if (d->extraAreaSelectionAnchorBlockNumber >= 0) {
        QTextCursor selection = cursor;
        selection.setVisualNavigation(true);
        if (e->type() == QEvent::MouseMove) {
            QTextBlock anchorBlock = document()->findBlockByNumber(d->extraAreaSelectionAnchorBlockNumber);
            selection.setPosition(anchorBlock.position());
            if (cursor.blockNumber() < d->extraAreaSelectionAnchorBlockNumber) {
                selection.movePosition(QTextCursor::EndOfBlock);
                selection.movePosition(QTextCursor::Right);
            }
            selection.setPosition(cursor.block().position(), QTextCursor::KeepAnchor);
            if (cursor.blockNumber() >= d->extraAreaSelectionAnchorBlockNumber) {
                selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
                selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
            }

            if (e->pos().y() >= 0 && e->pos().y() <= d->m_extraArea->height())
                d->autoScrollTimer.stop();
            else if (!d->autoScrollTimer.isActive())
                d->autoScrollTimer.start(100, this);

        } else {
            d->autoScrollTimer.stop();
            d->extraAreaSelectionAnchorBlockNumber = -1;
            return;
        }
        setTextCursor(selection);
    } else if (d->extraAreaToggleMarkBlockNumber >= 0 && d->m_marksVisible && d->m_requestMarkEnabled) {
        if (e->type() == QEvent::MouseButtonRelease && e->button() == Qt::LeftButton) {
            int n = d->extraAreaToggleMarkBlockNumber;
            d->extraAreaToggleMarkBlockNumber = -1;
            if (cursor.blockNumber() == n) {
                int line = n + 1;
                emit d->m_editable->markRequested(editableInterface(), line);
con's avatar
con committed
            }
        }
    }
}

QTextBlock TextBlockUserData::testCollapse(const QTextBlock& block)
{
    QTextBlock info = block;
    if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->collapseMode() == CollapseAfter)
        ;
    else if (block.next().userData()
             && static_cast<TextBlockUserData*>(block.next().userData())->collapseMode()
             == TextBlockUserData::CollapseThis)
        info = block.next();
    else
        return QTextBlock();
    int pos = static_cast<TextBlockUserData*>(info.userData())->collapseAtPos();
    if (pos < 0)
        return QTextBlock();
    QTextCursor cursor(info);
    cursor.setPosition(cursor.position() + pos);
    matchCursorForward(&cursor);
    return cursor.block();
}

void TextBlockUserData::doCollapse(const QTextBlock& block, bool visible)
{
    QTextBlock info = block;
    if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->collapseMode() == CollapseAfter)
        ;
    else if (block.next().userData()
             && static_cast<TextBlockUserData*>(block.next().userData())->collapseMode()
             == TextBlockUserData::CollapseThis)
        info = block.next();
    else {
        if (visible && !block.next().isVisible()) {
            // no match, at least unfold!
            QTextBlock b = block.next();
            while (b.isValid() && !b.isVisible()) {
                b.setVisible(true);
                b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0);
                b = b.next();
            }
        }
        return;
    }
    int pos = static_cast<TextBlockUserData*>(info.userData())->collapseAtPos();
    if (pos < 0)
        return;
    QTextCursor cursor(info);
    cursor.setPosition(cursor.position() + pos);
    if (matchCursorForward(&cursor) != Match) {
        if (visible) {
            // no match, at least unfold!
            QTextBlock b = block.next();
            while (b.isValid() && !b.isVisible()) {
                b.setVisible(true);
                b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0);
                b = b.next();
            }
        }
        return;
    }

    QTextBlock b = block.next();
    while (b < cursor.block()) {
        b.setVisible(visible);
        b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0);
        if (visible) {
            TextBlockUserData *data = canCollapse(b);
            if (data && data->collapsed()) {
                QTextBlock end =  testCollapse(b);
                if (data->collapseIncludesClosure())
                    end = end.next();
                if (end.isValid()) {
                    b = end;
                    continue;
                }
            }
        }
        b = b.next();
    }

    bool collapseIncludesClosure = hasClosingCollapseAtEnd(b);
    if (collapseIncludesClosure) {
        b.setVisible(visible);
        b.setLineCount(visible ? qMax(1, b.layout()->lineCount()) : 0);
    }
    static_cast<TextBlockUserData*>(info.userData())->setCollapseIncludesClosure(collapseIncludesClosure);
    static_cast<TextBlockUserData*>(info.userData())->setCollapsed(!block.next().isVisible());

}


void BaseTextEditor::ensureCursorVisible()
{
    QTextBlock block = textCursor().block();
    if (!block.isVisible()) {
        while (!block.isVisible() && block.previous().isValid())
            block = block.previous();
        toggleBlockVisible(block);
    }
    QPlainTextEdit::ensureCursorVisible();
}

void BaseTextEditor::toggleBlockVisible(const QTextBlock &block)
{
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
hjk's avatar
hjk committed
    QTC_ASSERT(documentLayout, return);
con's avatar
con committed

    bool visible = block.next().isVisible();
    TextBlockUserData::doCollapse(block, !visible);
    documentLayout->requestUpdate();
    documentLayout->emitDocumentSizeChanged();
}


const TabSettings &BaseTextEditor::tabSettings() const
{
    return d->m_document->tabSettings();
}

const DisplaySettings &BaseTextEditor::displaySettings() const
{
    return d->m_displaySettings;
}


void BaseTextEditor::indentOrUnindent(bool doIndent)
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();

    int pos = cursor.position();
    const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings();


    QTextDocument *doc = document();
    if (!cursor.hasSelection()
        || (doc->findBlock(cursor.selectionStart()) == doc->findBlock(cursor.selectionEnd()) )) {
        cursor.removeSelectedText();
        QTextBlock block = cursor.block();
        QString text = block.text();
        int indentPosition = (cursor.position() - block.position());;
        int spaces = tabSettings.spacesLeftFromPosition(text, indentPosition);
        int startColumn = tabSettings.columnAt(text, indentPosition - spaces);
        int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent);

        cursor.setPosition(block.position() + indentPosition);
        cursor.setPosition(block.position() + indentPosition - spaces, QTextCursor::KeepAnchor);
        cursor.removeSelectedText();
        cursor.insertText(tabSettings.indentationString(startColumn, targetColumn));
    } else {
        int anchor = cursor.anchor();
        int start = qMin(anchor, pos);
        int end = qMax(anchor, pos);

        QTextBlock startBlock = doc->findBlock(start);
        QTextBlock endBlock = doc->findBlock(end-1).next();

        for (QTextBlock block = startBlock; block != endBlock; block = block.next()) {
            QString text = block.text();
            int indentPosition = tabSettings.lineIndentPosition(text);
            if (!doIndent && !indentPosition)
                indentPosition = tabSettings.firstNonSpace(text);
            int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent);
            cursor.setPosition(block.position() + indentPosition);
            cursor.insertText(tabSettings.indentationString(0, targetColumn));
            cursor.setPosition(block.position());
            cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor);
            cursor.removeSelectedText();
        }
    }

    cursor.endEditBlock();
}

void BaseTextEditor::handleHomeKey(bool anchor)
{
    QTextCursor cursor = textCursor();
    QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;

    if (anchor)
        mode = QTextCursor::KeepAnchor;

    const int initpos = cursor.position();
    int pos = cursor.block().position();
    QChar character = characterAt(pos);
    const QLatin1Char tab = QLatin1Char('\t');

    while (character == tab || character.category() == QChar::Separator_Space) {
        ++pos;
con's avatar
con committed
        character = characterAt(pos);
    }

    // Go to the start of the block when we're already at the start of the text
    if (pos == initpos)
        pos = cursor.block().position();

    cursor.setPosition(pos, mode);
    setTextCursor(cursor);
}

void BaseTextEditor::handleBackspaceKey()
{
    QTextCursor cursor = textCursor();
hjk's avatar
hjk committed
    QTC_ASSERT(!cursor.hasSelection(), return);
con's avatar
con committed

    const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings();
    QTextBlock currentBlock = cursor.block();
    int positionInBlock = cursor.position() - currentBlock.position();
    const QString blockText = currentBlock.text();
    if (cursor.atBlockStart() || tabSettings.firstNonSpace(blockText) < positionInBlock) {
        cursor.deletePreviousChar();
        return;
    }

    int previousIndent = 0;
    const int indent = tabSettings.columnAt(blockText, positionInBlock);

    for (QTextBlock previousNonEmptyBlock = currentBlock.previous();
         previousNonEmptyBlock.isValid();
         previousNonEmptyBlock = previousNonEmptyBlock.previous()) {
        QString previousNonEmptyBlockText = previousNonEmptyBlock.text();
        if (previousNonEmptyBlockText.trimmed().isEmpty())
            continue;
        previousIndent = tabSettings.columnAt(previousNonEmptyBlockText,
                                              tabSettings.firstNonSpace(previousNonEmptyBlockText));
        if (previousIndent < indent)
            break;
    }

    if (previousIndent >= indent)
        previousIndent = 0;

    cursor.beginEditBlock();
    cursor.setPosition(currentBlock.position(), QTextCursor::KeepAnchor);
    cursor.insertText(tabSettings.indentationString(0, previousIndent));
    cursor.endEditBlock();
}

void BaseTextEditor::wheelEvent(QWheelEvent *e)
{
  d->clearVisibleCollapsedBlock();
    if (e->modifiers() & Qt::ControlModifier) {
        const int delta = e->delta();
        if (delta < 0)
            zoomOut();
        else if (delta > 0)
            zoomIn();
        return;
    }
    QPlainTextEdit::wheelEvent(e);
}

void BaseTextEditor::zoomIn(int range)
{
  d->clearVisibleCollapsedBlock();
    QFont f = font();
    const int newSize = f.pointSize() + range;
    if (newSize <= 0)
        return;
    f.setPointSize(newSize);
    setFont(f);
}

void BaseTextEditor::zoomOut(int range)
{
    zoomIn(-range);
}

bool BaseTextEditor::isElectricCharacter(const QChar &) const
{
    return false;
}

void BaseTextEditor::indentBlock(QTextDocument *, QTextBlock, QChar)
{
}

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