Skip to content
Snippets Groups Projects
basetexteditor.cpp 179 KiB
Newer Older
con's avatar
con committed
    QPointF offset(contentOffset());
    QTextBlock block = firstVisibleBlock();
    qreal top = blockBoundingGeometry(block).translated(offset).top();
    qreal bottom = top + blockBoundingRect(block).height();
con's avatar
con committed

    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 + blockBoundingRect(block).height();
con's avatar
con committed
    }
    return QTextBlock();
}

void BaseTextEditorPrivate::highlightSearchResults(const QTextBlock &block,
                                                   TextEditorOverlay *overlay)
con's avatar
con committed
{
    if (m_searchExpr.isEmpty())
        return;

    int blockPosition = block.position();

    QTextCursor cursor = q->textCursor();
con's avatar
con committed
    QString text = block.text();
    text.replace(QChar::Nbsp, QLatin1Char(' '));
    int idx = -1;

    int m_findScopeFirstColumn = 0;
mae's avatar
mae committed
    if (!m_findScopeStart.isNull() && m_findScopeVerticalBlockSelection)  {
        m_findScopeFirstColumn = m_findScopeStart.positionInBlock() + 1;
con's avatar
con committed
    while (idx < text.length()) {
        idx = m_searchExpr.indexIn(text, idx + l);
con's avatar
con committed
        if (idx < 0)
            break;
        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;

mae's avatar
mae committed
        if (!m_findScopeStart.isNull()) {
            if (blockPosition + idx < m_findScopeStart.position()
                || blockPosition + idx + l > m_findScopeEnd.position())
                continue;
            if (m_findScopeVerticalBlockSelection) {
                if (idx < m_findScopeFirstColumn
                    || idx + l > m_findScopeFirstColumn + m_findScopeVerticalBlockSelection)
                    continue;
            }
        }

        overlay->addOverlaySelection(blockPosition + idx,
                                     blockPosition + idx + l,
                                     m_searchResultFormat.background().color().darker(120),
                                     QColor(),
                                     (idx == cursor.selectionStart() - blockPosition
                                      && idx + l == cursor.selectionEnd() - blockPosition)?
                                     TextEditorOverlay::DropShadow : 0);
con's avatar
con committed
    }
}


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();
}

static QColor blendColors(const QColor &a, const QColor &b, int alpha)
{
    return QColor((a.red()   * (256 - alpha) + b.red()   * alpha) / 256,
                  (a.green() * (256 - alpha) + b.green() * alpha) / 256,
                  (a.blue()  * (256 - alpha) + b.blue()  * alpha) / 256);
}

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 blendColors(color80, color90, blendFactor);
mae's avatar
mae committed
}

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();
    BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
hjk's avatar
hjk committed
    QTC_ASSERT(documentLayout, return);
con's avatar
con committed

    QPointF offset(contentOffset());
    QTextBlock textCursorBlock = textCursor().block();
con's avatar
con committed

    bool hasMainSelection = textCursor().hasSelection();
    bool suppressSyntaxInIfdefedOutBlock = (d->m_ifdefedOutFormat.foreground()
                                           != palette().foreground());
con's avatar
con committed
    QRect er = e->rect();
    QRect viewportRect = viewport()->rect();

    if (d->m_visibleWrapColumn > 0) {
        lineX = fontMetrics().averageCharWidth() * d->m_visibleWrapColumn + offset.x() + 4;

        if (lineX < viewportRect.width()) {
            const QBrush background = d->m_ifdefedOutFormat.background();
            painter.fillRect(QRectF(lineX, er.top(), viewportRect.width() - lineX, er.height()),

            const QColor col = (palette().base().color().value() > 128) ? Qt::black : Qt::white;
            const QPen pen = painter.pen();
            painter.setPen(blendColors(background.color(), col, 32));
            painter.drawLine(QPointF(lineX, er.top()), QPointF(lineX, er.bottom()));
            painter.setPen(pen);
        }
    // Set a brush origin so that the WaveUnderline knows where the wave started
    painter.setBrushOrigin(offset);

//    // 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();

    QAbstractTextDocumentLayout::PaintContext context = getPaintContext();

    if (!d->m_highlightBlocksInfo.isEmpty()) {
        const QColor baseColor = palette().base().color();

        // extra pass for the block highlight

        const int margin = 5;
        QTextBlock blockFP = block;
        QPointF offsetFP = offset;
        while (blockFP.isValid()) {
            QRectF r = blockBoundingRect(blockFP).translated(offsetFP);

            int n = blockFP.blockNumber();
            int depth = 0;
            foreach (int i, d->m_highlightBlocksInfo.open)
                if (n >= i)
                    ++depth;
            foreach (int i, d->m_highlightBlocksInfo.close)
                if (n > i)
                    --depth;

            int count = d->m_highlightBlocksInfo.count();
            if (count) {
                QRectF rr = r;
                rr.setWidth(viewport()->width());
                if (lineX > 0)
                    rr.setRight(qMin(lineX, rr.right()));
                for (int i = 0; i <= depth; ++i) {
                    int vi = i > 0 ? d->m_highlightBlocksInfo.visualIndent.at(i-1) : 0;
                    painter.fillRect(rr.adjusted(vi, 0, -8*i, 0), calcBlendColor(baseColor, i, count));
                    if (d->m_highlightCurrentLine && blockFP == textCursorBlock) {
                        QRectF rrr = blockFP.layout()->lineForTextPosition(textCursor().positionInBlock()).rect();
                        rrr.moveTop(rrr.top() + rr.top());
                        rrr.setLeft(rr.left());
                        rrr.setRight(rr.right());
                        painter.fillRect(rrr.adjusted(vi, 0, -8*i, 0),
                                         calcBlendColor(d->m_currentLineFormat.background().color(), i, count));
                    }
                }
            }
            offsetFP.ry() += r.height();

            if (offsetFP.y() > viewportRect.height() + margin)
                break;

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



mae's avatar
mae committed
    if (!d->m_findScopeStart.isNull()) {

        TextEditorOverlay *overlay = new TextEditorOverlay(this);
mae's avatar
mae committed
        overlay->addOverlaySelection(d->m_findScopeStart.position(),
                                     d->m_findScopeEnd.position(),
mae's avatar
mae committed
                                     d->m_searchScopeFormat.background().color().darker(120),
                                     d->m_searchScopeFormat.background().color(),
mae's avatar
mae committed
                                     TextEditorOverlay::ExpandBegin,
                                     d->m_findScopeVerticalBlockSelection);
        overlay->setAlpha(false);
        overlay->paint(&painter, e->rect());
        delete overlay;
con's avatar
con committed
    }

    BlockSelectionData *blockSelection = 0;

    if (d->m_inBlockSelectionMode
        && context.selections.count() && context.selections.last().cursor == textCursor()) {
        blockSelection = new BlockSelectionData;
        blockSelection->selectionIndex = context.selections.size()-1;
        const QAbstractTextDocumentLayout::Selection &selection = context.selections[blockSelection->selectionIndex];
        int start = blockSelection->selectionStart = selection.cursor.selectionStart();
        int end = blockSelection->selectionEnd = selection.cursor.selectionEnd();
        QTextBlock block = doc->findBlock(start);
        int columnA = start - block.position();
        block = doc->findBlock(end);
        int columnB = end - block.position();
        blockSelection->firstColumn = qMin(columnA, columnB);
        blockSelection->lastColumn = qMax(columnA, columnB) + d->m_blockSelectionExtraX;
    }

    QTextBlock visibleCollapsedBlock;
    QPointF visibleCollapsedBlockOffset;

    QTextLayout *cursor_layout = 0;
    QPointF cursor_offset;
    int cursor_cpos = 0;
    QPen cursor_pen;
    d->m_searchResultOverlay->clear();
    if (!d->m_searchExpr.isEmpty()) { // first pass for the search result overlays
        const int margin = 5;
        QTextBlock blockFP = block;
        QPointF offsetFP = offset;
        while (blockFP.isValid()) {
            QRectF r = blockBoundingRect(blockFP).translated(offsetFP);
con's avatar
con committed

            if (r.bottom() >= er.top() - margin && r.top() <= er.bottom() + margin) {
                d->highlightSearchResults(blockFP,
                                          d->m_searchResultOverlay);
            }
            offsetFP.ry() += r.height();

            if (offsetFP.y() > viewportRect.height() + margin)
                break;
            blockFP = blockFP.next();
            if (!blockFP.isVisible()) {
                // invisible blocks do have zero line count
                blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
            }
    } // end first pass

    d->m_searchResultOverlay->fill(&painter,
                                   d->m_searchResultFormat.background().color(),
                                   e->rect());
    while (block.isValid()) {

        QRectF r = blockBoundingRect(block).translated(offset);

        if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
            if (BaseTextDocumentLayout::ifdefedOut(block)) {
                rr.setRight(viewportRect.width() - offset.x());
                if (lineX > 0)
                    rr.setRight(qMin(lineX, rr.right()));
                painter.fillRect(rr, d->m_ifdefedOutFormat.background());
            }

            QTextLayout *layout = block.layout();
con's avatar
con committed

            QTextOption option = layout->textOption();
            if (suppressSyntaxInIfdefedOutBlock && BaseTextDocumentLayout::ifdefedOut(block)) {
                option.setFlags(option.flags() | QTextOption::SuppressColors);
                painter.setPen(d->m_ifdefedOutFormat.foreground().color());
            } else {
                option.setFlags(option.flags() & ~QTextOption::SuppressColors);
                painter.setPen(context.palette.text().color());
            }
            layout->setTextOption(option);
            layout->setFont(doc->defaultFont()); // this really should be in qplaintextedit when creating the layout!
con's avatar
con committed

            int blpos = block.position();
            int bllen = block.length();

            QVector<QTextLayout::FormatRange> selections;
            QVector<QTextLayout::FormatRange> prioritySelections;
con's avatar
con committed

            for (int i = 0; i < context.selections.size(); ++i) {
                const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
                const int selStart = range.cursor.selectionStart() - blpos;
                const int selEnd = range.cursor.selectionEnd() - blpos;
                if (selStart < bllen && selEnd >= 0
con's avatar
con committed
                    && selEnd >= selStart) {
con's avatar
con committed
                    QTextLayout::FormatRange o;
                    o.start = selStart;
                    o.length = selEnd - selStart;
                    o.format = range.format;
                    if (blockSelection && blockSelection->selectionIndex == i) {
                        o.start = qMin(blockSelection->firstColumn, bllen-1);
                        o.length = qMin(blockSelection->lastColumn, bllen-1) - o.start;
                    }
                    if ((hasMainSelection && i == context.selections.size()-1)
                        || (o.format.foreground().style() == Qt::NoBrush
                        && o.format.underlineStyle() != QTextCharFormat::NoUnderline
                        && o.format.background() == Qt::NoBrush))
                        prioritySelections.append(o);
con's avatar
con committed
                    else
                        selections.append(o);
                }
#if 0
                // we disable fullwidth selection. It's only used for m_highlightCurrentLine which we
                // do differently now
                else if (!range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
                    && block.contains(range.cursor.position())) {
con's avatar
con committed
                    // for full width selections we don't require an actual selection, just
                    // a position to specify the line. that's more convenience in usage.
                    QTextLayout::FormatRange o;
                    QTextLine l = layout->lineForTextPosition(range.cursor.position() - blpos);
                    o.start = l.textStart();
                    o.length = l.textLength();
                    if (o.start + o.length == bllen - 1)
                        ++o.length; // include newline
                    o.format = range.format;
con's avatar
con committed
                }
con's avatar
con committed
            }
            selections += prioritySelections;
con's avatar
con committed

            if (d->m_highlightCurrentLine && block == textCursorBlock) {

                QRectF rr = layout->lineForTextPosition(textCursor().positionInBlock()).rect();
                rr.moveTop(rr.top() + r.top());
                rr.setLeft(0);
                rr.setRight(viewportRect.width() - offset.x());
                if (lineX > 0) {
                    if (lineX < rr.right()) {
                        QRectF rrr = rr;
                        rrr.setLeft(lineX);
                        painter.fillRect(rrr,
                                         blendColors(
                                                 d->m_currentLineFormat.background().color(),
                                                 d->m_ifdefedOutFormat.background().color(),
                                                 50)
                                         );
                        const QColor col = (palette().base().color().value() > 128) ? Qt::black : Qt::white;
                        const QPen pen = painter.pen();
                        painter.setPen(blendColors(d->m_currentLineFormat.background().color(), col, 32));
                        painter.drawLine(QPointF(lineX, rr.top()), QPointF(lineX, rr.bottom()));
                        painter.setPen(pen);
                    }
                    rr.setRight(qMin(lineX, rr.right()));
                }
                if (d->m_highlightBlocksInfo.isEmpty() || BaseTextDocumentLayout::ifdefedOut(block))
                    painter.fillRect(rr, d->m_currentLineFormat.background());
            }

            bool drawCursor = ((editable || true) // we want the cursor in read-only mode
                               && context.cursorPosition >= blpos
                               && context.cursorPosition < blpos + bllen);

            bool drawCursorAsBlock = drawCursor && overwriteMode() ;

            if (drawCursorAsBlock) {
                int relativePos = context.cursorPosition - blpos;
                QTextLine line = layout->lineForTextPosition(relativePos);
                qreal x = line.cursorToX(relativePos);
                qreal w = 0;
                if (relativePos < line.textLength() - line.textStart()) {
                    w = line.cursorToX(relativePos + 1) - x;
                    if (doc->characterAt(context.cursorPosition) == QLatin1Char('\t')) {
                        int space = QFontMetrics(layout->font()).width(QLatin1Char(' '));
                        if (w > space) {
                            x += w-space;
                            w = space;
                        }
                    }
                } else
                    w = QFontMetrics(layout->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()

                QRectF rr = line.rect();
                rr.moveTop(rr.top() + r.top());
                rr.moveLeft(r.left() + x);
                rr.setWidth(w);
                painter.fillRect(rr, palette().text());
                if (doSelection) {
                    QTextLayout::FormatRange o;
                    o.start = relativePos;
                    o.length = 1;
                    o.format.setForeground(palette().base());
                    selections.append(o);
                }
con's avatar
con committed
            layout->draw(&painter, offset, selections, er);

            if ((drawCursor && !drawCursorAsBlock)
                || (editable && context.cursorPosition < -1
                    && !layout->preeditAreaText().isEmpty())) {
                int cpos = context.cursorPosition;
                if (cpos < -1)
                    cpos = layout->preeditAreaPosition() - (cpos + 2);
                else
                    cpos -= blpos;
                cursor_layout = layout;
                cursor_offset = offset;
                cursor_cpos = cpos;
                cursor_pen = painter.pen();
con's avatar
con committed
            }
        }

        offset.ry() += r.height();

        if (offset.y() > viewportRect.height())
            break;
con's avatar
con committed
        block = block.next();
con's avatar
con committed
        if (!block.isVisible()) {
            if (block.blockNumber() == d->visibleCollapsedBlockNumber) {
                visibleCollapsedBlock = block;
mae's avatar
mae committed
                visibleCollapsedBlockOffset = offset + QPointF(0,1);
con's avatar
con committed
            }

            // invisible blocks do have zero line count
            block = doc->findBlockByLineNumber(block.firstLineNumber());
        }
    }
mae's avatar
mae committed
    painter.setPen(context.palette.text().color());
con's avatar
con committed

    if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom()
        && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) {
        painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background());
    }

    //end QPlainTextEdit::paintEvent()

    delete blockSelection;

    offset = contentOffset();
    block = firstVisibleBlock();

    qreal top = blockBoundingGeometry(block).translated(offset).top();
    qreal bottom = top + blockBoundingRect(block).height();
con's avatar
con committed

    QTextCursor cursor = textCursor();
    bool hasSelection = cursor.hasSelection();
    int selectionStart = cursor.selectionStart();
    int selectionEnd = cursor.selectionEnd();


    while (block.isValid() && top <= e->rect().bottom()) {
        QTextBlock nextBlock = block.next();
        QTextBlock nextVisibleBlock = nextBlock;

        if (!nextVisibleBlock.isVisible()) {
con's avatar
con committed
            // invisible blocks do have zero line count
            nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
            // paranoia in case our code somewhere did not set the line count
            // of the invisible block to 0
            while (nextVisibleBlock.isValid() && !nextVisibleBlock.isVisible())
                nextVisibleBlock = nextVisibleBlock.next();
        }
con's avatar
con committed
        if (block.isVisible() && bottom >= e->rect().top()) {
            if (d->m_displaySettings.m_visualizeWhitespace) {
                QTextLayout *layout = block.layout();
                int lineCount = layout->lineCount();
                if (lineCount >= 2 || !nextBlock.isValid()) {
                    painter.save();
                    painter.setPen(Qt::lightGray);
                    for (int i = 0; i < lineCount-1; ++i) { // paint line wrap indicator
                        QTextLine line = layout->lineAt(i);
                        QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                        QChar visualArrow((ushort)0x21b5);
                        painter.drawText(QPointF(lineRect.right(),
                                                 lineRect.top() + line.ascent()),
                                         visualArrow);
con's avatar
con committed
                    }
                    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 + blockBoundingRect(block).height();
con's avatar
con committed
    }

mae's avatar
mae committed
    if (d->m_animator && d->m_animator->isRunning()) {
        QTextCursor cursor = textCursor();
        cursor.setPosition(d->m_animator->position());
        d->m_animator->draw(&painter, cursorRect(cursor).topLeft());
    }

    if (d->m_overlay && d->m_overlay->isVisible())
        d->m_overlay->paint(&painter, e->rect());

    if (d->m_snippetOverlay && d->m_snippetOverlay->isVisible())
        d->m_snippetOverlay->paint(&painter, e->rect());
    if (!d->m_searchResultOverlay->isEmpty()) {
        d->m_searchResultOverlay->paint(&painter, e->rect());
        d->m_searchResultOverlay->clear();
    }

    // draw the cursor last, on top of everything
    if (cursor_layout) {
        painter.setPen(cursor_pen);
        cursor_layout->drawCursor(&painter, cursor_offset, cursor_cpos, cursorWidth());
    }

    if (visibleCollapsedBlock.isValid()) {
        drawCollapsedBlockPopup(painter,
                                visibleCollapsedBlock,
                                visibleCollapsedBlockOffset,
                                er);
    }
}

void BaseTextEditor::drawCollapsedBlockPopup(QPainter &painter,
                                             const QTextBlock &block,
                                             QPointF offset,
                                             const QRect &clip)
{
    int margin = block.document()->documentMargin();
    qreal maxWidth = 0;
    qreal blockHeight = 0;
    QTextBlock b = block;

    while (!b.isVisible()) {
        b.setVisible(true); // make sure block bounding rect works
        QRectF r = blockBoundingRect(b).translated(offset);

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

        blockHeight += r.height();

        b.setVisible(false); // restore previous state
        b.setLineCount(0); // restore 0 line count for invisible block
        b = b.next();
    }

    painter.save();
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.translate(.5, .5);
    QBrush brush = palette().base();
    if (d->m_ifdefedOutFormat.hasProperty(QTextFormat::BackgroundBrush))
        brush = d->m_ifdefedOutFormat.background();
    painter.setBrush(brush);
    painter.drawRoundedRect(QRectF(offset.x(),
                                   offset.y(),
                                   maxWidth, blockHeight).adjusted(0, 0, 0, 0), 3, 3);
    painter.restore();

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

        b.setVisible(false); // restore previous state
        offset.ry() += r.height();
        b = b.next();
    }
con's avatar
con committed
}

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

int BaseTextEditor::extraAreaWidth(int *markWidthPtr) const
{
    BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());
con's avatar
con committed
    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) {
        QFont fnt = d->m_extraArea->font();
        // this works under the assumption that bold or italic can only make a font wider
        fnt.setBold(d->m_currentLineNumberFormat.font().bold());
        fnt.setItalic(d->m_currentLineNumberFormat.font().italic());
        const QFontMetrics linefm(fnt);

con's avatar
con committed
        int digits = 2;
        int max = qMax(1, blockCount());
        while (max >= 100) {
            max /= 10;
            ++digits;
        }
        space += linefm.width(QLatin1Char('9')) * digits;
con's avatar
con committed
    }
    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 += collapseBoxWidth(fm);
con's avatar
con committed
    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
}

static void drawRectBox(QPainter *painter, const QRect &rect, bool start, bool end,
                        const QPalette &pal)
{
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, false);

    QRgb b = pal.base().color().rgb();
    QRgb h = pal.highlight().color().rgb();
    QColor c = Utils::StyleHelper::mergedColors(b,h, 50);
    QLinearGradient grad(rect.topLeft(), rect.topRight());
    grad.setColorAt(0, c.lighter(110));
    grad.setColorAt(1, c.lighter(130));

    painter->fillRect(rect, grad);
    painter->setPen(outline);
    if (start)
        painter->drawLine(rect.topLeft() + QPoint(1, 0), rect.topRight() -  QPoint(1, 0));
    if (end)
        painter->drawLine(rect.bottomLeft() + QPoint(1, 0), rect.bottomRight() -  QPoint(1, 0));

    painter->drawLine(rect.topRight() + QPoint(0, start ? 1 : 0), rect.bottomRight() - QPoint(0, end ? 1 : 0));
    painter->drawLine(rect.topLeft() + QPoint(0, start ? 1 : 0), rect.bottomLeft() - QPoint(0, end ? 1 : 0));

    painter->restore();
con's avatar
con committed
void BaseTextEditor::extraAreaPaintEvent(QPaintEvent *e)
{
    QTextDocument *doc = document();
    BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(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();
con's avatar
con committed
    QPalette pal = d->m_extraArea->palette();
    pal.setCurrentColorGroup(QPalette::Active);
    QPainter painter(d->m_extraArea);
    const QFontMetrics fm(d->m_extraArea->font());
con's avatar
con committed
    int fmLineSpacing = fm.lineSpacing();

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

    const int collapseColumnWidth = d->m_codeFoldingVisible ? collapseBoxWidth(fm): 0;
    const int extraAreaWidth = d->m_extraArea->width() - collapseColumnWidth;
con's avatar
con committed

    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();
    qreal top = blockBoundingGeometry(block).translated(contentOffset()).top();
    qreal bottom = top;
con's avatar
con committed

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

        top = bottom;
        const qreal height = blockBoundingRect(block).height();
        bottom = top + height;
con's avatar
con committed
        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();
        }

mae's avatar
mae committed
        if (bottom < e->rect().top()) {
            block = nextVisibleBlock;
            blockNumber = nextVisibleBlockNumber;
            continue;
        }

con's avatar
con committed
        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);