diff options
Diffstat (limited to 'src/plugins/texteditor/texteditor.cpp')
-rw-r--r-- | src/plugins/texteditor/texteditor.cpp | 7343 |
1 files changed, 7343 insertions, 0 deletions
diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp new file mode 100644 index 0000000000..62b443666d --- /dev/null +++ b/src/plugins/texteditor/texteditor.cpp @@ -0,0 +1,7343 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "texteditor.h" +#include "texteditor_p.h" + +#include "autocompleter.h" +#include "behaviorsettings.h" +#include "circularclipboard.h" +#include "circularclipboardassist.h" +#include "codecselector.h" +#include "completionsettings.h" +#include "convenience.h" +#include "highlighterutils.h" +#include "icodestylepreferences.h" +#include "indenter.h" +#include "snippets/snippet.h" +#include "syntaxhighlighter.h" +#include "tabsettings.h" +#include "textdocument.h" +#include "textdocumentlayout.h" +#include "texteditoroverlay.h" +#include "texteditorsettings.h" +#include "typingsettings.h" + +#include <texteditor/codeassist/codeassistant.h> +#include <texteditor/codeassist/assistinterface.h> +#include <texteditor/generichighlighter/context.h> +#include <texteditor/generichighlighter/highlightdefinition.h> +#include <texteditor/generichighlighter/highlighter.h> +#include <texteditor/generichighlighter/highlightersettings.h> +#include <texteditor/generichighlighter/manager.h> + +#include <coreplugin/icore.h> +#include <aggregation/aggregate.h> +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/actionmanager/actioncontainer.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/infobar.h> +#include <coreplugin/manhattanstyle.h> +#include <coreplugin/find/basetextfind.h> +#include <utils/linecolumnlabel.h> +#include <utils/hostosinfo.h> +#include <utils/qtcassert.h> +#include <utils/stylehelper.h> +#include <utils/tooltip/tooltip.h> +#include <utils/tooltip/tipcontents.h> +#include <utils/uncommentselection.h> + +#include <QAbstractTextDocumentLayout> +#include <QApplication> +#include <QClipboard> +#include <QCoreApplication> +#include <QDebug> +#include <QKeyEvent> +#include <QMenu> +#include <QMessageBox> +#include <QMimeData> +#include <QPainter> +#include <QPrintDialog> +#include <QPrinter> +#include <QScrollBar> +#include <QShortcut> +#include <QStyle> +#include <QTextBlock> +#include <QTextCodec> +#include <QTextCursor> +#include <QTextDocumentFragment> +#include <QTextLayout> +#include <QTime> +#include <QTimeLine> +#include <QTimer> +#include <QToolBar> + +//#define DO_FOO + +/*! + \namespace TextEditor + \brief The TextEditor namespace contains the base text editor and several classes which + provide supporting functionality like snippets, highlighting, \l{CodeAssist}{code assist}, + indentation and style, and others. +*/ + +/*! + \namespace TextEditor::Internal + \internal +*/ + +/*! + \class TextEditor::BaseTextEditor + \brief The BaseTextEditor class is base implementation for QPlainTextEdit-based + text editors. It can use the Kate text highlighting definitions, and some basic + auto indentation. + + The corresponding document base class is BaseTextDocument, the corresponding + widget base class is BaseTextEditorWidget. + + It is the default editor for text files used by \QC, if no other editor + implementation matches the MIME type. +*/ + + +using namespace Core; +using namespace Utils; + +namespace TextEditor { +namespace Internal { + +typedef QString (TransformationMethod)(const QString &); + +static QString QString_toUpper(const QString &str) +{ + return str.toUpper(); +} + +static QString QString_toLower(const QString &str) +{ + return str.toLower(); +} + +class BaseTextEditorAnimator : public QObject +{ + Q_OBJECT + +public: + BaseTextEditorAnimator(QObject *parent); + + inline void setPosition(int position) { m_position = position; } + inline int position() const { return m_position; } + + void setData(const QFont &f, const QPalette &pal, const QString &text); + + void draw(QPainter *p, const QPointF &pos); + QRectF rect() const; + + inline qreal value() const { return m_value; } + inline QPointF lastDrawPos() const { return m_lastDrawPos; } + + void finish(); + + bool isRunning() const; + +signals: + void updateRequest(int position, QPointF lastPos, QRectF rect); + +private: + void step(qreal v); + + QTimeLine m_timeline; + qreal m_value; + int m_position; + QPointF m_lastDrawPos; + QFont m_font; + QPalette m_palette; + QString m_text; + QSizeF m_size; +}; + +class TextEditExtraArea : public QWidget +{ +public: + TextEditExtraArea(BaseTextEditorWidget *edit) + : QWidget(edit) + { + textEdit = edit; + setAutoFillBackground(true); + } + +protected: + QSize sizeHint() const { + return QSize(textEdit->extraAreaWidth(), 0); + } + void paintEvent(QPaintEvent *event) { + textEdit->extraAreaPaintEvent(event); + } + void mousePressEvent(QMouseEvent *event) { + textEdit->extraAreaMouseEvent(event); + } + void mouseMoveEvent(QMouseEvent *event) { + textEdit->extraAreaMouseEvent(event); + } + void mouseReleaseEvent(QMouseEvent *event) { + textEdit->extraAreaMouseEvent(event); + } + void leaveEvent(QEvent *event) { + textEdit->extraAreaLeaveEvent(event); + } + void contextMenuEvent(QContextMenuEvent *event) { + textEdit->extraAreaContextMenuEvent(event); + } + + void wheelEvent(QWheelEvent *event) { + QCoreApplication::sendEvent(textEdit->viewport(), event); + } + +private: + BaseTextEditorWidget *textEdit; +}; + +class BaseTextEditorPrivate +{ +public: + BaseTextEditorPrivate() {} + + QPointer<BaseTextEditorFactory> m_origin; +}; + +class BaseTextEditorWidgetPrivate : public QObject +{ +public: + BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent); + ~BaseTextEditorWidgetPrivate() + { + delete m_toolBar; + } + + void setupDocumentSignals(); + void updateLineSelectionColor(); + + void print(QPrinter *printer); + + void maybeSelectLine(); + void updateCannotDecodeInfo(); + void collectToCircularClipboard(); + + void ctor(const QSharedPointer<TextDocument> &doc); + void handleHomeKey(bool anchor); + void handleBackspaceKey(); + void moveLineUpDown(bool up); + void copyLineUpDown(bool up); + void saveCurrentCursorPositionForNavigation(); + void updateHighlights(); + void updateCurrentLineHighlight(); + + void drawFoldingMarker(QPainter *painter, const QPalette &pal, + const QRect &rect, + bool expanded, + bool active, + bool hovered) const; + + void toggleBlockVisible(const QTextBlock &block); + QRect foldBox(); + + QTextBlock foldedBlockAt(const QPoint &pos, QRect *box = 0) const; + + void updateLink(QMouseEvent *e); + void showLink(const BaseTextEditorWidget::Link &); + void clearLink(); + + void universalHelper(); // test function for development + + bool cursorMoveKeyEvent(QKeyEvent *e); + bool camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode); + bool camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode); + + void processTooltipRequest(const QTextCursor &c); + + void transformSelection(Internal::TransformationMethod method); + void transformBlockSelection(Internal::TransformationMethod method); + + bool inFindScope(const QTextCursor &cursor); + bool inFindScope(int selectionStart, int selectionEnd); + + void slotUpdateExtraAreaWidth(); + void slotUpdateRequest(const QRect &r, int dy); + void slotUpdateBlockNotify(const QTextBlock &); + void updateTabStops(); + void applyFontSettingsDelayed(); + + void editorContentsChange(int position, int charsRemoved, int charsAdded); + void documentAboutToBeReloaded(); + void documentReloadFinished(bool success); + void highlightSearchResultsSlot(const QString &txt, FindFlags findFlags); + void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int); + + void updateCursorPosition(); + + // parentheses matcher + void _q_matchParentheses(); + void _q_highlightBlocks(); + void slotSelectionChanged(); + void _q_animateUpdate(int position, QPointF lastPos, QRectF rect); + void updateCodeFoldingVisible(); + + void reconfigure(); + +public: + BaseTextEditorWidget *q; + QToolBar *m_toolBar; + QWidget *m_stretchWidget; + LineColumnLabel *m_cursorPositionLabel; + LineColumnLabel *m_fileEncodingLabel; + QAction *m_cursorPositionLabelAction; + QAction *m_fileEncodingLabelAction; + + bool m_contentsChanged; + bool m_lastCursorChangeWasInteresting; + + QSharedPointer<TextDocument> m_document; + QByteArray m_tempState; + QByteArray m_tempNavigationState; + + bool m_parenthesesMatchingEnabled; + + // parentheses matcher + bool m_formatRange; + QTextCharFormat m_mismatchFormat; + QTimer m_parenthesesMatchingTimer; + // end parentheses matcher + + QWidget *m_extraArea; + + Id m_tabSettingsId; + ICodeStylePreferences *m_codeStylePreferences; + DisplaySettings m_displaySettings; + MarginSettings m_marginSettings; + bool m_fontSettingsNeedsApply; + BehaviorSettings m_behaviorSettings; + + int extraAreaSelectionAnchorBlockNumber; + int extraAreaToggleMarkBlockNumber; + int extraAreaHighlightFoldedBlockNumber; + + TextEditorOverlay *m_overlay; + TextEditorOverlay *m_snippetOverlay; + TextEditorOverlay *m_searchResultOverlay; + bool snippetCheckCursor(const QTextCursor &cursor); + void snippetTabOrBacktab(bool forward); + + RefactorOverlay *m_refactorOverlay; + + QBasicTimer foldedBlockTimer; + int visibleFoldedBlockNumber; + int suggestedVisibleFoldedBlockNumber; + void clearVisibleFoldedBlock(); + bool m_mouseOnFoldedMarker; + void foldLicenseHeader(); + + QBasicTimer autoScrollTimer; + uint m_marksVisible : 1; + uint m_codeFoldingVisible : 1; + uint m_codeFoldingSupported : 1; + uint m_revisionsVisible : 1; + uint m_lineNumbersVisible : 1; + uint m_highlightCurrentLine : 1; + uint m_requestMarkEnabled : 1; + uint m_lineSeparatorsAllowed : 1; + uint autoParenthesisOverwriteBackup : 1; + uint surroundWithEnabledOverwriteBackup : 1; + uint m_maybeFakeTooltipEvent : 1; + int m_visibleWrapColumn; + + BaseTextEditorWidget::Link m_currentLink; + bool m_linkPressed; + + QRegExp m_searchExpr; + FindFlags m_findFlags; + void highlightSearchResults(const QTextBlock &block, TextEditorOverlay *overlay); + QTimer m_delayedUpdateTimer; + + QList<QTextEdit::ExtraSelection> m_extraSelections[BaseTextEditorWidget::NExtraSelectionKinds]; + + // block selection mode + bool m_inBlockSelectionMode; + QString copyBlockSelection(); + void insertIntoBlockSelection(const QString &text = QString()); + void setCursorToColumn(QTextCursor &cursor, int column, + QTextCursor::MoveMode moveMode = QTextCursor::MoveAnchor); + void removeBlockSelection(); + void enableBlockSelection(const QTextCursor &cursor); + void enableBlockSelection(int positionBlock, int positionColumn, + int anchorBlock, int anchorColumn); + void disableBlockSelection(bool keepSelection = true); + void resetCursorFlashTimer(); + QBasicTimer m_cursorFlashTimer; + bool m_cursorVisible; + bool m_moveLineUndoHack; + + QTextCursor m_findScopeStart; + QTextCursor m_findScopeEnd; + int m_findScopeVerticalBlockSelectionFirstColumn; + int m_findScopeVerticalBlockSelectionLastColumn; + + QTextCursor m_selectBlockAnchor; + + Internal::BaseTextBlockSelection m_blockSelection; + + void moveCursorVisible(bool ensureVisible = true); + + int visualIndent(const QTextBlock &block) const; + BaseTextEditorPrivateHighlightBlocks m_highlightBlocksInfo; + QTimer m_highlightBlocksTimer; + + CodeAssistant m_codeAssistant; + bool m_assistRelevantContentAdded; + + QPointer<BaseTextEditorAnimator> m_animator; + int m_cursorBlockNumber; + int m_blockCount; + + QPoint m_markDragStart; + bool m_markDragging; + + QScopedPointer<Internal::ClipboardAssistProvider> m_clipboardAssistProvider; + + bool m_isMissingSyntaxDefinition; + + QScopedPointer<AutoCompleter> m_autoCompleter; + CommentDefinition m_commentDefinition; + CompletionAssistProvider *m_completionAssistProvider; +}; + +BaseTextEditorWidgetPrivate::BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent) + : q(parent), + m_toolBar(0), + m_stretchWidget(0), + m_cursorPositionLabel(0), + m_fileEncodingLabel(0), + m_cursorPositionLabelAction(0), + m_fileEncodingLabelAction(0), + m_contentsChanged(false), + m_lastCursorChangeWasInteresting(false), + m_parenthesesMatchingEnabled(false), + m_formatRange(false), + m_parenthesesMatchingTimer(0), + m_extraArea(0), + m_codeStylePreferences(0), + m_fontSettingsNeedsApply(true), // apply when making visible the first time, for the split case + extraAreaSelectionAnchorBlockNumber(-1), + extraAreaToggleMarkBlockNumber(-1), + extraAreaHighlightFoldedBlockNumber(-1), + m_overlay(0), + m_snippetOverlay(0), + m_searchResultOverlay(0), + m_refactorOverlay(0), + visibleFoldedBlockNumber(-1), + suggestedVisibleFoldedBlockNumber(-1), + m_mouseOnFoldedMarker(false), + m_marksVisible(false), + m_codeFoldingVisible(false), + m_codeFoldingSupported(false), + m_revisionsVisible(false), + m_lineNumbersVisible(true), + m_highlightCurrentLine(true), + m_requestMarkEnabled(true), + m_lineSeparatorsAllowed(false), + m_maybeFakeTooltipEvent(false), + m_visibleWrapColumn(0), + m_linkPressed(false), + m_delayedUpdateTimer(0), + m_inBlockSelectionMode(false), + m_moveLineUndoHack(false), + m_findScopeVerticalBlockSelectionFirstColumn(-1), + m_findScopeVerticalBlockSelectionLastColumn(-1), + m_highlightBlocksTimer(0), + m_assistRelevantContentAdded(false), + m_cursorBlockNumber(-1), + m_blockCount(0), + m_markDragging(false), + m_clipboardAssistProvider(new Internal::ClipboardAssistProvider), + m_isMissingSyntaxDefinition(false), + m_autoCompleter(new AutoCompleter), + m_completionAssistProvider(0) +{ + Aggregation::Aggregate *aggregate = new Aggregation::Aggregate; + BaseTextFind *baseTextFind = new BaseTextFind(q); + connect(baseTextFind, &BaseTextFind::highlightAllRequested, + this, &BaseTextEditorWidgetPrivate::highlightSearchResultsSlot); + connect(baseTextFind, &BaseTextFind::findScopeChanged, + this, &BaseTextEditorWidgetPrivate::setFindScope); + aggregate->add(baseTextFind); + aggregate->add(q); + + m_extraArea = new TextEditExtraArea(q); + m_extraArea->setMouseTracking(true); + + m_stretchWidget = new QWidget; + m_stretchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + m_toolBar = new QToolBar; + m_toolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + m_toolBar->addWidget(m_stretchWidget); + + m_cursorPositionLabel = new LineColumnLabel; + const int spacing = q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2; + m_cursorPositionLabel->setContentsMargins(spacing, 0, spacing, 0); + + m_fileEncodingLabel = new LineColumnLabel; + m_fileEncodingLabel->setContentsMargins(spacing, 0, spacing, 0); + + m_cursorPositionLabelAction = m_toolBar->addWidget(m_cursorPositionLabel); + m_fileEncodingLabelAction = m_toolBar->addWidget(m_fileEncodingLabel); +} + +} // namespace Internal + +using namespace Internal; + +/*! + * Test if syntax highlighter is available (or unneeded) for \a widget. + * If not found, show a warning with a link to the relevant settings page. + */ +static void updateEditorInfoBar(BaseTextEditorWidget *widget) +{ + Id infoSyntaxDefinition(Constants::INFO_SYNTAX_DEFINITION); + InfoBar *infoBar = widget->textDocument()->infoBar(); + if (!widget->isMissingSyntaxDefinition()) { + infoBar->removeInfo(infoSyntaxDefinition); + } else if (infoBar->canInfoBeAdded(infoSyntaxDefinition)) { + InfoBarEntry info(infoSyntaxDefinition, + BaseTextEditor::tr("A highlight definition was not found for this file. " + "Would you like to try to find one?"), + InfoBarEntry::GlobalSuppressionEnabled); + info.setCustomButtonInfo(BaseTextEditor::tr("Show Highlighter Options..."), [widget]() { + ICore::showOptionsDialog(Constants::TEXT_EDITOR_SETTINGS_CATEGORY, + Constants::TEXT_EDITOR_HIGHLIGHTER_SETTINGS, + widget); + }); + + infoBar->addInfo(info); + } +} + +QString BaseTextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const +{ + // Copy the selected text as plain text + QString text = cursor.selectedText(); + return convertToPlainText(text); +} + +QString BaseTextEditorWidget::convertToPlainText(const QString &txt) +{ + QString ret = txt; + QChar *uc = ret.data(); + QChar *e = uc + ret.size(); + + for (; uc != e; ++uc) { + switch (uc->unicode()) { + case 0xfdd0: // QTextBeginningOfFrame + case 0xfdd1: // QTextEndOfFrame + case QChar::ParagraphSeparator: + case QChar::LineSeparator: + *uc = QLatin1Char('\n'); + break; + case QChar::Nbsp: + *uc = QLatin1Char(' '); + break; + default: + ; + } + } + return ret; +} + +static const char kTextBlockMimeType[] = "application/vnd.qtcreator.blocktext"; + +BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent) + : QPlainTextEdit(parent) +{ + // "Needed", as the creation below triggers ChildEvents that are + // passed to this object's event() which uses 'd'. + d = 0; + d = new BaseTextEditorWidgetPrivate(this); +} + +void BaseTextEditorWidget::setTextDocument(const QSharedPointer<TextDocument> &doc) +{ + d->ctor(doc); +} + +void BaseTextEditorWidgetPrivate::ctor(const QSharedPointer<TextDocument> &doc) +{ + q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + m_overlay = new TextEditorOverlay(q); + m_snippetOverlay = new TextEditorOverlay(q); + m_searchResultOverlay = new TextEditorOverlay(q); + m_refactorOverlay = new RefactorOverlay(q); + + m_document = doc; + setupDocumentSignals(); + + // from RESEARCH + + q->setLayoutDirection(Qt::LeftToRight); + q->viewport()->setMouseTracking(true); + + extraAreaSelectionAnchorBlockNumber = -1; + extraAreaToggleMarkBlockNumber = -1; + extraAreaHighlightFoldedBlockNumber = -1; + visibleFoldedBlockNumber = -1; + suggestedVisibleFoldedBlockNumber = -1; + + QObject::connect(&m_codeAssistant, &CodeAssistant::finished, + q, &BaseTextEditorWidget::assistFinished); + + QObject::connect(q, &QPlainTextEdit::blockCountChanged, + this, &BaseTextEditorWidgetPrivate::slotUpdateExtraAreaWidth); + + QObject::connect(q, &QPlainTextEdit::modificationChanged, m_extraArea, + static_cast<void (QWidget::*)()>(&QWidget::update)); + + QObject::connect(q, &QPlainTextEdit::cursorPositionChanged, + q, &BaseTextEditorWidget::slotCursorPositionChanged); + + QObject::connect(q, &QPlainTextEdit::cursorPositionChanged, + this, &BaseTextEditorWidgetPrivate::updateCursorPosition); + + QObject::connect(q, &QPlainTextEdit::updateRequest, + this, &BaseTextEditorWidgetPrivate::slotUpdateRequest); + + QObject::connect(q, &QPlainTextEdit::selectionChanged, + this, &BaseTextEditorWidgetPrivate::slotSelectionChanged); + +// (void) new QShortcut(tr("CTRL+L"), this, SLOT(centerCursor()), 0, Qt::WidgetShortcut); +// (void) new QShortcut(tr("F9"), this, SLOT(slotToggleMark()), 0, Qt::WidgetShortcut); +// (void) new QShortcut(tr("F11"), this, SLOT(slotToggleBlockVisible())); + +#ifdef DO_FOO + (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo())); +#endif + + // parentheses matcher + m_formatRange = true; + m_mismatchFormat.setBackground(q->palette().color(QPalette::Base).value() < 128 + ? Qt::darkMagenta : Qt::magenta); + m_parenthesesMatchingTimer.setSingleShot(true); + QObject::connect(&m_parenthesesMatchingTimer, &QTimer::timeout, + this, &BaseTextEditorWidgetPrivate::_q_matchParentheses); + + m_highlightBlocksTimer.setSingleShot(true); + QObject::connect(&m_highlightBlocksTimer, &QTimer::timeout, + this, &BaseTextEditorWidgetPrivate::_q_highlightBlocks); + + m_animator = 0; + + slotUpdateExtraAreaWidth(); + updateHighlights(); + q->setFrameStyle(QFrame::NoFrame); + + m_delayedUpdateTimer.setSingleShot(true); + QObject::connect(&m_delayedUpdateTimer, &QTimer::timeout, q->viewport(), + static_cast<void (QWidget::*)()>(&QWidget::update)); + + m_moveLineUndoHack = false; +} + +BaseTextEditorWidget::~BaseTextEditorWidget() +{ + delete d; + d = 0; +} + +void BaseTextEditorWidget::print(QPrinter *printer) +{ + const bool oldFullPage = printer->fullPage(); + printer->setFullPage(true); + QPrintDialog *dlg = new QPrintDialog(printer, this); + dlg->setWindowTitle(tr("Print Document")); + if (dlg->exec() == QDialog::Accepted) + d->print(printer); + printer->setFullPage(oldFullPage); + delete dlg; +} + +static int foldBoxWidth(const QFontMetrics &fm) +{ + const int lineSpacing = fm.lineSpacing(); + return lineSpacing + lineSpacing % 2 + 1; +} + +static void printPage(int index, QPainter *painter, const QTextDocument *doc, + const QRectF &body, const QRectF &titleBox, + const QString &title) +{ + painter->save(); + + painter->translate(body.left(), body.top() - (index - 1) * body.height()); + QRectF view(0, (index - 1) * body.height(), body.width(), body.height()); + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QAbstractTextDocumentLayout::PaintContext ctx; + + painter->setFont(QFont(doc->defaultFont())); + QRectF box = titleBox.translated(0, view.top()); + int dpix = painter->device()->logicalDpiX(); + int dpiy = painter->device()->logicalDpiY(); + int mx = 5 * dpix / 72.0; + int my = 2 * dpiy / 72.0; + painter->fillRect(box.adjusted(-mx, -my, mx, my), QColor(210, 210, 210)); + if (!title.isEmpty()) + painter->drawText(box, Qt::AlignCenter, title); + const QString pageString = QString::number(index); + painter->drawText(box, Qt::AlignRight, pageString); + + painter->setClipRect(view); + ctx.clip = view; + // don't use the system palette text as default text color, on HP/UX + // for example that's white, and white text on white paper doesn't + // look that nice + ctx.palette.setColor(QPalette::Text, Qt::black); + + layout->draw(painter, ctx); + + painter->restore(); +} + +void BaseTextEditorWidgetPrivate::print(QPrinter *printer) +{ + QTextDocument *doc = q->document(); + + QString title = m_document->displayName(); + if (!title.isEmpty()) + printer->setDocName(title); + + + QPainter p(printer); + + // Check that there is a valid device to print to. + if (!p.isActive()) + return; + + doc = doc->clone(doc); + + QTextOption opt = doc->defaultTextOption(); + opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + doc->setDefaultTextOption(opt); + + (void)doc->documentLayout(); // make sure that there is a layout + + + QColor background = q->palette().color(QPalette::Base); + bool backgroundIsDark = background.value() < 128; + + for (QTextBlock srcBlock = q->document()->firstBlock(), dstBlock = doc->firstBlock(); + srcBlock.isValid() && dstBlock.isValid(); + srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) { + + + QList<QTextLayout::FormatRange> formatList = srcBlock.layout()->additionalFormats(); + if (backgroundIsDark) { + // adjust syntax highlighting colors for better contrast + for (int i = formatList.count() - 1; i >= 0; --i) { + QTextCharFormat &format = formatList[i].format; + if (format.background().color() == background) { + QBrush brush = format.foreground(); + QColor color = brush.color(); + int h,s,v,a; + color.getHsv(&h, &s, &v, &a); + color.setHsv(h, s, qMin(128, v), a); + brush.setColor(color); + format.setForeground(brush); + } + format.setBackground(Qt::white); + } + } + + dstBlock.layout()->setAdditionalFormats(formatList); + } + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + layout->setPaintDevice(p.device()); + + int dpiy = p.device()->logicalDpiY(); + int margin = (int) ((2/2.54)*dpiy); // 2 cm margins + + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setMargin(margin); + doc->rootFrame()->setFrameFormat(fmt); + + QRectF pageRect(printer->pageRect()); + QRectF body = QRectF(0, 0, pageRect.width(), pageRect.height()); + QFontMetrics fontMetrics(doc->defaultFont(), p.device()); + + QRectF titleBox(margin, + body.top() + margin + - fontMetrics.height() + - 6 * dpiy / 72.0, + body.width() - 2*margin, + fontMetrics.height()); + doc->setPageSize(body.size()); + + int docCopies; + int pageCopies; + if (printer->collateCopies() == true) { + docCopies = 1; + pageCopies = printer->numCopies(); + } else { + docCopies = printer->numCopies(); + pageCopies = 1; + } + + int fromPage = printer->fromPage(); + int toPage = printer->toPage(); + bool ascending = true; + + if (fromPage == 0 && toPage == 0) { + fromPage = 1; + toPage = doc->pageCount(); + } + // paranoia check + fromPage = qMax(1, fromPage); + toPage = qMin(doc->pageCount(), toPage); + + if (printer->pageOrder() == QPrinter::LastPageFirst) { + int tmp = fromPage; + fromPage = toPage; + toPage = tmp; + ascending = false; + } + + for (int i = 0; i < docCopies; ++i) { + + int page = fromPage; + while (true) { + for (int j = 0; j < pageCopies; ++j) { + if (printer->printerState() == QPrinter::Aborted + || printer->printerState() == QPrinter::Error) + goto UserCanceled; + printPage(page, &p, doc, body, titleBox, title); + if (j < pageCopies - 1) + printer->newPage(); + } + + if (page == toPage) + break; + + if (ascending) + ++page; + else + --page; + + printer->newPage(); + } + + if ( i < docCopies - 1) + printer->newPage(); + } + +UserCanceled: + delete doc; +} + + +int BaseTextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const +{ + if (!block.isValid()) + return 0; + const QTextDocument *document = block.document(); + int i = 0; + while (i < block.length()) { + if (!document->characterAt(block.position() + i).isSpace()) { + QTextCursor cursor(block); + cursor.setPosition(block.position() + i); + return q->cursorRect(cursor).x(); + } + ++i; + } + + return 0; +} + +void BaseTextEditorWidget::selectEncoding() +{ + TextDocument *doc = d->m_document.data(); + CodecSelector codecSelector(this, doc); + + switch (codecSelector.exec()) { + case CodecSelector::Reload: { + QString errorString; + if (!doc->reload(&errorString, codecSelector.selectedCodec())) { + QMessageBox::critical(this, tr("File Error"), errorString); + break; + } + break; } + case CodecSelector::Save: + doc->setCodec(codecSelector.selectedCodec()); + EditorManager::saveDocument(textDocument()); + updateTextCodecLabel(); + break; + case CodecSelector::Cancel: + break; + } +} + +void BaseTextEditorWidget::updateTextCodecLabel() +{ + QString text = QString::fromLatin1(d->m_document->codec()->name()); + d->m_fileEncodingLabel->setText(text, text); +} + +QString BaseTextEditorWidget::msgTextTooLarge(quint64 size) +{ + return tr("The text is too large to be displayed (%1 MB)."). + arg(size >> 20); +} + +void BaseTextEditorWidget::insertPlainText(const QString &text) +{ + if (d->m_inBlockSelectionMode) + d->insertIntoBlockSelection(text); + else + QPlainTextEdit::insertPlainText(text); +} + +QString BaseTextEditorWidget::selectedText() const +{ + if (d->m_inBlockSelectionMode) + return d->copyBlockSelection(); + else + return textCursor().selectedText(); +} + +void BaseTextEditorWidgetPrivate::updateCannotDecodeInfo() +{ + q->setReadOnly(m_document->hasDecodingError()); + InfoBar *infoBar = m_document->infoBar(); + Id selectEncodingId(Constants::SELECT_ENCODING); + if (m_document->hasDecodingError()) { + if (!infoBar->canInfoBeAdded(selectEncodingId)) + return; + InfoBarEntry info(selectEncodingId, + BaseTextEditorWidget::tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.") + .arg(m_document->displayName()).arg(QString::fromLatin1(m_document->codec()->name()))); + info.setCustomButtonInfo(BaseTextEditorWidget::tr("Select Encoding"), [this]() { q->selectEncoding(); }); + infoBar->addInfo(info); + } else { + infoBar->removeInfo(selectEncodingId); + } +} + +bool BaseTextEditorWidget::open(QString *errorString, const QString &fileName, const QString &realFileName) +{ + if (d->m_document->open(errorString, fileName, realFileName)) { + moveCursor(QTextCursor::Start); + d->updateCannotDecodeInfo(); + if (d->m_fileEncodingLabel) { + connect(d->m_fileEncodingLabel, &LineColumnLabel::clicked, + this, &BaseTextEditorWidget::selectEncoding, Qt::UniqueConnection); + connect(d->m_document->document(), &QTextDocument::modificationChanged, + this, &BaseTextEditorWidget::updateTextCodecLabel, Qt::UniqueConnection); + updateTextCodecLabel(); + } + return true; + } + return false; +} + +/* + Collapses the first comment in a file, if there is only whitespace above + */ +void BaseTextEditorWidgetPrivate::foldLicenseHeader() +{ + QTextDocument *doc = q->document(); + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return); + QTextBlock block = doc->firstBlock(); + while (block.isValid() && block.isVisible()) { + QString text = block.text(); + if (TextDocumentLayout::canFold(block) && block.next().isVisible()) { + if (text.trimmed().startsWith(QLatin1String("/*"))) { + TextDocumentLayout::doFoldOrUnfold(block, false); + moveCursorVisible(); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); + break; + } + } + if (TabSettings::firstNonSpace(text) < text.size()) + break; + block = block.next(); + } +} + +TextDocument *BaseTextEditorWidget::textDocument() const +{ + return d->m_document.data(); +} + +BaseTextDocumentPtr BaseTextEditorWidget::textDocumentPtr() const +{ + return d->m_document; +} + +void BaseTextEditorWidgetPrivate::editorContentsChange(int position, int charsRemoved, int charsAdded) +{ + if (m_animator) + m_animator->finish(); + + m_contentsChanged = true; + QTextDocument *doc = q->document(); + TextDocumentLayout *documentLayout = static_cast<TextDocumentLayout*>(doc->documentLayout()); + const QTextBlock posBlock = doc->findBlock(position); + + // Keep the line numbers and the block information for the text marks updated + if (charsRemoved != 0) { + documentLayout->updateMarksLineNumber(); + documentLayout->updateMarksBlock(posBlock); + } else { + const QTextBlock nextBlock = doc->findBlock(position + charsAdded); + if (posBlock != nextBlock) { + documentLayout->updateMarksLineNumber(); + documentLayout->updateMarksBlock(posBlock); + documentLayout->updateMarksBlock(nextBlock); + } else { + documentLayout->updateMarksBlock(posBlock); + } + } + + if (m_snippetOverlay->isVisible()) { + QTextCursor cursor = q->textCursor(); + cursor.setPosition(position); + snippetCheckCursor(cursor); + } + + if (charsAdded != 0 && q->document()->characterAt(position + charsAdded - 1).isPrint()) + m_assistRelevantContentAdded = true; + + int newBlockCount = doc->blockCount(); + if (!q->hasFocus() && newBlockCount != m_blockCount) { + // lines were inserted or removed from outside, keep viewport on same part of text + if (q->firstVisibleBlock().blockNumber() > posBlock.blockNumber()) + q->verticalScrollBar()->setValue(q->verticalScrollBar()->value() + newBlockCount - m_blockCount); + } + m_blockCount = newBlockCount; +} + +void BaseTextEditorWidgetPrivate::slotSelectionChanged() +{ + if (!q->textCursor().hasSelection() && !m_selectBlockAnchor.isNull()) + m_selectBlockAnchor = QTextCursor(); + // Clear any link which might be showing when the selection changes + clearLink(); +} + +void BaseTextEditorWidget::gotoBlockStart() +{ + QTextCursor cursor = textCursor(); + if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) { + setTextCursor(cursor); + d->_q_matchParentheses(); + } +} + +void BaseTextEditorWidget::gotoBlockEnd() +{ + QTextCursor cursor = textCursor(); + if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) { + setTextCursor(cursor); + d->_q_matchParentheses(); + } +} + +void BaseTextEditorWidget::gotoBlockStartWithSelection() +{ + QTextCursor cursor = textCursor(); + if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) { + setTextCursor(cursor); + d->_q_matchParentheses(); + } +} + +void BaseTextEditorWidget::gotoBlockEndWithSelection() +{ + QTextCursor cursor = textCursor(); + if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) { + setTextCursor(cursor); + d->_q_matchParentheses(); + } +} + +void BaseTextEditorWidget::gotoLineStart() +{ + d->handleHomeKey(false); +} + +void BaseTextEditorWidget::gotoLineStartWithSelection() +{ + d->handleHomeKey(true); +} + +void BaseTextEditorWidget::gotoLineEnd() +{ + moveCursor(QTextCursor::EndOfLine); +} + +void BaseTextEditorWidget::gotoLineEndWithSelection() +{ + moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); +} + +void BaseTextEditorWidget::gotoNextLine() +{ + moveCursor(QTextCursor::Down); +} + +void BaseTextEditorWidget::gotoNextLineWithSelection() +{ + moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor); +} + +void BaseTextEditorWidget::gotoPreviousLine() +{ + moveCursor(QTextCursor::Up); +} + +void BaseTextEditorWidget::gotoPreviousLineWithSelection() +{ + moveCursor(QTextCursor::Up, QTextCursor::KeepAnchor); +} + +void BaseTextEditorWidget::gotoPreviousCharacter() +{ + moveCursor(QTextCursor::PreviousCharacter); +} + +void BaseTextEditorWidget::gotoPreviousCharacterWithSelection() +{ + moveCursor(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); +} + +void BaseTextEditorWidget::gotoNextCharacter() +{ + moveCursor(QTextCursor::NextCharacter); +} + +void BaseTextEditorWidget::gotoNextCharacterWithSelection() +{ + moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); +} + +void BaseTextEditorWidget::gotoPreviousWord() +{ + moveCursor(QTextCursor::PreviousWord); + setTextCursor(textCursor()); +} + +void BaseTextEditorWidget::gotoPreviousWordWithSelection() +{ + moveCursor(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + setTextCursor(textCursor()); +} + +void BaseTextEditorWidget::gotoNextWord() +{ + moveCursor(QTextCursor::NextWord); + setTextCursor(textCursor()); +} + +void BaseTextEditorWidget::gotoNextWordWithSelection() +{ + moveCursor(QTextCursor::NextWord, QTextCursor::KeepAnchor); + setTextCursor(textCursor()); +} + +void BaseTextEditorWidget::gotoPreviousWordCamelCase() +{ + QTextCursor c = textCursor(); + d->camelCaseLeft(c, QTextCursor::MoveAnchor); + setTextCursor(c); +} + +void BaseTextEditorWidget::gotoPreviousWordCamelCaseWithSelection() +{ + QTextCursor c = textCursor(); + d->camelCaseLeft(c, QTextCursor::KeepAnchor); + setTextCursor(c); +} + +void BaseTextEditorWidget::gotoNextWordCamelCase() +{ + QTextCursor c = textCursor(); + d->camelCaseRight(c, QTextCursor::MoveAnchor); + setTextCursor(c); +} + +void BaseTextEditorWidget::gotoNextWordCamelCaseWithSelection() +{ + QTextCursor c = textCursor(); + d->camelCaseRight(c, QTextCursor::KeepAnchor); + setTextCursor(c); +} + +static QTextCursor flippedCursor(const QTextCursor &cursor) +{ + QTextCursor flipped = cursor; + flipped.clearSelection(); + flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor); + return flipped; +} + +bool BaseTextEditorWidget::selectBlockUp() +{ + QTextCursor cursor = textCursor(); + if (!cursor.hasSelection()) + d->m_selectBlockAnchor = cursor; + else + cursor.setPosition(cursor.selectionStart()); + + if (!TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) + return false; + if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true)) + return false; + + setTextCursor(flippedCursor(cursor)); + d->_q_matchParentheses(); + return true; +} + +bool BaseTextEditorWidget::selectBlockDown() +{ + QTextCursor tc = textCursor(); + QTextCursor cursor = d->m_selectBlockAnchor; + + if (!tc.hasSelection() || cursor.isNull()) + return false; + tc.setPosition(tc.selectionStart()); + + forever { + QTextCursor ahead = cursor; + if (!TextBlockUserData::findPreviousOpenParenthesis(&ahead, false)) + break; + if (ahead.position() <= tc.position()) + break; + cursor = ahead; + } + if ( cursor != d->m_selectBlockAnchor) + TextBlockUserData::findNextClosingParenthesis(&cursor, true); + + setTextCursor(flippedCursor(cursor)); + d->_q_matchParentheses(); + return true; +} + +void BaseTextEditorWidget::copyLineUp() +{ + d->copyLineUpDown(true); +} + +void BaseTextEditorWidget::copyLineDown() +{ + d->copyLineUpDown(false); +} + +// @todo: Potential reuse of some code around the following functions... +void BaseTextEditorWidgetPrivate::copyLineUpDown(bool up) +{ + QTextCursor cursor = q->textCursor(); + QTextCursor move = cursor; + move.beginEditBlock(); + + bool hasSelection = cursor.hasSelection(); + + if (hasSelection) { + move.setPosition(cursor.selectionStart()); + move.movePosition(QTextCursor::StartOfBlock); + move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor); + move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock, + QTextCursor::KeepAnchor); + } else { + move.movePosition(QTextCursor::StartOfBlock); + move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + } + + QString text = move.selectedText(); + + if (up) { + move.setPosition(cursor.selectionStart()); + move.movePosition(QTextCursor::StartOfBlock); + move.insertBlock(); + move.movePosition(QTextCursor::Left); + } else { + move.movePosition(QTextCursor::EndOfBlock); + if (move.atBlockStart()) { + move.movePosition(QTextCursor::NextBlock); + move.insertBlock(); + move.movePosition(QTextCursor::Left); + } else { + move.insertBlock(); + } + } + + int start = move.position(); + move.clearSelection(); + move.insertText(text); + int end = move.position(); + + move.setPosition(start); + move.setPosition(end, QTextCursor::KeepAnchor); + + m_document->autoIndent(move); + move.endEditBlock(); + + q->setTextCursor(move); +} + +void BaseTextEditorWidget::joinLines() +{ + QTextCursor cursor = textCursor(); + QTextCursor start = cursor; + QTextCursor end = cursor; + + start.setPosition(cursor.selectionStart()); + end.setPosition(cursor.selectionEnd() - 1); + + int lineCount = qMax(1, end.blockNumber() - start.blockNumber()); + + cursor.beginEditBlock(); + cursor.setPosition(cursor.selectionStart()); + while (lineCount--) { + cursor.movePosition(QTextCursor::NextBlock); + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + QString cutLine = cursor.selectedText(); + + // Collapse leading whitespaces to one or insert whitespace + cutLine.replace(QRegExp(QLatin1String("^\\s*")), QLatin1String(" ")); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + + cursor.movePosition(QTextCursor::PreviousBlock); + cursor.movePosition(QTextCursor::EndOfBlock); + + cursor.insertText(cutLine); + } + cursor.endEditBlock(); + + setTextCursor(cursor); +} + +void BaseTextEditorWidget::insertLineAbove() +{ + QTextCursor cursor = textCursor(); + cursor.beginEditBlock(); + // If the cursor is at the beginning of the document, + // it should still insert a line above the current line. + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor); + cursor.insertBlock(); + cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor); + d->m_document->autoIndent(cursor); + cursor.endEditBlock(); + setTextCursor(cursor); +} + +void BaseTextEditorWidget::insertLineBelow() +{ + if (d->m_inBlockSelectionMode) + d->disableBlockSelection(false); + QTextCursor cursor = textCursor(); + cursor.beginEditBlock(); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor); + cursor.insertBlock(); + d->m_document->autoIndent(cursor); + cursor.endEditBlock(); + setTextCursor(cursor); +} + +void BaseTextEditorWidget::moveLineUp() +{ + d->moveLineUpDown(true); +} + +void BaseTextEditorWidget::moveLineDown() +{ + d->moveLineUpDown(false); +} + +void BaseTextEditorWidget::uppercaseSelection() +{ + d->transformSelection(&QString_toUpper); +} + +void BaseTextEditorWidget::lowercaseSelection() +{ + d->transformSelection(&QString_toLower); +} + +void BaseTextEditorWidget::indent() +{ + setTextCursor(textDocument()->indent(textCursor())); +} + +void BaseTextEditorWidget::unindent() +{ + setTextCursor(textDocument()->unindent(textCursor())); +} + +void BaseTextEditorWidget::undo() +{ + if (d->m_inBlockSelectionMode) + d->disableBlockSelection(false); + QPlainTextEdit::undo(); +} + +void BaseTextEditorWidget::redo() +{ + if (d->m_inBlockSelectionMode) + d->disableBlockSelection(false); + QPlainTextEdit::redo(); +} + +void BaseTextEditorWidget::openLinkUnderCursor() +{ + const bool openInNextSplit = alwaysOpenLinksInNextSplit(); + Link symbolLink = findLinkAt(textCursor(), true, openInNextSplit); + openLink(symbolLink, openInNextSplit); +} + +void BaseTextEditorWidget::openLinkUnderCursorInNextSplit() +{ + const bool openInNextSplit = !alwaysOpenLinksInNextSplit(); + Link symbolLink = findLinkAt(textCursor(), true, openInNextSplit); + openLink(symbolLink, openInNextSplit); +} + +void BaseTextEditorWidget::abortAssist() +{ + d->m_codeAssistant.destroyContext(); +} + +void BaseTextEditorWidgetPrivate::moveLineUpDown(bool up) +{ + QTextCursor cursor = q->textCursor(); + QTextCursor move = cursor; + + move.setVisualNavigation(false); // this opens folded items instead of destroying them + + if (m_moveLineUndoHack) + move.joinPreviousEditBlock(); + else + move.beginEditBlock(); + + bool hasSelection = cursor.hasSelection(); + + if (hasSelection) { + if (m_inBlockSelectionMode) + disableBlockSelection(true); + move.setPosition(cursor.selectionStart()); + move.movePosition(QTextCursor::StartOfBlock); + move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor); + move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock, + QTextCursor::KeepAnchor); + } else { + move.movePosition(QTextCursor::StartOfBlock); + move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + } + QString text = move.selectedText(); + + RefactorMarkers affectedMarkers; + RefactorMarkers nonAffectedMarkers; + QList<int> markerOffsets; + + foreach (const RefactorMarker &marker, m_refactorOverlay->markers()) { + //test if marker is part of the selection to be moved + if ((move.selectionStart() <= marker.cursor.position()) + && (move.selectionEnd() >= marker.cursor.position())) { + affectedMarkers.append(marker); + //remember the offset of markers in text + int offset = marker.cursor.position() - move.selectionStart(); + markerOffsets.append(offset); + } else { + nonAffectedMarkers.append(marker); + } + } + + move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + move.removeSelectedText(); + + if (up) { + move.movePosition(QTextCursor::PreviousBlock); + move.insertBlock(); + move.movePosition(QTextCursor::Left); + } else { + move.movePosition(QTextCursor::EndOfBlock); + if (move.atBlockStart()) { // empty block + move.movePosition(QTextCursor::NextBlock); + move.insertBlock(); + move.movePosition(QTextCursor::Left); + } else { + move.insertBlock(); + } + } + + int start = move.position(); + move.clearSelection(); + move.insertText(text); + int end = move.position(); + + if (hasSelection) { + move.setPosition(end); + move.setPosition(start, QTextCursor::KeepAnchor); + } else { + move.setPosition(start); + } + + //update positions of affectedMarkers + for (int i=0;i < affectedMarkers.count(); i++) { + int newPosition = start + markerOffsets.at(i); + affectedMarkers[i].cursor.setPosition(newPosition); + } + m_refactorOverlay->setMarkers(nonAffectedMarkers + affectedMarkers); + + bool shouldReindent = true; + if (m_commentDefinition.isValid()) { + QString trimmedText(text.trimmed()); + + if (m_commentDefinition.hasSingleLineStyle()) { + if (trimmedText.startsWith(m_commentDefinition.singleLine)) + shouldReindent = false; + } + if (shouldReindent && m_commentDefinition.hasMultiLineStyle()) { + // Don't have any single line comments; try multi line. + if (trimmedText.startsWith(m_commentDefinition.multiLineStart) + && trimmedText.endsWith(m_commentDefinition.multiLineEnd)) { + shouldReindent = false; + } + } + } + + if (shouldReindent) { + // The text was not commented at all; re-indent. + m_document->autoReindent(move); + } + move.endEditBlock(); + + q->setTextCursor(move); + m_moveLineUndoHack = true; +} + +void BaseTextEditorWidget::cleanWhitespace() +{ + d->m_document->cleanWhitespace(textCursor()); +} + + +// could go into QTextCursor... +static QTextLine currentTextLine(const QTextCursor &cursor) +{ + const QTextBlock block = cursor.block(); + if (!block.isValid()) + return QTextLine(); + + const QTextLayout *layout = block.layout(); + if (!layout) + return QTextLine(); + + const int relativePos = cursor.position() - block.position(); + return layout->lineForTextPosition(relativePos); +} + +bool BaseTextEditorWidgetPrivate::camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode) +{ + int state = 0; + enum Input { + Input_U, + Input_l, + Input_underscore, + Input_space, + Input_other + }; + + if (!cursor.movePosition(QTextCursor::Left, mode)) + return false; + + forever { + QChar c = q->document()->characterAt(cursor.position()); + Input input = Input_other; + if (c.isUpper()) + input = Input_U; + else if (c.isLower() || c.isDigit()) + input = Input_l; + else if (c == QLatin1Char('_')) + input = Input_underscore; + else if (c.isSpace() && c != QChar::ParagraphSeparator) + input = Input_space; + else + input = Input_other; + + switch (state) { + case 0: + switch (input) { + case Input_U: + state = 1; + break; + case Input_l: + state = 2; + break; + case Input_underscore: + state = 3; + break; + case Input_space: + state = 4; + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + return cursor.movePosition(QTextCursor::WordLeft, mode); + } + break; + case 1: + switch (input) { + case Input_U: + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + return true; + } + break; + + case 2: + switch (input) { + case Input_U: + return true; + case Input_l: + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + return true; + } + break; + case 3: + switch (input) { + case Input_underscore: + break; + case Input_U: + state = 1; + break; + case Input_l: + state = 2; + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + return true; + } + break; + case 4: + switch (input) { + case Input_space: + break; + case Input_U: + state = 1; + break; + case Input_l: + state = 2; + break; + case Input_underscore: + state = 3; + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + if (cursor.positionInBlock() == 0) + return true; + return cursor.movePosition(QTextCursor::WordLeft, mode); + } + } + + if (!cursor.movePosition(QTextCursor::Left, mode)) + return true; + } +} + +bool BaseTextEditorWidgetPrivate::camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode) +{ + int state = 0; + enum Input { + Input_U, + Input_l, + Input_underscore, + Input_space, + Input_other + }; + + forever { + QChar c = q->document()->characterAt(cursor.position()); + Input input = Input_other; + if (c.isUpper()) + input = Input_U; + else if (c.isLower() || c.isDigit()) + input = Input_l; + else if (c == QLatin1Char('_')) + input = Input_underscore; + else if (c.isSpace() && c != QChar::ParagraphSeparator) + input = Input_space; + else + input = Input_other; + + switch (state) { + case 0: + switch (input) { + case Input_U: + state = 4; + break; + case Input_l: + state = 1; + break; + case Input_underscore: + state = 6; + break; + default: + return cursor.movePosition(QTextCursor::WordRight, mode); + } + break; + case 1: + switch (input) { + case Input_U: + return true; + case Input_l: + break; + case Input_underscore: + state = 6; + break; + case Input_space: + state = 7; + break; + default: + return true; + } + break; + case 2: + switch (input) { + case Input_U: + break; + case Input_l: + cursor.movePosition(QTextCursor::Left, mode); + return true; + case Input_underscore: + state = 6; + break; + case Input_space: + state = 7; + break; + default: + return true; + } + break; + case 4: + switch (input) { + case Input_U: + state = 2; + break; + case Input_l: + state = 1; + break; + case Input_underscore: + state = 6; + break; + case Input_space: + state = 7; + break; + default: + return true; + } + break; + case 6: + switch (input) { + case Input_underscore: + break; + case Input_space: + state = 7; + break; + default: + return true; + } + break; + case 7: + switch (input) { + case Input_space: + break; + default: + return true; + } + break; + } + cursor.movePosition(QTextCursor::Right, mode); + } +} + +bool BaseTextEditorWidgetPrivate::cursorMoveKeyEvent(QKeyEvent *e) +{ + QTextCursor cursor = q->textCursor(); + + QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; + QTextCursor::MoveOperation op = QTextCursor::NoMove; + + if (e == QKeySequence::MoveToNextChar) { + op = QTextCursor::Right; + } else if (e == QKeySequence::MoveToPreviousChar) { + op = QTextCursor::Left; + } else if (e == QKeySequence::SelectNextChar) { + op = QTextCursor::Right; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectPreviousChar) { + op = QTextCursor::Left; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectNextWord) { + op = QTextCursor::WordRight; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectPreviousWord) { + op = QTextCursor::WordLeft; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectStartOfLine) { + op = QTextCursor::StartOfLine; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectEndOfLine) { + op = QTextCursor::EndOfLine; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectStartOfBlock) { + op = QTextCursor::StartOfBlock; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectEndOfBlock) { + op = QTextCursor::EndOfBlock; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectStartOfDocument) { + op = QTextCursor::Start; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectEndOfDocument) { + op = QTextCursor::End; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectPreviousLine) { + op = QTextCursor::Up; + mode = QTextCursor::KeepAnchor; + } else if (e == QKeySequence::SelectNextLine) { + op = QTextCursor::Down; + mode = QTextCursor::KeepAnchor; + { + QTextBlock block = cursor.block(); + QTextLine line = currentTextLine(cursor); + if (!block.next().isValid() + && line.isValid() + && line.lineNumber() == block.layout()->lineCount() - 1) + op = QTextCursor::End; + } + } else if (e == QKeySequence::MoveToNextWord) { + op = QTextCursor::WordRight; + } else if (e == QKeySequence::MoveToPreviousWord) { + op = QTextCursor::WordLeft; + } else if (e == QKeySequence::MoveToEndOfBlock) { + op = QTextCursor::EndOfBlock; + } else if (e == QKeySequence::MoveToStartOfBlock) { + op = QTextCursor::StartOfBlock; + } else if (e == QKeySequence::MoveToNextLine) { + op = QTextCursor::Down; + } else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } else if (e == QKeySequence::MoveToStartOfLine) { + op = QTextCursor::StartOfLine; + } else if (e == QKeySequence::MoveToEndOfLine) { + op = QTextCursor::EndOfLine; + } else if (e == QKeySequence::MoveToStartOfDocument) { + op = QTextCursor::Start; + } else if (e == QKeySequence::MoveToEndOfDocument) { + op = QTextCursor::End; + } else { + return false; + } + + +// Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but +// here's the breakdown: +// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command), +// Alt (Option), or Meta (Control). +// Command/Control + Left/Right -- Move to left or right of the line +// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor) +// Option + Left/Right -- Move one word Left/right. +// + Up/Down -- Begin/End of Paragraph. +// Home/End Top/Bottom of file. (usually don't move the cursor, but will select) + + bool visualNavigation = cursor.visualNavigation(); + cursor.setVisualNavigation(true); + + if (q->camelCaseNavigationEnabled() && op == QTextCursor::WordRight) + camelCaseRight(cursor, mode); + else if (q->camelCaseNavigationEnabled() && op == QTextCursor::WordLeft) + camelCaseLeft(cursor, mode); + else if (!cursor.movePosition(op, mode) && mode == QTextCursor::MoveAnchor) + cursor.clearSelection(); + cursor.setVisualNavigation(visualNavigation); + + q->setTextCursor(cursor); + q->ensureCursorVisible(); + return true; +} + +void BaseTextEditorWidget::viewPageUp() +{ + verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); +} + +void BaseTextEditorWidget::viewPageDown() +{ + verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); +} + +void BaseTextEditorWidget::viewLineUp() +{ + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); +} + +void BaseTextEditorWidget::viewLineDown() +{ + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); +} + +static inline bool isModifier(QKeyEvent *e) +{ + if (!e) + return false; + switch (e->key()) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Meta: + case Qt::Key_Alt: + return true; + default: + return false; + } +} + +static inline bool isPrintableText(const QString &text) +{ + return !text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t')); +} + +void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e) +{ + if (!isModifier(e) && mouseHidingEnabled()) + viewport()->setCursor(Qt::BlankCursor); + ToolTip::hide(); + + d->m_moveLineUndoHack = false; + d->clearVisibleFoldedBlock(); + + if (e->key() == Qt::Key_Alt + && d->m_behaviorSettings.m_keyboardTooltips) { + d->m_maybeFakeTooltipEvent = true; + } else { + d->m_maybeFakeTooltipEvent = false; + if (e->key() == Qt::Key_Escape + && d->m_snippetOverlay->isVisible()) { + e->accept(); + d->m_snippetOverlay->hide(); + d->m_snippetOverlay->mangle(); + d->m_snippetOverlay->clear(); + QTextCursor cursor = textCursor(); + cursor.clearSelection(); + setTextCursor(cursor); + return; + } + } + + bool ro = isReadOnly(); + const bool inOverwriteMode = overwriteMode(); + + if (!ro && d->m_inBlockSelectionMode) { + if (e == QKeySequence::Cut) { + cut(); + e->accept(); + return; + } else if (e == QKeySequence::Delete || e->key() == Qt::Key_Backspace) { + if (d->m_blockSelection.positionColumn == d->m_blockSelection.anchorColumn) { + if (e == QKeySequence::Delete) + ++d->m_blockSelection.positionColumn; + else if (d->m_blockSelection.positionColumn > 0) + --d->m_blockSelection.positionColumn; + } + d->removeBlockSelection(); + e->accept(); + return; + } else if (e == QKeySequence::Paste) { + d->removeBlockSelection(); + // continue + } + } + + + if (!ro + && (e == QKeySequence::InsertParagraphSeparator + || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))) { + if (d->m_inBlockSelectionMode) { + d->disableBlockSelection(false); + e->accept(); + return; + } + if (d->m_snippetOverlay->isVisible()) { + e->accept(); + d->m_snippetOverlay->hide(); + d->m_snippetOverlay->mangle(); + d->m_snippetOverlay->clear(); + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::EndOfBlock); + setTextCursor(cursor); + return; + } + + QTextCursor cursor = textCursor(); + const TabSettings &ts = d->m_document->tabSettings(); + const TypingSettings &tps = d->m_document->typingSettings(); + cursor.beginEditBlock(); + + int extraBlocks = + d->m_autoCompleter->paragraphSeparatorAboutToBeInserted(cursor, + d->m_document->tabSettings()); + + QString previousIndentationString; + if (tps.m_autoIndent) { + cursor.insertBlock(); + d->m_document->autoIndent(cursor); + } else { + cursor.insertBlock(); + + // After inserting the block, to avoid duplicating whitespace on the same line + const QString &previousBlockText = cursor.block().previous().text(); + previousIndentationString = ts.indentationString(previousBlockText); + if (!previousIndentationString.isEmpty()) + cursor.insertText(previousIndentationString); + } + cursor.endEditBlock(); + e->accept(); + + if (extraBlocks > 0) { + QTextCursor ensureVisible = cursor; + while (extraBlocks > 0) { + --extraBlocks; + ensureVisible.movePosition(QTextCursor::NextBlock); + if (tps.m_autoIndent) + d->m_document->autoIndent(ensureVisible); + else if (!previousIndentationString.isEmpty()) + ensureVisible.insertText(previousIndentationString); + } + setTextCursor(ensureVisible); + } + + setTextCursor(cursor); + return; + } else if (!ro + && (e == QKeySequence::MoveToStartOfBlock + || e == QKeySequence::SelectStartOfBlock)){ + if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) { + e->accept(); + return; + } + d->handleHomeKey(e == QKeySequence::SelectStartOfBlock); + e->accept(); + return; + } else if (!ro + && (e == QKeySequence::MoveToStartOfLine + || e == QKeySequence::SelectStartOfLine)){ + if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) { + e->accept(); + return; + } + QTextCursor cursor = textCursor(); + if (QTextLayout *layout = cursor.block().layout()) { + if (layout->lineForTextPosition(cursor.position() - cursor.block().position()).lineNumber() == 0) { + d->handleHomeKey(e == QKeySequence::SelectStartOfLine); + e->accept(); + return; + } + } + } else if (!ro + && e == QKeySequence::DeleteStartOfWord + && d->m_document->typingSettings().m_autoIndent + && !textCursor().hasSelection()){ + e->accept(); + QTextCursor c = textCursor(); + int pos = c.position(); + if (camelCaseNavigationEnabled()) + d->camelCaseLeft(c, QTextCursor::MoveAnchor); + else + c.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); + int targetpos = c.position(); + forever { + d->handleBackspaceKey(); + int cpos = textCursor().position(); + if (cpos == pos || cpos <= targetpos) + break; + pos = cpos; + } + return; + } else if (!ro && e == QKeySequence::DeleteStartOfWord && !textCursor().hasSelection()) { + e->accept(); + QTextCursor c = textCursor(); + if (camelCaseNavigationEnabled()) + d->camelCaseLeft(c, QTextCursor::KeepAnchor); + else + c.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + c.removeSelectedText(); + return; + } else if (!ro && e == QKeySequence::DeleteEndOfWord && !textCursor().hasSelection()) { + e->accept(); + QTextCursor c = textCursor(); + if (camelCaseNavigationEnabled()) + d->camelCaseRight(c, QTextCursor::KeepAnchor); + else + c.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + c.removeSelectedText(); + return; + } else if (!ro && (e == QKeySequence::MoveToNextPage || e == QKeySequence::MoveToPreviousPage) + && d->m_inBlockSelectionMode) { + d->disableBlockSelection(false); + QPlainTextEdit::keyPressEvent(e); + return; + } else if (!ro && (e == QKeySequence::SelectNextPage || e == QKeySequence::SelectPreviousPage) + && d->m_inBlockSelectionMode) { + QPlainTextEdit::keyPressEvent(e); + d->m_blockSelection.positionBlock = QPlainTextEdit::textCursor().blockNumber(); + setTextCursor(d->m_blockSelection.selection(d->m_document.data()), true); + viewport()->update(); + e->accept(); + return; + } else switch (e->key()) { + + +#if 0 + case Qt::Key_Dollar: { + d->m_overlay->setVisible(!d->m_overlay->isVisible()); + d->m_overlay->setCursor(textCursor()); + e->accept(); + return; + + } break; +#endif + case Qt::Key_Tab: + case Qt::Key_Backtab: { + if (ro) break; + if (d->m_snippetOverlay->isVisible() && !d->m_snippetOverlay->isEmpty()) { + d->snippetTabOrBacktab(e->key() == Qt::Key_Tab); + e->accept(); + return; + } + QTextCursor cursor = textCursor(); + int newPosition; + if (d->m_document->typingSettings().tabShouldIndent(document(), cursor, &newPosition)) { + if (newPosition != cursor.position() && !cursor.hasSelection()) { + cursor.setPosition(newPosition); + setTextCursor(cursor); + } + d->m_document->autoIndent(cursor); + } else { + if (e->key() == Qt::Key_Tab) + indent(); + else + unindent(); + } + e->accept(); + return; + } break; + case Qt::Key_Backspace: + if (ro) break; + if ((e->modifiers() & (Qt::ControlModifier + | Qt::ShiftModifier + | Qt::AltModifier + | Qt::MetaModifier)) == Qt::NoModifier + && !textCursor().hasSelection()) { + d->handleBackspaceKey(); + e->accept(); + return; + } + break; + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Right: + case Qt::Key_Left: + if (HostOsInfo::isMacHost()) + break; + if ((e->modifiers() + & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) { + if (!d->m_inBlockSelectionMode) + d->enableBlockSelection(textCursor()); + switch (e->key()) { + case Qt::Key_Up: + if (d->m_blockSelection.positionBlock > 0) + --d->m_blockSelection.positionBlock; + break; + case Qt::Key_Down: + if (d->m_blockSelection.positionBlock < document()->blockCount() - 1) + ++d->m_blockSelection.positionBlock; + break; + case Qt::Key_Left: + if (d->m_blockSelection.positionColumn > 0) + --d->m_blockSelection.positionColumn; + break; + case Qt::Key_Right: + ++d->m_blockSelection.positionColumn; + break; + default: + break; + } + d->resetCursorFlashTimer(); + setTextCursor(d->m_blockSelection.selection(d->m_document.data()), true); + viewport()->update(); + e->accept(); + return; + } else if (d->m_inBlockSelectionMode) { // leave block selection mode + d->disableBlockSelection(); + } + break; + case Qt::Key_Insert: + if (ro) break; + if (e->modifiers() == Qt::NoModifier) { + AutoCompleter *ac = d->m_autoCompleter.data(); + if (inOverwriteMode) { + ac->setAutoParenthesesEnabled(d->autoParenthesisOverwriteBackup); + ac->setSurroundWithEnabled(d->surroundWithEnabledOverwriteBackup); + setOverwriteMode(false); + viewport()->update(); + } else { + d->autoParenthesisOverwriteBackup = ac->isAutoParenthesesEnabled(); + d->surroundWithEnabledOverwriteBackup = ac->isSurroundWithEnabled(); + ac->setAutoParenthesesEnabled(false); + ac->setSurroundWithEnabled(false); + setOverwriteMode(true); + } + e->accept(); + return; + } + break; + + default: + break; + } + + const QString eventText = e->text(); + if (!ro && d->m_inBlockSelectionMode) { + if (isPrintableText(eventText)) { + d->insertIntoBlockSelection(eventText); + goto skip_event; + } + } + + if (e->key() == Qt::Key_H + && e->modifiers() == Qt::KeyboardModifiers(HostOsInfo::controlModifier())) { + d->universalHelper(); + e->accept(); + return; + } + + if (ro || !isPrintableText(eventText)) { + if (!d->cursorMoveKeyEvent(e)) { + QTextCursor cursor = textCursor(); + bool cursorWithinSnippet = false; + if (d->m_snippetOverlay->isVisible() + && (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)) { + cursorWithinSnippet = d->snippetCheckCursor(cursor); + } + if (cursorWithinSnippet) + cursor.beginEditBlock(); + + QPlainTextEdit::keyPressEvent(e); + + if (cursorWithinSnippet) { + cursor.endEditBlock(); + d->m_snippetOverlay->updateEquivalentSelections(textCursor()); + } + } + } else if ((e->modifiers() & (Qt::ControlModifier|Qt::AltModifier)) != Qt::ControlModifier){ + // only go here if control is not pressed, except if also alt is pressed + // because AltGr maps to Alt + Ctrl + QTextCursor cursor = textCursor(); + const QString &autoText = d->m_autoCompleter->autoComplete(cursor, eventText); + + QChar electricChar; + if (d->m_document->typingSettings().m_autoIndent) { + foreach (QChar c, eventText) { + if (d->m_document->indenter()->isElectricCharacter(c)) { + electricChar = c; + break; + } + } + } + + bool cursorWithinSnippet = false; + if (d->m_snippetOverlay->isVisible()) + cursorWithinSnippet = d->snippetCheckCursor(cursor); + + bool doEditBlock = !electricChar.isNull() || !autoText.isEmpty() || cursorWithinSnippet; + if (doEditBlock) + cursor.beginEditBlock(); + + if (inOverwriteMode) { + if (!doEditBlock) + cursor.beginEditBlock(); + QTextBlock block = cursor.block(); + int eolPos = block.position() + block.length() - 1; + int selEndPos = qMin(cursor.position() + eventText.length(), eolPos); + cursor.setPosition(selEndPos, QTextCursor::KeepAnchor); + cursor.insertText(eventText); + if (!doEditBlock) + cursor.endEditBlock(); + } else { + cursor.insertText(eventText); + } + + if (!autoText.isEmpty()) { + int pos = cursor.position(); + cursor.insertText(autoText); + //Select the inserted text, to be able to re-indent the inserted text + cursor.setPosition(pos, QTextCursor::KeepAnchor); + } + if (!electricChar.isNull() && d->m_autoCompleter->contextAllowsElectricCharacters(cursor)) + d->m_document->autoIndent(cursor, electricChar); + if (!autoText.isEmpty()) + cursor.setPosition(autoText.length() == 1 ? cursor.position() : cursor.anchor()); + + if (doEditBlock) { + cursor.endEditBlock(); + if (cursorWithinSnippet) + d->m_snippetOverlay->updateEquivalentSelections(textCursor()); + } + + setTextCursor(cursor); + } + + skip_event: + if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled) + d->m_parenthesesMatchingTimer.start(50); + + if (!ro && d->m_contentsChanged && isPrintableText(eventText) && !inOverwriteMode) + d->m_codeAssistant.process(); +} + +void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet) +{ + Snippet::ParsedSnippet data = Snippet::parse(snippet); + + QTextCursor cursor = cursor_arg; + cursor.beginEditBlock(); + cursor.removeSelectedText(); + const int startCursorPosition = cursor.position(); + + cursor.insertText(data.text); + QList<QTextEdit::ExtraSelection> selections; + + QList<NameMangler *> manglers; + for (int i = 0; i < data.ranges.count(); ++i) { + int position = data.ranges.at(i).start + startCursorPosition; + int length = data.ranges.at(i).length; + + QTextCursor tc(document()); + tc.setPosition(position); + tc.setPosition(position + length, QTextCursor::KeepAnchor); + QTextEdit::ExtraSelection selection; + selection.cursor = tc; + selection.format = (length + ? textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES) + : textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES_RENAME)); + selections.append(selection); + manglers << data.ranges.at(i).mangler; + } + + cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor); + d->m_document->autoIndent(cursor); + cursor.endEditBlock(); + + setExtraSelections(BaseTextEditorWidget::SnippetPlaceholderSelection, selections); + d->m_snippetOverlay->setNameMangler(manglers); + + if (!selections.isEmpty()) { + const QTextEdit::ExtraSelection &selection = selections.first(); + + cursor = textCursor(); + if (selection.cursor.hasSelection()) { + cursor.setPosition(selection.cursor.selectionStart()); + cursor.setPosition(selection.cursor.selectionEnd(), QTextCursor::KeepAnchor); + } else { + cursor.setPosition(selection.cursor.position()); + } + setTextCursor(cursor); + } +} + +void BaseTextEditorWidgetPrivate::universalHelper() +{ + // Test function for development. Place your new fangled experiment here to + // give it proper scrutiny before pushing it onto others. +} + +void BaseTextEditorWidget::setTextCursor(const QTextCursor &cursor, bool keepBlockSelection) +{ + // workaround for QTextControl bug + bool selectionChange = cursor.hasSelection() || textCursor().hasSelection(); + if (!keepBlockSelection && d->m_inBlockSelectionMode) + d->disableBlockSelection(false); + QTextCursor c = cursor; + c.setVisualNavigation(true); + QPlainTextEdit::setTextCursor(c); + if (selectionChange) + d->slotSelectionChanged(); +} + +void BaseTextEditorWidget::setTextCursor(const QTextCursor &cursor) +{ + setTextCursor(cursor, false); +} + +void BaseTextEditorWidget::gotoLine(int line, int column, bool centerLine) +{ + d->m_lastCursorChangeWasInteresting = false; // avoid adding the previous position to history + 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 (document()->characterAt(pos).category() == QChar::Separator_Space) { + ++pos; + } + cursor.setPosition(pos); + } + setTextCursor(cursor); + + if (centerLine) + centerCursor(); + else + ensureCursorVisible(); + } + d->saveCurrentCursorPositionForNavigation(); +} + +int BaseTextEditorWidget::position(BaseTextEditor::PositionOperation posOp, int at) const +{ + QTextCursor tc = textCursor(); + + if (at != -1) + tc.setPosition(at); + + if (posOp == BaseTextEditor::Current) + return tc.position(); + + switch (posOp) { + case BaseTextEditor::EndOfLine: + tc.movePosition(QTextCursor::EndOfLine); + return tc.position(); + case BaseTextEditor::StartOfLine: + tc.movePosition(QTextCursor::StartOfLine); + return tc.position(); + case BaseTextEditor::Anchor: + if (tc.hasSelection()) + return tc.anchor(); + break; + case BaseTextEditor::EndOfDoc: + tc.movePosition(QTextCursor::End); + return tc.position(); + default: + break; + } + + return -1; +} + +QRect BaseTextEditorWidget::cursorRect(int pos) const +{ + QTextCursor tc = textCursor(); + if (pos >= 0) + tc.setPosition(pos); + QRect result = cursorRect(tc); + result.moveTo(viewport()->mapToGlobal(result.topLeft())); + return result; +} + +void BaseTextEditorWidget::convertPosition(int pos, int *line, int *column) const +{ + Convenience::convertPosition(document(), pos, line, column); +} + +bool BaseTextEditorWidget::event(QEvent *e) +{ + // FIXME: That's far too heavy, and triggers e.g for ChildEvent + if (d && e->type() != QEvent::InputMethodQuery) + d->m_contentsChanged = false; + switch (e->type()) { + case QEvent::ShortcutOverride: + if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && d->m_snippetOverlay->isVisible()) { + e->accept(); + return true; + } + e->ignore(); // we are a really nice citizen + d->m_maybeFakeTooltipEvent = false; + return true; + break; + default: + break; + } + + return QPlainTextEdit::event(e); +} + +void BaseTextEditorWidget::inputMethodEvent(QInputMethodEvent *e) +{ + if (d->m_inBlockSelectionMode) { + if (!e->commitString().isEmpty()) + d->insertIntoBlockSelection(e->commitString()); + return; + } + QPlainTextEdit::inputMethodEvent(e); +} + +void BaseTextEditorWidgetPrivate::documentAboutToBeReloaded() +{ + //memorize cursor position + m_tempState = q->saveState(); + + // remove extra selections (loads of QTextCursor objects) + + for (int i = 0; i < BaseTextEditorWidget::NExtraSelectionKinds; ++i) + m_extraSelections[i].clear(); + q->QPlainTextEdit::setExtraSelections(QList<QTextEdit::ExtraSelection>()); + + // clear all overlays + m_overlay->clear(); + m_snippetOverlay->clear(); + m_searchResultOverlay->clear(); + m_refactorOverlay->clear(); +} + +void BaseTextEditorWidgetPrivate::documentReloadFinished(bool success) +{ + if (!success) + return; + + // restore cursor position + q->restoreState(m_tempState); + updateCannotDecodeInfo(); +} + +QByteArray BaseTextEditorWidget::saveState() const +{ + QByteArray state; + QDataStream stream(&state, QIODevice::WriteOnly); + stream << 1; // version number + stream << verticalScrollBar()->value(); + stream << horizontalScrollBar()->value(); + int line, column; + convertPosition(textCursor().position(), &line, &column); + stream << line; + stream << column; + + // store code folding state + QList<int> foldedBlocks; + QTextBlock block = document()->firstBlock(); + while (block.isValid()) { + if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->folded()) { + int number = block.blockNumber(); + foldedBlocks += number; + } + block = block.next(); + } + stream << foldedBlocks; + + return state; +} + +bool BaseTextEditorWidget::restoreState(const QByteArray &state) +{ + if (state.isEmpty()) { + if (d->m_displaySettings.m_autoFoldFirstComment) + d->foldLicenseHeader(); + return false; + } + int version; + int vval; + int hval; + int lval; + int cval; + QDataStream stream(state); + stream >> version; + stream >> vval; + stream >> hval; + stream >> lval; + stream >> cval; + + if (version >= 1) { + QList<int> collapsedBlocks; + stream >> collapsedBlocks; + QTextDocument *doc = document(); + bool layoutChanged = false; + foreach (int blockNumber, collapsedBlocks) { + QTextBlock block = doc->findBlockByNumber(qMax(0, blockNumber)); + if (block.isValid()) { + TextDocumentLayout::doFoldOrUnfold(block, false); + layoutChanged = true; + } + } + if (layoutChanged) { + TextDocumentLayout *documentLayout = + qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return false); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); + } + } else { + if (d->m_displaySettings.m_autoFoldFirstComment) + d->foldLicenseHeader(); + } + + d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history + gotoLine(lval, cval); + verticalScrollBar()->setValue(vval); + horizontalScrollBar()->setValue(hval); + d->saveCurrentCursorPositionForNavigation(); + return true; +} + +void BaseTextEditorWidget::setParenthesesMatchingEnabled(bool b) +{ + d->m_parenthesesMatchingEnabled = b; +} + +bool BaseTextEditorWidget::isParenthesesMatchingEnabled() const +{ + return d->m_parenthesesMatchingEnabled; +} + +void BaseTextEditorWidget::setHighlightCurrentLine(bool b) +{ + d->m_highlightCurrentLine = b; + d->updateCurrentLineHighlight(); +} + +bool BaseTextEditorWidget::highlightCurrentLine() const +{ + return d->m_highlightCurrentLine; +} + +void BaseTextEditorWidget::setLineNumbersVisible(bool b) +{ + d->m_lineNumbersVisible = b; + d->slotUpdateExtraAreaWidth(); +} + +bool BaseTextEditorWidget::lineNumbersVisible() const +{ + return d->m_lineNumbersVisible; +} + +void BaseTextEditorWidget::setAlwaysOpenLinksInNextSplit(bool b) +{ + d->m_displaySettings.m_openLinksInNextSplit = b; +} + +bool BaseTextEditorWidget::alwaysOpenLinksInNextSplit() const +{ + return d->m_displaySettings.m_openLinksInNextSplit; +} + +void BaseTextEditorWidget::setMarksVisible(bool b) +{ + d->m_marksVisible = b; + d->slotUpdateExtraAreaWidth(); +} + +bool BaseTextEditorWidget::marksVisible() const +{ + return d->m_marksVisible; +} + +void BaseTextEditorWidget::setRequestMarkEnabled(bool b) +{ + d->m_requestMarkEnabled = b; +} + +bool BaseTextEditorWidget::requestMarkEnabled() const +{ + return d->m_requestMarkEnabled; +} + +void BaseTextEditorWidget::setLineSeparatorsAllowed(bool b) +{ + d->m_lineSeparatorsAllowed = b; +} + +bool BaseTextEditorWidget::lineSeparatorsAllowed() const +{ + return d->m_lineSeparatorsAllowed; +} + +void BaseTextEditorWidgetPrivate::updateCodeFoldingVisible() +{ + const bool visible = m_codeFoldingSupported && m_displaySettings.m_displayFoldingMarkers; + if (m_codeFoldingVisible != visible) { + m_codeFoldingVisible = visible; + slotUpdateExtraAreaWidth(); + } +} + +void BaseTextEditorWidgetPrivate::reconfigure() +{ + q->configureMimeType(MimeDatabase::findByFile(m_document->filePath())); +} + +bool BaseTextEditorWidget::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 BaseTextEditorWidget::setCodeFoldingSupported(bool b) +{ + d->m_codeFoldingSupported = b; + d->updateCodeFoldingVisible(); +} + +bool BaseTextEditorWidget::codeFoldingSupported() const +{ + return d->m_codeFoldingSupported; +} + +void BaseTextEditorWidget::setMouseNavigationEnabled(bool b) +{ + d->m_behaviorSettings.m_mouseNavigation = b; +} + +bool BaseTextEditorWidget::mouseNavigationEnabled() const +{ + return d->m_behaviorSettings.m_mouseNavigation; +} + +void BaseTextEditorWidget::setMouseHidingEnabled(bool b) +{ + d->m_behaviorSettings.m_mouseHiding = b; +} + +bool BaseTextEditorWidget::mouseHidingEnabled() const +{ + return d->m_behaviorSettings.m_mouseHiding; +} + +void BaseTextEditorWidget::setScrollWheelZoomingEnabled(bool b) +{ + d->m_behaviorSettings.m_scrollWheelZooming = b; +} + +bool BaseTextEditorWidget::scrollWheelZoomingEnabled() const +{ + return d->m_behaviorSettings.m_scrollWheelZooming; +} + +void BaseTextEditorWidget::setConstrainTooltips(bool b) +{ + d->m_behaviorSettings.m_constrainHoverTooltips = b; +} + +bool BaseTextEditorWidget::constrainTooltips() const +{ + return d->m_behaviorSettings.m_constrainHoverTooltips; +} + +void BaseTextEditorWidget::setCamelCaseNavigationEnabled(bool b) +{ + d->m_behaviorSettings.m_camelCaseNavigation = b; +} + +bool BaseTextEditorWidget::camelCaseNavigationEnabled() const +{ + return d->m_behaviorSettings.m_camelCaseNavigation; +} + +void BaseTextEditorWidget::setRevisionsVisible(bool b) +{ + d->m_revisionsVisible = b; + d->slotUpdateExtraAreaWidth(); +} + +bool BaseTextEditorWidget::revisionsVisible() const +{ + return d->m_revisionsVisible; +} + +void BaseTextEditorWidget::setVisibleWrapColumn(int column) +{ + d->m_visibleWrapColumn = column; + viewport()->update(); +} + +int BaseTextEditorWidget::visibleWrapColumn() const +{ + return d->m_visibleWrapColumn; +} + +void BaseTextEditorWidget::setAutoCompleter(AutoCompleter *autoCompleter) +{ + d->m_autoCompleter.reset(autoCompleter); +} + +AutoCompleter *BaseTextEditorWidget::autoCompleter() const +{ + return d->m_autoCompleter.data(); +} + +//--------- BaseTextEditorPrivate ----------- + + +void BaseTextEditorWidgetPrivate::setupDocumentSignals() +{ + QTextDocument *doc = m_document->document(); + q->QPlainTextEdit::setDocument(doc); + q->setCursorWidth(2); // Applies to the document layout + + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_CHECK(documentLayout); + + QObject::connect(documentLayout, &QPlainTextDocumentLayout::updateBlock, + this, &BaseTextEditorWidgetPrivate::slotUpdateBlockNotify); + + QObject::connect(documentLayout, &TextDocumentLayout::updateExtraArea, + m_extraArea, static_cast<void (QWidget::*)()>(&QWidget::update)); + + QObject::connect(q, &BaseTextEditorWidget::requestBlockUpdate, + documentLayout, &QPlainTextDocumentLayout::updateBlock); + + QObject::connect(doc, &QTextDocument::contentsChange, + this, &BaseTextEditorWidgetPrivate::editorContentsChange); + + QObject::connect(m_document.data(), &TextDocument::aboutToReload, + this, &BaseTextEditorWidgetPrivate::documentAboutToBeReloaded); + + QObject::connect(m_document.data(), &TextDocument::reloadFinished, + this, &BaseTextEditorWidgetPrivate::documentReloadFinished); + + QObject::connect(m_document.data(), &TextDocument::tabSettingsChanged, + this, &BaseTextEditorWidgetPrivate::updateTabStops); + + QObject::connect(m_document.data(), &TextDocument::fontSettingsChanged, + this, &BaseTextEditorWidgetPrivate::applyFontSettingsDelayed); + + slotUpdateExtraAreaWidth(); + + TextEditorSettings *settings = TextEditorSettings::instance(); + + // Connect to settings change signals + connect(settings, &TextEditorSettings::fontSettingsChanged, + m_document.data(), &TextDocument::setFontSettings); + connect(settings, &TextEditorSettings::typingSettingsChanged, + q, &BaseTextEditorWidget::setTypingSettings); + connect(settings, &TextEditorSettings::storageSettingsChanged, + q, &BaseTextEditorWidget::setStorageSettings); + connect(settings, &TextEditorSettings::behaviorSettingsChanged, + q, &BaseTextEditorWidget::setBehaviorSettings); + connect(settings, &TextEditorSettings::marginSettingsChanged, + q, &BaseTextEditorWidget::setMarginSettings); + connect(settings, &TextEditorSettings::displaySettingsChanged, + q, &BaseTextEditorWidget::setDisplaySettings); + connect(settings, &TextEditorSettings::completionSettingsChanged, + q, &BaseTextEditorWidget::setCompletionSettings); + connect(settings, &TextEditorSettings::extraEncodingSettingsChanged, + q, &BaseTextEditorWidget::setExtraEncodingSettings); + + connect(q, &BaseTextEditorWidget::requestFontZoom, + settings, &TextEditorSettings::fontZoomRequested); + connect(q, &BaseTextEditorWidget::requestZoomReset, + settings, &TextEditorSettings::zoomResetRequested); + + // Apply current settings + m_document->setFontSettings(settings->fontSettings()); + m_document->setTabSettings(settings->codeStyle()->tabSettings()); // also set through code style ??? + q->setTypingSettings(settings->typingSettings()); + q->setStorageSettings(settings->storageSettings()); + q->setBehaviorSettings(settings->behaviorSettings()); + q->setMarginSettings(settings->marginSettings()); + q->setDisplaySettings(settings->displaySettings()); + q->setCompletionSettings(settings->completionSettings()); + q->setExtraEncodingSettings(settings->extraEncodingSettings()); + q->setCodeStyle(settings->codeStyle(q->languageSettingsId())); +} + +bool BaseTextEditorWidgetPrivate::snippetCheckCursor(const QTextCursor &cursor) +{ + if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty()) + return false; + + QTextCursor start = cursor; + start.setPosition(cursor.selectionStart()); + QTextCursor end = cursor; + end.setPosition(cursor.selectionEnd()); + if (!m_snippetOverlay->hasCursorInSelection(start) + || !m_snippetOverlay->hasCursorInSelection(end) + || m_snippetOverlay->hasFirstSelectionBeginMoved()) { + m_snippetOverlay->setVisible(false); + m_snippetOverlay->mangle(); + m_snippetOverlay->clear(); + return false; + } + return true; +} + +void BaseTextEditorWidgetPrivate::snippetTabOrBacktab(bool forward) +{ + if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty()) + return; + QTextCursor cursor = q->textCursor(); + OverlaySelection final; + if (forward) { + for (int i = 0; i < m_snippetOverlay->selections().count(); ++i){ + const OverlaySelection &selection = m_snippetOverlay->selections().at(i); + if (selection.m_cursor_begin.position() >= cursor.position() + && selection.m_cursor_end.position() > cursor.position()) { + final = selection; + break; + } + } + } else { + for (int i = m_snippetOverlay->selections().count()-1; i >= 0; --i){ + const OverlaySelection &selection = m_snippetOverlay->selections().at(i); + if (selection.m_cursor_end.position() < cursor.position()) { + final = selection; + break; + } + } + + } + if (final.m_cursor_begin.isNull()) + final = forward ? m_snippetOverlay->selections().first() : m_snippetOverlay->selections().last(); + + if (final.m_cursor_begin.position() == final.m_cursor_end.position()) { // empty tab stop + cursor.setPosition(final.m_cursor_end.position()); + } else { + cursor.setPosition(final.m_cursor_begin.position()); + cursor.setPosition(final.m_cursor_end.position(), QTextCursor::KeepAnchor); + } + q->setTextCursor(cursor); +} + +// Calculate global position for a tooltip considering the left extra area. +QPoint BaseTextEditorWidget::toolTipPosition(const QTextCursor &c) const +{ + const QPoint cursorPos = mapToGlobal(cursorRect(c).bottomRight() + QPoint(1,1)); + return cursorPos + QPoint(d->m_extraArea->width(), HostOsInfo::isWindowsHost() ? -24 : -16); +} + +void BaseTextEditorWidgetPrivate::processTooltipRequest(const QTextCursor &c) +{ + const QPoint toolTipPoint = q->toolTipPosition(c); + bool handled = false; + emit q->tooltipOverrideRequested(q, toolTipPoint, c.position(), &handled); + if (!handled) + emit q->tooltipRequested(toolTipPoint, c.position()); +} + +bool BaseTextEditorWidget::viewportEvent(QEvent *event) +{ + d->m_contentsChanged = false; + if (event->type() == QEvent::ToolTip) { + if (QApplication::keyboardModifiers() & Qt::ControlModifier + || (!(QApplication::keyboardModifiers() & Qt::ShiftModifier) + && d->m_behaviorSettings.m_constrainHoverTooltips)) { + // Tooltips should be eaten when either control is pressed (so they don't get in the + // way of code navigation) or if they are in constrained mode and shift is not pressed. + return true; + } + const QHelpEvent *he = static_cast<QHelpEvent*>(event); + const QPoint &pos = he->pos(); + + RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(pos); + if (refactorMarker.isValid() && !refactorMarker.tooltip.isEmpty()) { + ToolTip::show(he->globalPos(), TextContent(refactorMarker.tooltip), + viewport(), + refactorMarker.rect); + return true; + } + + QTextCursor tc = cursorForPosition(pos); + QTextBlock block = tc.block(); + QTextLine line = block.layout()->lineForTextPosition(tc.positionInBlock()); + QTC_CHECK(line.isValid()); + // Only handle tool tip for text cursor if mouse is within the block for the text cursor, + // and not if the mouse is e.g. in the empty space behind a short line. + if (line.isValid() + && pos.x() <= blockBoundingGeometry(block).left() + line.naturalTextRect().right()) { + d->processTooltipRequest(tc); + return true; + } + } + return QPlainTextEdit::viewportEvent(event); +} + + +void BaseTextEditorWidget::resizeEvent(QResizeEvent *e) +{ + QPlainTextEdit::resizeEvent(e); + QRect cr = rect(); + d->m_extraArea->setGeometry( + QStyle::visualRect(layoutDirection(), cr, + QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height()))); +} + +QRect BaseTextEditorWidgetPrivate::foldBox() +{ + if (m_highlightBlocksInfo.isEmpty() || extraAreaHighlightFoldedBlockNumber < 0) + return QRect(); + + QTextBlock begin = q->document()->findBlockByNumber(m_highlightBlocksInfo.open.last()); + + QTextBlock end = q->document()->findBlockByNumber(m_highlightBlocksInfo.close.first()); + if (!begin.isValid() || !end.isValid()) + return QRect(); + QRectF br = q->blockBoundingGeometry(begin).translated(q->contentOffset()); + QRectF er = q->blockBoundingGeometry(end).translated(q->contentOffset()); + + return QRect(m_extraArea->width() - foldBoxWidth(q->fontMetrics()), + int(br.top()), + foldBoxWidth(q->fontMetrics()), + er.bottom() - br.top()); +} + +QTextBlock BaseTextEditorWidgetPrivate::foldedBlockAt(const QPoint &pos, QRect *box) const +{ + QPointF offset = q->contentOffset(); + QTextBlock block = q->firstVisibleBlock(); + qreal top = q->blockBoundingGeometry(block).translated(offset).top(); + qreal bottom = top + q->blockBoundingRect(block).height(); + + int viewportHeight = q->viewport()->height(); + + while (block.isValid() && top <= viewportHeight) { + QTextBlock nextBlock = block.next(); + if (block.isVisible() && bottom >= 0 && q->replacementVisible(block.blockNumber())) { + 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); + + QString replacement = QLatin1String(" {") + q->foldReplacementText(block) + + QLatin1String("}; "); + + QRectF collapseRect(lineRect.right() + 12, + lineRect.top(), + q->fontMetrics().width(replacement), + 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 + q->blockBoundingRect(block).height(); + } + return QTextBlock(); +} + +void BaseTextEditorWidgetPrivate::highlightSearchResults(const QTextBlock &block, + TextEditorOverlay *overlay) +{ + if (m_searchExpr.isEmpty()) + return; + + int blockPosition = block.position(); + + QTextCursor cursor = q->textCursor(); + QString text = block.text(); + text.replace(QChar::Nbsp, QLatin1Char(' ')); + int idx = -1; + int l = 1; + + while (idx < text.length()) { + idx = m_searchExpr.indexIn(text, idx + l); + if (idx < 0) + break; + l = m_searchExpr.matchedLength(); + if (l == 0) + break; + if ((m_findFlags & FindWholeWords) + && ((idx && text.at(idx-1).isLetterOrNumber()) + || (idx + l < text.length() && text.at(idx + l).isLetterOrNumber()))) + continue; + + if (!inFindScope(blockPosition + idx, blockPosition + idx + l)) + continue; + + const QTextCharFormat &searchResultFormat + = m_document->fontSettings().toTextCharFormat(C_SEARCH_RESULT); + overlay->addOverlaySelection(blockPosition + idx, + blockPosition + idx + l, + searchResultFormat.background().color().darker(120), + QColor(), + (idx == cursor.selectionStart() - blockPosition + && idx + l == cursor.selectionEnd() - blockPosition)? + TextEditorOverlay::DropShadow : 0); + + } +} + +QString BaseTextEditorWidgetPrivate::copyBlockSelection() +{ + if (!m_inBlockSelectionMode) + return QString(); + QString selection; + const TabSettings &ts = m_document->tabSettings(); + QTextBlock block = + m_document->document()->findBlockByNumber(m_blockSelection.firstBlockNumber()); + const QTextBlock &lastBlock = + m_document->document()->findBlockByNumber(m_blockSelection.lastBlockNumber()); + bool textInserted = false; + for (;;) { + if (q->selectionVisible(block.blockNumber())) { + if (textInserted) + selection += QLatin1Char('\n'); + textInserted = true; + + QString text = block.text(); + int startOffset = 0; + int startPos = ts.positionAtColumn(text, m_blockSelection.firstVisualColumn(), &startOffset); + int endOffset = 0; + int endPos = ts.positionAtColumn(text, m_blockSelection.lastVisualColumn(), &endOffset); + + if (startPos == endPos) { + selection += QString(endOffset - startOffset, QLatin1Char(' ')); + } else { + if (startOffset < 0) + selection += QString(-startOffset, QLatin1Char(' ')); + if (endOffset < 0) + --endPos; + selection += text.mid(startPos, endPos - startPos); + if (endOffset < 0) + selection += QString(ts.m_tabSize + endOffset, QLatin1Char(' ')); + else if (endOffset > 0) + selection += QString(endOffset, QLatin1Char(' ')); + } + } + if (block == lastBlock) + break; + + block = block.next(); + } + return selection; +} + +void BaseTextEditorWidgetPrivate::setCursorToColumn(QTextCursor &cursor, int column, QTextCursor::MoveMode moveMode) +{ + const TabSettings &ts = m_document->tabSettings(); + int offset = 0; + const int cursorPosition = cursor.position(); + const int pos = ts.positionAtColumn(cursor.block().text(), column, &offset); + cursor.setPosition(cursor.block().position() + pos, offset == 0 ? moveMode : QTextCursor::MoveAnchor); + if (offset == 0) + return; + if (offset < 0) { + // the column is inside a tab so it is replaced with spaces + cursor.setPosition(cursor.block().position() + pos - 1, QTextCursor::KeepAnchor); + cursor.insertText(ts.indentationString( + ts.columnAt(cursor.block().text(), pos - 1), + ts.columnAt(cursor.block().text(), pos), cursor.block())); + } else { + // column is behind the last position + cursor.insertText(ts.indentationString(ts.columnAt(cursor.block().text(), pos), + column, cursor.block())); + } + if (moveMode == QTextCursor::KeepAnchor) + cursor.setPosition(cursorPosition); + cursor.setPosition(cursor.block().position() + ts.positionAtColumn( + cursor.block().text(), column), moveMode); +} + +void BaseTextEditorWidgetPrivate::insertIntoBlockSelection(const QString &text) +{ + // TODO: add autocompleter support + QTextCursor cursor = q->textCursor(); + cursor.beginEditBlock(); + + if (q->overwriteMode() && m_blockSelection.lastVisualColumn() == m_blockSelection.positionColumn) + ++m_blockSelection.positionColumn; + + if (m_blockSelection.positionColumn != m_blockSelection.anchorColumn) { + removeBlockSelection(); + if (!m_inBlockSelectionMode) { + q->insertPlainText(text); + cursor.endEditBlock(); + return; + } + } + + if (text.isEmpty()) { + cursor.endEditBlock(); + return; + } + + int positionBlock = m_blockSelection.positionBlock; + int anchorBlock = m_blockSelection.anchorBlock; + int column = m_blockSelection.positionColumn; + + const QTextBlock &firstBlock = + m_document->document()->findBlockByNumber(m_blockSelection.firstBlockNumber()); + QTextBlock block = + m_document->document()->findBlockByNumber(m_blockSelection.lastBlockNumber()); + + // unify the length of all lines in a multiline text + const int selectionLineCount = m_blockSelection.lastBlockNumber() + - m_blockSelection.firstBlockNumber(); + const int textNewLineCount = text.count(QLatin1Char('\n')) ; + QStringList textLines = text.split(QLatin1Char('\n')); + const TabSettings &ts = m_document->tabSettings(); + int textLength = 0; + const QStringList::const_iterator endLine = textLines.constEnd(); + for (QStringList::const_iterator textLine = textLines.constBegin(); textLine != endLine; ++textLine) + textLength += qMax(0, ts.columnCountForText(*textLine, column) - textLength); + for (QStringList::iterator textLine = textLines.begin(); textLine != endLine; ++textLine) + textLine->append(QString(qMax(0, textLength - ts.columnCountForText(*textLine, column)), QLatin1Char(' '))); + + // insert Text + for (;;) { + // If the number of lines to be inserted equals the number of the selected lines the + // lines of the copy paste buffer are inserted in the corresponding lines of the selection. + // Otherwise the complete buffer is inserted in each of the selected lines. + cursor.setPosition(block.position()); + if (selectionLineCount == textNewLineCount) { + setCursorToColumn(cursor, column); + cursor.insertText(textLines.at(block.blockNumber() + - m_blockSelection.firstBlockNumber())); + } else { + QStringList::const_iterator textLine = textLines.constBegin(); + while (true) { + setCursorToColumn(cursor, column); + cursor.insertText(*textLine); + ++textLine; + if (textLine == endLine) + break; + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.insertText(QLatin1String("\n")); + if (qMax(anchorBlock, positionBlock) == anchorBlock) + ++anchorBlock; + else + ++positionBlock; + } + } + if (block == firstBlock) + break; + block = block.previous(); + } + cursor.endEditBlock(); + + column += textLength; + m_blockSelection.fromPostition(positionBlock, column, anchorBlock, column); + q->setTextCursor(m_blockSelection.selection(m_document.data()), true); +} + +void BaseTextEditorWidgetPrivate::removeBlockSelection() +{ + QTextCursor cursor = q->textCursor(); + if (!cursor.hasSelection() || !m_inBlockSelectionMode) + return; + + const int firstColumn = m_blockSelection.firstVisualColumn(); + const int lastColumn = m_blockSelection.lastVisualColumn(); + if (firstColumn == lastColumn) + return; + const int positionBlock = m_blockSelection.positionBlock; + const int anchorBlock = m_blockSelection.anchorBlock; + + int cursorPosition = cursor.selectionStart(); + cursor.clearSelection(); + cursor.beginEditBlock(); + + const TabSettings &ts = m_document->tabSettings(); + QTextBlock block = m_document->document()->findBlockByNumber(m_blockSelection.firstBlockNumber()); + const QTextBlock &lastBlock = m_document->document()->findBlockByNumber(m_blockSelection.lastBlockNumber()); + for (;;) { + int startOffset = 0; + const int startPos = ts.positionAtColumn(block.text(), firstColumn, &startOffset); + // removing stuff doesn't make sense if the cursor is behind the code + if (startPos < block.length() - 1 || startOffset < 0) { + cursor.setPosition(block.position()); + setCursorToColumn(cursor, firstColumn); + setCursorToColumn(cursor, lastColumn, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + if (block == lastBlock) + break; + block = block.next(); + } + + cursor.setPosition(cursorPosition); + cursor.endEditBlock(); + m_blockSelection.fromPostition(positionBlock, firstColumn, anchorBlock, firstColumn); + cursor = m_blockSelection.selection(m_document.data()); + q->setTextCursor(cursor, m_blockSelection.hasSelection()); +} + +void BaseTextEditorWidgetPrivate::enableBlockSelection(const QTextCursor &cursor) +{ + const TabSettings &ts = m_document->tabSettings(); + const QTextBlock &positionTextBlock = cursor.block(); + int positionBlock = positionTextBlock.blockNumber(); + int positionColumn = ts.columnAt(positionTextBlock.text(), + cursor.position() - positionTextBlock.position()); + + const QTextDocument *document = cursor.document(); + const QTextBlock &anchorTextBlock = document->findBlock(cursor.anchor()); + int anchorBlock = anchorTextBlock.blockNumber(); + int anchorColumn = ts.columnAt(anchorTextBlock.text(), + cursor.anchor() - anchorTextBlock.position()); + enableBlockSelection(positionBlock, anchorColumn, anchorBlock, positionColumn); +} + +void BaseTextEditorWidgetPrivate::enableBlockSelection(int positionBlock, int positionColumn, + int anchorBlock, int anchorColumn) +{ + m_blockSelection.fromPostition(positionBlock, anchorColumn, anchorBlock, positionColumn); + resetCursorFlashTimer(); + m_inBlockSelectionMode = true; + q->setTextCursor(m_blockSelection.selection(m_document.data()), true); + q->viewport()->update(); +} + +void BaseTextEditorWidgetPrivate::disableBlockSelection(bool keepSelection) +{ + m_inBlockSelectionMode = false; + m_cursorFlashTimer.stop(); + QTextCursor cursor = m_blockSelection.selection(m_document.data()); + m_blockSelection.clear(); + if (!keepSelection) + cursor.clearSelection(); + q->setTextCursor(cursor); + q->viewport()->update(); +} + +void BaseTextEditorWidgetPrivate::resetCursorFlashTimer() +{ + m_cursorVisible = true; + const int flashTime = qApp->cursorFlashTime(); + if (flashTime > 0) { + m_cursorFlashTimer.stop(); + m_cursorFlashTimer.start(flashTime / 2, q); + } +} + +void BaseTextEditorWidgetPrivate::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); +} + +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)); + } 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)); + } + + 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); +} + +static QTextLayout::FormatRange createBlockCursorCharFormatRange(int pos, const QPalette &palette) +{ + QTextLayout::FormatRange o; + o.start = pos; + o.length = 1; + o.format.setForeground(palette.base()); + o.format.setBackground(palette.text()); + return o; +} + +void BaseTextEditorWidget::paintEvent(QPaintEvent *e) +{ + // draw backgrond to the right of the wrap column before everything else + qreal lineX = 0; + QPointF offset(contentOffset()); + QRect viewportRect = viewport()->rect(); + QRect er = e->rect(); + + const FontSettings &fs = textDocument()->fontSettings(); + const QTextCharFormat &searchScopeFormat = fs.toTextCharFormat(C_SEARCH_SCOPE); + const QTextCharFormat &ifdefedOutFormat = fs.toTextCharFormat(C_DISABLED_CODE); + + if (d->m_visibleWrapColumn > 0) { + QPainter painter(viewport()); + // Don't use QFontMetricsF::averageCharWidth here, due to it returning + // a fractional size even when this is not supported by the platform. + lineX = QFontMetricsF(font()).width(QLatin1Char('x')) * d->m_visibleWrapColumn + offset.x() + 4; + if (lineX < viewportRect.width()) + painter.fillRect(QRectF(lineX, er.top(), viewportRect.width() - lineX, er.height()), + ifdefedOutFormat.background()); + } + + innerPaintEvent(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(); + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return); + + QTextBlock textCursorBlock = textCursor().block(); + + bool hasMainSelection = textCursor().hasSelection(); + bool suppressSyntaxInIfdefedOutBlock = (ifdefedOutFormat.foreground() + != palette().foreground()); + + // 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); + + bool editable = !isReadOnly(); + QTextBlock block = firstVisibleBlock(); + + QAbstractTextDocumentLayout::PaintContext context = getPaintContext(); + + int documentWidth = int(document()->size().width()); + + 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) { + for (int i = 0; i <= depth; ++i) { + const QColor &blendedColor = calcBlendColor(baseColor, i, count); + int vi = i > 0 ? d->m_highlightBlocksInfo.visualIndent.at(i-1) : 0; + QRectF oneRect = r; + oneRect.setWidth(qMax(viewport()->width(), documentWidth)); + oneRect.adjust(vi, 0, 0, 0); + if (oneRect.left() >= oneRect.right()) + continue; + if (lineX > 0 && oneRect.left() < lineX && oneRect.right() > lineX) { + QRectF otherRect = r; + otherRect.setLeft(lineX + 1); + otherRect.setRight(oneRect.right()); + oneRect.setRight(lineX - 1); + painter.fillRect(otherRect, blendedColor); + } + painter.fillRect(oneRect, blendedColor); + } + } + 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()); + } + } + } + + int blockSelectionIndex = -1; + + if (d->m_inBlockSelectionMode && context.selections.count() + && context.selections.last().cursor == textCursor()) { + blockSelectionIndex = context.selections.size()-1; + context.selections[blockSelectionIndex].format.clearBackground(); + } + + 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); + + 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 + + + { // extra pass for ifdefed out blocks + QTextBlock blockIDO = block; + QPointF offsetIDO = offset; + while (blockIDO.isValid()) { + + QRectF r = blockBoundingRect(blockIDO).translated(offsetIDO); + + if (r.bottom() >= er.top() && r.top() <= er.bottom()) { + if (TextDocumentLayout::ifdefedOut(blockIDO)) { + QRectF rr = r; + rr.setRight(viewportRect.width() - offset.x()); + if (lineX > 0) + rr.setRight(qMin(lineX, rr.right())); + painter.fillRect(rr, ifdefedOutFormat.background()); + } + } + offsetIDO.ry() += r.height(); + + if (offsetIDO.y() > viewportRect.height()) + break; + + blockIDO = blockIDO.next(); + if (!blockIDO.isVisible()) { + // invisible blocks do have zero line count + blockIDO = doc->findBlockByLineNumber(blockIDO.firstLineNumber()); + } + + } + } + + // draw wrap column after ifdefed out blocks + if (d->m_visibleWrapColumn > 0) { + if (lineX < viewportRect.width()) { + const QBrush background = ifdefedOutFormat.background(); + const QColor col = (palette().base().color().value() > 128) ? Qt::black : Qt::white; + const QPen pen = painter.pen(); + painter.setPen(blendColors(background.isOpaque() ? background.color() : palette().base().color(), + col, 32)); + painter.drawLine(QPointF(lineX, er.top()), QPointF(lineX, er.bottom())); + painter.setPen(pen); + } + } + + // possible extra pass for the block selection find scope + if (!d->m_findScopeStart.isNull() && d->m_findScopeVerticalBlockSelectionFirstColumn >= 0) { + QTextBlock blockFS = block; + QPointF offsetFS = offset; + while (blockFS.isValid()) { + + QRectF r = blockBoundingRect(blockFS).translated(offsetFS); + + if (r.bottom() >= er.top() && r.top() <= er.bottom()) { + + if (blockFS.position() >= d->m_findScopeStart.block().position() + && blockFS.position() <= d->m_findScopeEnd.block().position()) { + QTextLayout *layout = blockFS.layout(); + QString text = blockFS.text(); + const TabSettings &ts = d->m_document->tabSettings(); + qreal spacew = QFontMetricsF(font()).width(QLatin1Char(' ')); + + int offset = 0; + int relativePos = ts.positionAtColumn(text, + d->m_findScopeVerticalBlockSelectionFirstColumn, + &offset); + QTextLine line = layout->lineForTextPosition(relativePos); + qreal x = line.cursorToX(relativePos) + offset * spacew; + + int eoffset = 0; + int erelativePos = ts.positionAtColumn(text, + d->m_findScopeVerticalBlockSelectionLastColumn, + &eoffset); + QTextLine eline = layout->lineForTextPosition(erelativePos); + qreal ex = eline.cursorToX(erelativePos) + eoffset * spacew; + + QRectF rr = line.naturalTextRect(); + rr.moveTop(rr.top() + r.top()); + rr.setLeft(r.left() + x); + if (line.lineNumber() == eline.lineNumber()) + rr.setRight(r.left() + ex); + painter.fillRect(rr, searchScopeFormat.background()); + + QColor lineCol = searchScopeFormat.foreground().color(); + QPen pen = painter.pen(); + painter.setPen(lineCol); + if (blockFS == d->m_findScopeStart.block()) + painter.drawLine(rr.topLeft(), rr.topRight()); + if (blockFS == d->m_findScopeEnd.block()) + painter.drawLine(rr.bottomLeft(), rr.bottomRight()); + painter.drawLine(rr.topLeft(), rr.bottomLeft()); + painter.drawLine(rr.topRight(), rr.bottomRight()); + painter.setPen(pen); + } + } + offsetFS.ry() += r.height(); + + if (offsetFS.y() > viewportRect.height()) + break; + + blockFS = blockFS.next(); + if (!blockFS.isVisible()) { + // invisible blocks do have zero line count + blockFS = doc->findBlockByLineNumber(blockFS.firstLineNumber()); + } + + } + } + + if (!d->m_findScopeStart.isNull() && d->m_findScopeVerticalBlockSelectionFirstColumn < 0) { + + TextEditorOverlay *overlay = new TextEditorOverlay(this); + overlay->addOverlaySelection(d->m_findScopeStart.position(), + d->m_findScopeEnd.position(), + searchScopeFormat.foreground().color(), + searchScopeFormat.background().color(), + TextEditorOverlay::ExpandBegin); + overlay->setAlpha(false); + overlay->paint(&painter, e->rect()); + delete overlay; + } + + + + d->m_searchResultOverlay->fill(&painter, + fs.toTextCharFormat(C_SEARCH_RESULT).background().color(), + e->rect()); + + + while (block.isValid()) { + + QRectF r = blockBoundingRect(block).translated(offset); + + if (r.bottom() >= er.top() && r.top() <= er.bottom()) { + + QTextLayout *layout = block.layout(); + + QTextOption option = layout->textOption(); + if (suppressSyntaxInIfdefedOutBlock && TextDocumentLayout::ifdefedOut(block)) { + option.setFlags(option.flags() | QTextOption::SuppressColors); + painter.setPen(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! + + int blpos = block.position(); + int bllen = block.length(); + + QVector<QTextLayout::FormatRange> selections; + QVector<QTextLayout::FormatRange> prioritySelections; + + 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 + && selEnd >= selStart) { + QTextLayout::FormatRange o; + o.start = selStart; + o.length = selEnd - selStart; + o.format = range.format; + if (i == blockSelectionIndex) { + QString text = block.text(); + const TabSettings &ts = d->m_document->tabSettings(); + o.start = ts.positionAtColumn(text, d->m_blockSelection.firstVisualColumn()); + o.length = ts.positionAtColumn(text, d->m_blockSelection.lastVisualColumn()) - 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)) { + if (selectionVisible(block.blockNumber())) + prioritySelections.append(o); + } + 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())) { + // 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; + selections.append(o); + } +#endif + } + selections += prioritySelections; + + 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()); + QColor color = fs.toTextCharFormat(C_CURRENT_LINE).background().color(); + // set alpha, otherwise we cannot see block highlighting and find scope underneath + color.setAlpha(128); + painter.fillRect(rr, color); + } + + + QRectF blockSelectionCursorRect; + if (d->m_inBlockSelectionMode + && block.blockNumber() >= d->m_blockSelection.firstBlockNumber() + && block.blockNumber() <= d->m_blockSelection.lastBlockNumber()) { + QString text = block.text(); + const TabSettings &ts = d->m_document->tabSettings(); + const qreal spacew = QFontMetricsF(font()).width(QLatin1Char(' ')); + const int cursorw = overwriteMode() ? QFontMetrics(font()).width(QLatin1Char(' ')) + : cursorWidth(); + + int offset = 0; + int relativePos = ts.positionAtColumn(text, d->m_blockSelection.firstVisualColumn(), + &offset); + const QTextLine line = layout->lineForTextPosition(relativePos); + const qreal x = line.cursorToX(relativePos) + offset * spacew; + + int eoffset = 0; + int erelativePos = ts.positionAtColumn(text, d->m_blockSelection.lastVisualColumn(), + &eoffset); + const QTextLine eline = layout->lineForTextPosition(erelativePos); + const qreal ex = eline.cursorToX(erelativePos) + eoffset * spacew; + + QRectF rr = line.naturalTextRect(); + rr.moveTop(rr.top() + r.top()); + rr.setLeft(r.left() + x); + if (line.lineNumber() == eline.lineNumber()) + rr.setRight(r.left() + ex); + painter.fillRect(rr, palette().highlight()); + if (d->m_cursorVisible + && d->m_blockSelection.firstVisualColumn() + == d->m_blockSelection.positionColumn) { + if (overwriteMode() && offset == 0 + && relativePos < text.length() + && text.at(relativePos) != QLatin1Char('\t') + && text.at(relativePos) != QLatin1Char('\n')) { + selections.append(createBlockCursorCharFormatRange(relativePos, palette())); + } else { + blockSelectionCursorRect = rr; + blockSelectionCursorRect.setRight(rr.left() + cursorw); + } + } + for (int i = line.lineNumber() + 1; i < eline.lineNumber(); ++i) { + rr = layout->lineAt(i).naturalTextRect(); + rr.moveTop(rr.top() + r.top()); + rr.setLeft(r.left() + x); + painter.fillRect(rr, palette().highlight()); + } + + rr = eline.naturalTextRect(); + rr.moveTop(rr.top() + r.top()); + rr.setRight(r.left() + ex); + if (line.lineNumber() != eline.lineNumber()) + painter.fillRect(rr, palette().highlight()); + if (d->m_cursorVisible + && d->m_blockSelection.lastVisualColumn() + == d->m_blockSelection.positionColumn) { + if (overwriteMode() && eoffset == 0 + && erelativePos < text.length() + && text.at(erelativePos) != QLatin1Char('\t') + && text.at(erelativePos) != QLatin1Char('\n')) { + selections.append(createBlockCursorCharFormatRange(erelativePos, palette())); + } else { + blockSelectionCursorRect = rr; + blockSelectionCursorRect.setLeft(rr.right()); + blockSelectionCursorRect.setRight(rr.right() + cursorw); + } + } + } + + + bool drawCursor = ((editable || true) // we want the cursor in read-only mode + && context.cursorPosition >= blpos + && context.cursorPosition < blpos + bllen); + + bool drawCursorAsBlock = drawCursor && overwriteMode() && !d->m_inBlockSelectionMode; + + if (drawCursorAsBlock) { + int relativePos = context.cursorPosition - blpos; + bool doSelection = true; + 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')) { + doSelection = false; + qreal space = QFontMetricsF(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) + selections.append(createBlockCursorCharFormatRange(relativePos, palette())); + } + + + + 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(); + } + + if ((!HostOsInfo::isMacHost() + || d->m_blockSelection.positionColumn == d->m_blockSelection.anchorColumn) + && blockSelectionCursorRect.isValid()) + painter.fillRect(blockSelectionCursorRect, palette().text()); + } + + offset.ry() += r.height(); + + if (offset.y() > viewportRect.height()) + break; + + block = block.next(); + + if (!block.isVisible()) { + if (block.blockNumber() == d->visibleFoldedBlockNumber) { + visibleCollapsedBlock = block; + visibleCollapsedBlockOffset = offset; + } + + // invisible blocks do have zero line count + block = doc->findBlockByLineNumber(block.firstLineNumber()); + } + } + painter.setPen(context.palette.text().color()); + + 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() + + offset = contentOffset(); + block = firstVisibleBlock(); + + qreal top = blockBoundingGeometry(block).translated(offset).top(); + qreal bottom = top + blockBoundingRect(block).height(); + + 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()) { + // 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(); + } + 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); + } + 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() && replacementVisible(block.blockNumber())) { + + bool selectThis = (hasSelection + && nextBlock.position() >= selectionStart + && nextBlock.position() < selectionEnd); + painter.save(); + if (selectThis) { + painter.setBrush(palette().highlight()); + } else { + QColor rc = replacementPenColor(block.blockNumber()); + if (rc.isValid()) + painter.setPen(rc); + } + + 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); + + QString replacement = foldReplacementText(block); + QString rectReplacement = QLatin1String(" {") + replacement + QLatin1String("}; "); + + QRectF collapseRect(lineRect.right() + 12, + lineRect.top(), + fontMetrics().width(rectReplacement), + 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); + + if (TextBlockUserData *nextBlockUserData = TextDocumentLayout::testUserData(nextBlock)) { + if (nextBlockUserData->foldingStartIncluded()) + replacement.prepend(nextBlock.text().trimmed().left(1)); + } + + block = nextVisibleBlock.previous(); + if (!block.isValid()) + block = doc->lastBlock(); + + if (TextBlockUserData *blockUserData = TextDocumentLayout::testUserData(block)) { + if (blockUserData->foldingEndIncluded()) { + 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); + painter.restore(); + } + } + + block = nextVisibleBlock; + top = bottom; + bottom = top + blockBoundingRect(block).height(); + } + + 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()); + } + + // draw the overlays, but only if we do not have a find scope, otherwise the + // view becomes too noisy. + if (d->m_findScopeStart.isNull()) { + if (d->m_overlay->isVisible()) + d->m_overlay->paint(&painter, e->rect()); + + if (d->m_snippetOverlay->isVisible()) + d->m_snippetOverlay->paint(&painter, e->rect()); + + if (!d->m_refactorOverlay->isEmpty()) + d->m_refactorOverlay->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 && !d->m_inBlockSelectionMode) { + painter.setPen(cursor_pen); + cursor_layout->drawCursor(&painter, cursor_offset, cursor_cpos, cursorWidth()); + } + + if (visibleCollapsedBlock.isValid()) { + drawCollapsedBlockPopup(painter, + visibleCollapsedBlock, + visibleCollapsedBlockOffset, + er); + } +} + +int BaseTextEditorWidget::visibleFoldedBlockNumber() const +{ + return d->visibleFoldedBlockNumber; +} + +void BaseTextEditorWidget::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(); + const QTextCharFormat &ifdefedOutFormat + = textDocument()->fontSettings().toTextCharFormat(C_DISABLED_CODE); + if (ifdefedOutFormat.hasProperty(QTextFormat::BackgroundBrush)) + brush = 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 + b.setLineCount(0); // restore 0 line count for invisible block + offset.ry() += r.height(); + b = b.next(); + } +} + +QWidget *BaseTextEditorWidget::extraArea() const +{ + return d->m_extraArea; +} + +int BaseTextEditorWidget::extraAreaWidth(int *markWidthPtr) const +{ + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(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) { + QFont fnt = d->m_extraArea->font(); + // this works under the assumption that bold or italic + // can only make a font wider + const QTextCharFormat ¤tLineNumberFormat + = textDocument()->fontSettings().toTextCharFormat(C_CURRENT_LINE_NUMBER); + fnt.setBold(currentLineNumberFormat.font().bold()); + fnt.setItalic(currentLineNumberFormat.font().italic()); + const QFontMetrics linefm(fnt); + + space += linefm.width(QLatin1Char('9')) * lineNumberDigits(); + } + int markWidth = 0; + + if (d->m_marksVisible) { + markWidth += documentLayout->maxMarkWidthFactor * fm.lineSpacing() + 2; + +// if (documentLayout->doubleMarkCount) +// markWidth += fm.lineSpacing() / 3; + space += markWidth; + } else { + space += 2; + } + + if (markWidthPtr) + *markWidthPtr = markWidth; + + space += 4; + + if (d->m_codeFoldingVisible) + space += foldBoxWidth(fm); + return space; +} + +void BaseTextEditorWidgetPrivate::slotUpdateExtraAreaWidth() +{ + if (q->isLeftToRight()) + q->setViewportMargins(q->extraAreaWidth(), 0, 0, 0); + else + q->setViewportMargins(0, 0, q->extraAreaWidth(), 0); +} + +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 = StyleHelper::mergedColors(b,h, 50); + + QLinearGradient grad(rect.topLeft(), rect.topRight()); + grad.setColorAt(0, c.lighter(110)); + grad.setColorAt(1, c.lighter(130)); + QColor outline = c; + + 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(); +} + +void BaseTextEditorWidget::extraAreaPaintEvent(QPaintEvent *e) +{ + QTextDocument *doc = document(); + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return); + + int selStart = textCursor().selectionStart(); + int selEnd = textCursor().selectionEnd(); + + QPalette pal = d->m_extraArea->palette(); + pal.setCurrentColorGroup(QPalette::Active); + QPainter painter(d->m_extraArea); + const QFontMetrics fm(d->m_extraArea->font()); + int fmLineSpacing = fm.lineSpacing(); + + int markWidth = 0; + if (d->m_marksVisible) + markWidth += fm.lineSpacing(); + + const int collapseColumnWidth = d->m_codeFoldingVisible ? foldBoxWidth(fm): 0; + const int extraAreaWidth = d->m_extraArea->width() - collapseColumnWidth; + + painter.fillRect(e->rect(), pal.color(QPalette::Background)); + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + qreal top = blockBoundingGeometry(block).translated(contentOffset()).top(); + qreal bottom = top; + + while (block.isValid() && top <= e->rect().bottom()) { + + top = bottom; + const qreal height = blockBoundingRect(block).height(); + bottom = top + 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(); + } + + if (bottom < e->rect().top()) { + block = nextVisibleBlock; + blockNumber = nextVisibleBlockNumber; + continue; + } + + painter.setPen(pal.color(QPalette::Dark)); + + if (d->m_codeFoldingVisible || d->m_marksVisible) { + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, false); + + if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) { + if (d->m_marksVisible) { + int xoffset = 0; + TextMarks marks = userData->marks(); + TextMarks::const_iterator it = marks.constBegin(); + if (marks.size() > 3) { + // We want the 3 with the highest priority so iterate from the back + int count = 0; + it = marks.constEnd() - 1; + while (it != marks.constBegin()) { + if ((*it)->isVisible()) + ++count; + if (count == 3) + break; + --it; + } + } + TextMarks::const_iterator end = marks.constEnd(); + for ( ; it != end; ++it) { + TextMark *mark = *it; + if (!mark->isVisible()) + continue; + const int height = fmLineSpacing - 1; + const int width = int(.5 + height * mark->widthFactor()); + const QRect r(xoffset, top, width, height); + mark->paint(&painter, r); + xoffset += 2; + } + } + } + + if (d->m_codeFoldingVisible) { + + int extraAreaHighlightFoldBlockNumber = -1; + int extraAreaHighlightFoldEndBlockNumber = -1; + bool endIsVisible = false; + if (!d->m_highlightBlocksInfo.isEmpty()) { + extraAreaHighlightFoldBlockNumber = d->m_highlightBlocksInfo.open.last(); + extraAreaHighlightFoldEndBlockNumber = d->m_highlightBlocksInfo.close.first(); + endIsVisible = doc->findBlockByNumber(extraAreaHighlightFoldEndBlockNumber).isVisible(); + +// QTextBlock before = doc->findBlockByNumber(extraAreaHighlightCollapseBlockNumber-1); +// if (TextBlockUserData::hasCollapseAfter(before)) { +// extraAreaHighlightCollapseBlockNumber--; +// } + } + + TextBlockUserData *nextBlockUserData = TextDocumentLayout::testUserData(nextBlock); + + bool drawBox = nextBlockUserData + && TextDocumentLayout::foldingIndent(block) < nextBlockUserData->foldingIndent(); + + + + bool active = blockNumber == extraAreaHighlightFoldBlockNumber; + + bool drawStart = active; + bool drawEnd = blockNumber == extraAreaHighlightFoldEndBlockNumber || (drawStart && !endIsVisible); + bool hovered = blockNumber >= extraAreaHighlightFoldBlockNumber + && blockNumber <= extraAreaHighlightFoldEndBlockNumber; + + int boxWidth = foldBoxWidth(fm); + if (hovered) { + int itop = qRound(top); + int ibottom = qRound(bottom); + QRect box = QRect(extraAreaWidth + 1, itop, boxWidth - 2, ibottom - itop); + drawRectBox(&painter, box, drawStart, drawEnd, pal); + } + + if (drawBox) { + bool expanded = nextBlock.isVisible(); + int size = boxWidth/4; + QRect box(extraAreaWidth + size, top + size, + 2 * (size) + 1, 2 * (size) + 1); + d->drawFoldingMarker(&painter, pal, box, expanded, active, hovered); + } + } + + 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 = lineNumber(blockNumber); + bool selected = ( + (selStart < block.position() + block.length() + + && selEnd > block.position()) + || (selStart == selEnd && selStart == block.position()) + ); + if (selected) { + painter.save(); + QFont f = painter.font(); + const QTextCharFormat ¤tLineNumberFormat + = textDocument()->fontSettings().toTextCharFormat(C_CURRENT_LINE_NUMBER); + f.setBold(currentLineNumberFormat.font().bold()); + f.setItalic(currentLineNumberFormat.font().italic()); + painter.setFont(f); + painter.setPen(currentLineNumberFormat.foreground().color()); + if (currentLineNumberFormat.background() != Qt::NoBrush) + painter.fillRect(QRect(0, top, extraAreaWidth, height), currentLineNumberFormat.background().color()); + } + painter.drawText(QRectF(markWidth, top, extraAreaWidth - markWidth - 4, height), Qt::AlignRight, number); + if (selected) + painter.restore(); + } + + block = nextVisibleBlock; + blockNumber = nextVisibleBlockNumber; + } +} + +void BaseTextEditorWidgetPrivate::drawFoldingMarker(QPainter *painter, const QPalette &pal, + const QRect &rect, + bool expanded, + bool active, + bool hovered) const +{ + QStyle *s = q->style(); + if (ManhattanStyle *ms = qobject_cast<ManhattanStyle*>(s)) + s = ms->baseStyle(); + + if (!qstrcmp(s->metaObject()->className(), "OxygenStyle")) { + painter->save(); + painter->setPen(Qt::NoPen); + int size = rect.size().width(); + int sqsize = 2*(size/2); + + QColor textColor = pal.buttonText().color(); + QColor brushColor = textColor; + + textColor.setAlpha(100); + brushColor.setAlpha(100); + + QPolygon a; + if (expanded) { + // down arrow + a.setPoints(3, 0, sqsize/3, sqsize/2, sqsize - sqsize/3, sqsize, sqsize/3); + } else { + // right arrow + a.setPoints(3, sqsize - sqsize/3, sqsize/2, sqsize/2 - sqsize/3, 0, sqsize/2 - sqsize/3, sqsize); + painter->setBrush(brushColor); + } + painter->translate(0.5, 0.5); + painter->setRenderHint(QPainter::Antialiasing); + painter->translate(rect.topLeft()); + painter->setPen(textColor); + painter->setBrush(textColor); + painter->drawPolygon(a); + painter->restore(); + } else { + QStyleOptionViewItemV2 opt; + opt.rect = rect; + opt.state = QStyle::State_Active | QStyle::State_Item | QStyle::State_Children; + if (expanded) + opt.state |= QStyle::State_Open; + if (active) + opt.state |= QStyle::State_MouseOver | QStyle::State_Enabled | QStyle::State_Selected; + if (hovered) + opt.palette.setBrush(QPalette::Window, pal.highlight()); + + // QGtkStyle needs a small correction to draw the marker in the right place + if (!qstrcmp(s->metaObject()->className(), "QGtkStyle")) + opt.rect.translate(-2, 0); + else if (!qstrcmp(s->metaObject()->className(), "QMacStyle")) + opt.rect.translate(-1, 0); + + s->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, q); + } +} + +void BaseTextEditorWidgetPrivate::slotUpdateRequest(const QRect &r, int dy) +{ + if (dy) { + m_extraArea->scroll(0, dy); + } else if (r.width() > 4) { // wider than cursor width, not just cursor blinking + m_extraArea->update(0, r.y(), m_extraArea->width(), r.height()); + if (!m_searchExpr.isEmpty()) { + const int m = m_searchResultOverlay->dropShadowWidth(); + q->viewport()->update(r.adjusted(-m, -m, m, m)); + } + } + + if (r.contains(q->viewport()->rect())) + slotUpdateExtraAreaWidth(); +} + +void BaseTextEditorWidgetPrivate::saveCurrentCursorPositionForNavigation() +{ + m_lastCursorChangeWasInteresting = true; + m_tempNavigationState = q->saveState(); +} + +void BaseTextEditorWidgetPrivate::updateCurrentLineHighlight() +{ + QList<QTextEdit::ExtraSelection> extraSelections; + + if (m_highlightCurrentLine) { + QTextEdit::ExtraSelection sel; + sel.format.setBackground(q->textDocument()->fontSettings() + .toTextCharFormat(C_CURRENT_LINE).background()); + sel.format.setProperty(QTextFormat::FullWidthSelection, true); + sel.cursor = q->textCursor(); + sel.cursor.clearSelection(); + extraSelections.append(sel); + } + + q->setExtraSelections(BaseTextEditorWidget::CurrentLineSelection, extraSelections); + + // the extra area shows information for the entire current block, not just the currentline. + // This is why we must force a bigger update region. + int cursorBlockNumber = q->textCursor().blockNumber(); + if (cursorBlockNumber != m_cursorBlockNumber) { + QPointF offset = q->contentOffset(); + QTextBlock block = q->document()->findBlockByNumber(m_cursorBlockNumber); + if (block.isValid()) + m_extraArea->update(q->blockBoundingGeometry(block).translated(offset).toAlignedRect()); + block = q->document()->findBlockByNumber(cursorBlockNumber); + if (block.isValid() && block.isVisible()) + m_extraArea->update(q->blockBoundingGeometry(block).translated(offset).toAlignedRect()); + m_cursorBlockNumber = cursorBlockNumber; + } +} + +void BaseTextEditorWidget::slotCursorPositionChanged() +{ +#if 0 + qDebug() << "block" << textCursor().blockNumber()+1 + << "brace depth:" << BaseTextDocumentLayout::braceDepth(textCursor().block()) + << "indent:" << BaseTextDocumentLayout::userData(textCursor().block())->foldingIndent(); +#endif + if (!d->m_contentsChanged && d->m_lastCursorChangeWasInteresting) { + if (EditorManager::currentEditor() && EditorManager::currentEditor()->widget() == this) + EditorManager::addCurrentPositionToNavigationHistory(d->m_tempNavigationState); + d->m_lastCursorChangeWasInteresting = false; + } else if (d->m_contentsChanged) { + d->saveCurrentCursorPositionForNavigation(); + } + + d->updateHighlights(); +} + +void BaseTextEditorWidgetPrivate::updateHighlights() +{ + if (m_parenthesesMatchingEnabled && q->hasFocus()) { + // Delay update when no matching is displayed yet, to avoid flicker + if (q->extraSelections(BaseTextEditorWidget::ParenthesesMatchingSelection).isEmpty() + && m_animator == 0) { + m_parenthesesMatchingTimer.start(50); + } else { + // when we uncheck "highlight matching parentheses" + // we need clear current selection before viewport update + // otherwise we get sticky highlighted parentheses + if (!m_displaySettings.m_highlightMatchingParentheses) + q->setExtraSelections(BaseTextEditorWidget::ParenthesesMatchingSelection, QList<QTextEdit::ExtraSelection>()); + + // use 0-timer, not direct call, to give the syntax highlighter a chance + // to update the parentheses information + m_parenthesesMatchingTimer.start(0); + } + } + + updateCurrentLineHighlight(); + + if (m_displaySettings.m_highlightBlocks) { + QTextCursor cursor = q->textCursor(); + extraAreaHighlightFoldedBlockNumber = cursor.blockNumber(); + m_highlightBlocksTimer.start(100); + } +} + +void BaseTextEditorWidgetPrivate::slotUpdateBlockNotify(const QTextBlock &block) +{ + static bool blockRecursion = false; + if (blockRecursion) + return; + blockRecursion = true; + if (m_overlay->isVisible()) { + /* an overlay might draw outside the block bounderies, force + complete viewport update */ + q->viewport()->update(); + } else { + 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 fold + box which now is invalid.*/ + emit q->requestBlockUpdate(block.previous()); + } + if (!m_findScopeStart.isNull()) { + if (block.position() < m_findScopeEnd.position() + && block.position() + block.length() >= m_findScopeStart.position()) { + QTextBlock b = block.document()->findBlock(m_findScopeStart.position()); + do { + emit q->requestBlockUpdate(b); + b = b.next(); + } while (b.isValid() && b.position() < m_findScopeEnd.position()); + } + } + } + blockRecursion = false; +} + +void BaseTextEditorWidget::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->foldedBlockTimer.timerId()) { + d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber; + d->suggestedVisibleFoldedBlockNumber = -1; + d->foldedBlockTimer.stop(); + viewport()->update(); + } else if (e->timerId() == d->m_cursorFlashTimer.timerId()) { + d->m_cursorVisible = !d->m_cursorVisible; + viewport()->update(); + } + QPlainTextEdit::timerEvent(e); +} + + +void BaseTextEditorWidgetPrivate::clearVisibleFoldedBlock() +{ + if (suggestedVisibleFoldedBlockNumber) { + suggestedVisibleFoldedBlockNumber = -1; + foldedBlockTimer.stop(); + } + if (visibleFoldedBlockNumber >= 0) { + visibleFoldedBlockNumber = -1; + q->viewport()->update(); + } +} + +void BaseTextEditorWidget::mouseMoveEvent(QMouseEvent *e) +{ + d->updateLink(e); + + if (e->buttons() == Qt::NoButton) { + const QTextBlock collapsedBlock = d->foldedBlockAt(e->pos()); + const int blockNumber = collapsedBlock.next().blockNumber(); + if (blockNumber < 0) { + d->clearVisibleFoldedBlock(); + } else if (blockNumber != d->visibleFoldedBlockNumber) { + d->suggestedVisibleFoldedBlockNumber = blockNumber; + d->foldedBlockTimer.start(40, this); + } + + const RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(e->pos()); + + // Update the mouse cursor + if ((collapsedBlock.isValid() || refactorMarker.isValid()) && !d->m_mouseOnFoldedMarker) { + d->m_mouseOnFoldedMarker = true; + viewport()->setCursor(Qt::PointingHandCursor); + } else if (!collapsedBlock.isValid() && !refactorMarker.isValid() && d->m_mouseOnFoldedMarker) { + d->m_mouseOnFoldedMarker = false; + viewport()->setCursor(Qt::IBeamCursor); + } + } else { + QPlainTextEdit::mouseMoveEvent(e); + + if (e->modifiers() & Qt::AltModifier) { + if (!d->m_inBlockSelectionMode) { + if (textCursor().hasSelection()) { + d->enableBlockSelection(textCursor()); + } else { + const QTextCursor &cursor = cursorForPosition(e->pos()); + int column = d->m_document->tabSettings().columnAt( + cursor.block().text(), cursor.positionInBlock()); + if (cursor.positionInBlock() == cursor.block().length()-1) + column += (e->pos().x() - cursorRect().center().x()) / QFontMetricsF(font()).width(QLatin1Char(' ')); + int block = cursor.blockNumber(); + if (block == blockCount() - 1) + block += (e->pos().y() - cursorRect().center().y()) / QFontMetricsF(font()).lineSpacing(); + d->enableBlockSelection(block, column, block, column); + } + } else { + const QTextCursor &cursor = textCursor(); + + // get visual column + int column = d->m_document->tabSettings().columnAt( + cursor.block().text(), cursor.positionInBlock()); + if (cursor.positionInBlock() == cursor.block().length()-1) + column += (e->pos().x() - cursorRect().center().x()) / QFontMetricsF(font()).width(QLatin1Char(' ')); + + d->m_blockSelection.positionBlock = cursor.blockNumber(); + d->m_blockSelection.positionColumn = column; + + setTextCursor(d->m_blockSelection.selection(d->m_document.data()), true); + viewport()->update(); + } + } else if (d->m_inBlockSelectionMode) { + d->disableBlockSelection(); + } + } + if (viewport()->cursor().shape() == Qt::BlankCursor) + viewport()->setCursor(Qt::IBeamCursor); +} + +static bool handleForwardBackwardMouseButtons(QMouseEvent *e) +{ + if (e->button() == Qt::XButton1) { + EditorManager::goBackInNavigationHistory(); + return true; + } + if (e->button() == Qt::XButton2) { + EditorManager::goForwardInNavigationHistory(); + return true; + } + + return false; +} + +void BaseTextEditorWidget::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + if (e->modifiers() == Qt::AltModifier) { + const QTextCursor &cursor = cursorForPosition(e->pos()); + int column = d->m_document->tabSettings().columnAt( + cursor.block().text(), cursor.positionInBlock()); + if (cursor.positionInBlock() == cursor.block().length()-1) + column += (e->pos().x() - cursorRect(cursor).center().x()) / QFontMetricsF(font()).width(QLatin1Char(' ')); + int block = cursor.blockNumber(); + if (block == blockCount() - 1) + block += (e->pos().y() - cursorRect(cursor).center().y()) / QFontMetricsF(font()).lineSpacing(); + if (d->m_inBlockSelectionMode) { + d->m_blockSelection.positionBlock = block; + d->m_blockSelection.positionColumn = column; + + setTextCursor(d->m_blockSelection.selection(d->m_document.data()), true); + viewport()->update(); + } else { + d->enableBlockSelection(block, column, block, column); + } + } else { + if (d->m_inBlockSelectionMode) + d->disableBlockSelection(false); // just in case, otherwise we might get strange drag and drop + + QTextBlock foldedBlock = d->foldedBlockAt(e->pos()); + if (foldedBlock.isValid()) { + d->toggleBlockVisible(foldedBlock); + viewport()->setCursor(Qt::IBeamCursor); + } + + RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(e->pos()); + if (refactorMarker.isValid()) { + onRefactorMarkerClicked(refactorMarker); + } else { + d->updateLink(e); + + if (d->m_currentLink.hasValidLinkText()) + d->m_linkPressed = true; + } + } + } else if (e->button() == Qt::RightButton) { + int eventCursorPosition = cursorForPosition(e->pos()).position(); + if (eventCursorPosition < textCursor().selectionStart() + || eventCursorPosition > textCursor().selectionEnd()) { + setTextCursor(cursorForPosition(e->pos())); + } + } + + if (HostOsInfo::isLinuxHost() && handleForwardBackwardMouseButtons(e)) + return; + + QPlainTextEdit::mousePressEvent(e); +} + +void BaseTextEditorWidget::mouseReleaseEvent(QMouseEvent *e) +{ + if (mouseNavigationEnabled() + && d->m_linkPressed + && e->modifiers() & Qt::ControlModifier + && !(e->modifiers() & Qt::ShiftModifier) + && e->button() == Qt::LeftButton + ) { + + EditorManager::addCurrentPositionToNavigationHistory(); + bool inNextSplit = ((e->modifiers() & Qt::AltModifier) && !alwaysOpenLinksInNextSplit()) + || (alwaysOpenLinksInNextSplit() && !(e->modifiers() & Qt::AltModifier)); + if (openLink(findLinkAt(cursorForPosition(e->pos())), inNextSplit)) { + d->clearLink(); + return; + } + } + + if (!HostOsInfo::isLinuxHost() && handleForwardBackwardMouseButtons(e)) + return; + + QPlainTextEdit::mouseReleaseEvent(e); +} + +void BaseTextEditorWidget::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + QTextCursor cursor = textCursor(); + const int position = cursor.position(); + if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false, true)) { + if (position - cursor.position() == 1 && selectBlockUp()) + return; + } + } + + QPlainTextEdit::mouseDoubleClickEvent(e); +} + +void BaseTextEditorWidget::leaveEvent(QEvent *e) +{ + // Clear link emulation when the mouse leaves the editor + d->clearLink(); + QPlainTextEdit::leaveEvent(e); +} + +void BaseTextEditorWidget::keyReleaseEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Control) { + d->clearLink(); + } else if (e->key() == Qt::Key_Shift + && d->m_behaviorSettings.m_constrainHoverTooltips + && ToolTip::isVisible()) { + ToolTip::hide(); + } else if (e->key() == Qt::Key_Alt + && d->m_maybeFakeTooltipEvent) { + d->m_maybeFakeTooltipEvent = false; + d->processTooltipRequest(textCursor()); + } + + QPlainTextEdit::keyReleaseEvent(e); +} + +void BaseTextEditorWidget::dragEnterEvent(QDragEnterEvent *e) +{ + // If the drag event contains URLs, we don't want to insert them as text + if (e->mimeData()->hasUrls()) { + e->ignore(); + return; + } + + QPlainTextEdit::dragEnterEvent(e); +} + +static void appendMenuActionsFromContext(QMenu *menu, Id menuContextId) +{ + ActionContainer *mcontext = ActionManager::actionContainer(menuContextId); + QMenu *contextMenu = mcontext->menu(); + + foreach (QAction *action, contextMenu->actions()) + menu->addAction(action); +} + +void BaseTextEditorWidget::showDefaultContextMenu(QContextMenuEvent *e, Id menuContextId) +{ + QMenu menu; + appendMenuActionsFromContext(&menu, menuContextId); + appendStandardContextMenuActions(&menu); + menu.exec(e->globalPos()); +} + +void BaseTextEditorWidget::extraAreaLeaveEvent(QEvent *) +{ + // fake missing mouse move event from Qt + QMouseEvent me(QEvent::MouseMove, QPoint(-1, -1), Qt::NoButton, 0, 0); + extraAreaMouseEvent(&me); +} + +void BaseTextEditorWidget::extraAreaContextMenuEvent(QContextMenuEvent *e) +{ + if (d->m_marksVisible) { + QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y())); + QMenu * contextMenu = new QMenu(this); + emit markContextMenuRequested(cursor.blockNumber() + 1, contextMenu); + if (!contextMenu->isEmpty()) + contextMenu->exec(e->globalPos()); + delete contextMenu; + e->accept(); + } +} + +void BaseTextEditorWidget::updateFoldingHighlight(const QPoint &pos) +{ + if (!d->m_codeFoldingVisible) + return; + + QTextCursor cursor = cursorForPosition(QPoint(0, pos.y())); + + // Update which folder marker is highlighted + const int highlightBlockNumber = d->extraAreaHighlightFoldedBlockNumber; + d->extraAreaHighlightFoldedBlockNumber = -1; + + if (pos.x() > extraArea()->width() - foldBoxWidth(fontMetrics())) { + d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber(); + } else if (d->m_displaySettings.m_highlightBlocks) { + QTextCursor cursor = textCursor(); + d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber(); + } + + if (highlightBlockNumber != d->extraAreaHighlightFoldedBlockNumber) + d->m_highlightBlocksTimer.start(d->m_highlightBlocksInfo.isEmpty() ? 120 : 0); +} + +void BaseTextEditorWidget::extraAreaMouseEvent(QMouseEvent *e) +{ + QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y())); + + int markWidth; + extraAreaWidth(&markWidth); + const bool inMarkArea = e->pos().x() <= markWidth && e->pos().x() >= 0; + + if (d->m_codeFoldingVisible + && e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking + updateFoldingHighlight(e->pos()); + } + + // Set whether the mouse cursor is a hand or normal arrow + if (e->type() == QEvent::MouseMove) { + if (inMarkArea) { + //Find line by cursor position + int line = cursor.blockNumber() + 1; + emit markTooltipRequested(mapToGlobal(e->pos()), line); + } + + if (e->buttons() & Qt::LeftButton && !d->m_markDragStart.isNull()) { + int dist = (e->pos() - d->m_markDragStart).manhattanLength(); + if (dist > QApplication::startDragDistance()) + d->m_markDragging = true; + } + + if (d->m_markDragging) + d->m_extraArea->setCursor(inMarkArea ? Qt::DragMoveCursor : Qt::ForbiddenCursor); + else if (inMarkArea != (d->m_extraArea->cursor().shape() == Qt::PointingHandCursor)) + d->m_extraArea->setCursor(inMarkArea ? Qt::PointingHandCursor : Qt::ArrowCursor); + } + + if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { + if (e->button() == Qt::LeftButton) { + int boxWidth = foldBoxWidth(fontMetrics()); + if (d->m_codeFoldingVisible && e->pos().x() > extraArea()->width() - boxWidth) { + if (!cursor.block().next().isVisible()) { + d->toggleBlockVisible(cursor.block()); + d->moveCursorVisible(false); + } else if (d->foldBox().contains(e->pos())) { + cursor.setPosition( + document()->findBlockByNumber(d->m_highlightBlocksInfo.open.last()).position() + ); + QTextBlock c = cursor.block(); + d->toggleBlockVisible(c); + d->moveCursorVisible(false); + } + } else if (d->m_lineNumbersVisible && !inMarkArea) { + 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(); + d->m_markDragging = false; + QTextBlock block = cursor.document()->findBlockByNumber(d->extraAreaToggleMarkBlockNumber); + if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) { + TextMarks marks = data->marks(); + for (int i = marks.size(); --i >= 0; ) { + TextMark *mark = marks.at(i); + if (mark->isDraggable()) { + d->m_markDragStart = e->pos(); + break; + } + } + } + } + } + } 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; + const bool sameLine = cursor.blockNumber() == n; + const bool wasDragging = d->m_markDragging; + d->m_markDragging = false; + d->m_markDragStart = QPoint(); + QTextBlock block = cursor.document()->findBlockByNumber(n); + if (TextBlockUserData *data = static_cast<TextBlockUserData *>(block.userData())) { + TextMarks marks = data->marks(); + for (int i = marks.size(); --i >= 0; ) { + TextMark *mark = marks.at(i); + if (sameLine) { + if (mark->isClickable()) { + mark->clicked(); + return; + } + } else { + if (wasDragging && mark->isDraggable()) { + if (inMarkArea) { + mark->dragToLine(cursor.blockNumber() + 1); + d->m_extraArea->setCursor(Qt::PointingHandCursor); + } else { + d->m_extraArea->setCursor(Qt::ArrowCursor); + } + return; + } + } + } + } + int line = n + 1; + BaseTextEditor::MarkRequestKind kind; + if (QApplication::keyboardModifiers() & Qt::ShiftModifier) + kind = BaseTextEditor::BookmarkRequest; + else + kind = BaseTextEditor::BreakpointRequest; + + emit markRequested(line, kind); + } + } +} + +void BaseTextEditorWidget::ensureCursorVisible() +{ + QTextBlock block = textCursor().block(); + if (!block.isVisible()) { + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(document()->documentLayout()); + QTC_ASSERT(documentLayout, return); + + // Open all parent folds of current line. + int indent = TextDocumentLayout::foldingIndent(block); + block = block.previous(); + while (block.isValid()) { + const int indent2 = TextDocumentLayout::foldingIndent(block); + if (TextDocumentLayout::canFold(block) && indent2 < indent) { + TextDocumentLayout::doFoldOrUnfold(block, /* unfold = */ true); + if (block.isVisible()) + break; + indent = indent2; + } + block = block.previous(); + } + + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); + } + QPlainTextEdit::ensureCursorVisible(); +} + +void BaseTextEditorWidgetPrivate::toggleBlockVisible(const QTextBlock &block) +{ + auto documentLayout = qobject_cast<TextDocumentLayout*>(q->document()->documentLayout()); + QTC_ASSERT(documentLayout, return); + + TextDocumentLayout::doFoldOrUnfold(block, TextDocumentLayout::isFolded(block)); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); +} + +void BaseTextEditorWidget::setLanguageSettingsId(Id settingsId) +{ + d->m_tabSettingsId = settingsId; +} + +Id BaseTextEditorWidget::languageSettingsId() const +{ + return d->m_tabSettingsId; +} + +void BaseTextEditorWidget::setCodeStyle(ICodeStylePreferences *preferences) +{ + textDocument()->indenter()->setCodeStylePreferences(preferences); + if (d->m_codeStylePreferences) { + disconnect(d->m_codeStylePreferences, SIGNAL(currentTabSettingsChanged(TextEditor::TabSettings)), + d->m_document.data(), SLOT(setTabSettings(TextEditor::TabSettings))); + disconnect(d->m_codeStylePreferences, SIGNAL(currentValueChanged(QVariant)), + this, SLOT(slotCodeStyleSettingsChanged(QVariant))); + } + d->m_codeStylePreferences = preferences; + if (d->m_codeStylePreferences) { + connect(d->m_codeStylePreferences, SIGNAL(currentTabSettingsChanged(TextEditor::TabSettings)), + d->m_document.data(), SLOT(setTabSettings(TextEditor::TabSettings))); + connect(d->m_codeStylePreferences, SIGNAL(currentValueChanged(QVariant)), + this, SLOT(slotCodeStyleSettingsChanged(QVariant))); + d->m_document->setTabSettings(d->m_codeStylePreferences->currentTabSettings()); + slotCodeStyleSettingsChanged(d->m_codeStylePreferences->currentValue()); + } +} + +void BaseTextEditorWidget::slotCodeStyleSettingsChanged(const QVariant &) +{ + +} + +const DisplaySettings &BaseTextEditorWidget::displaySettings() const +{ + return d->m_displaySettings; +} + +const MarginSettings &BaseTextEditorWidget::marginSettings() const +{ + return d->m_marginSettings; +} + +void BaseTextEditorWidgetPrivate::handleHomeKey(bool anchor) +{ + QTextCursor cursor = q->textCursor(); + QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; + + if (anchor) + mode = QTextCursor::KeepAnchor; + + const int initpos = cursor.position(); + int pos = cursor.block().position(); + QChar character = q->document()->characterAt(pos); + const QLatin1Char tab = QLatin1Char('\t'); + + while (character == tab || character.category() == QChar::Separator_Space) { + ++pos; + if (pos == initpos) + break; + character = q->document()->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); + q->setTextCursor(cursor); +} + +void BaseTextEditorWidgetPrivate::handleBackspaceKey() +{ + QTextCursor cursor = q->textCursor(); + QTC_ASSERT(!cursor.hasSelection(), return); + + const int pos = cursor.position(); + if (!pos) + return; + + bool cursorWithinSnippet = false; + if (m_snippetOverlay->isVisible()) { + QTextCursor snippetCursor = cursor; + snippetCursor.movePosition(QTextCursor::Left); + cursorWithinSnippet = snippetCheckCursor(snippetCursor); + } + + const TextEditor::TabSettings &tabSettings = m_document->tabSettings(); + const TextEditor::TypingSettings &typingSettings = m_document->typingSettings(); + + if (typingSettings.m_autoIndent && m_autoCompleter->autoBackspace(cursor)) + return; + + bool handled = false; + if (typingSettings.m_smartBackspaceBehavior == TypingSettings::BackspaceNeverIndents) { + if (cursorWithinSnippet) + cursor.beginEditBlock(); + cursor.deletePreviousChar(); + handled = true; + } else if (typingSettings.m_smartBackspaceBehavior == TypingSettings::BackspaceFollowsPreviousIndents) { + QTextBlock currentBlock = cursor.block(); + int positionInBlock = pos - currentBlock.position(); + const QString blockText = currentBlock.text(); + if (cursor.atBlockStart() || tabSettings.firstNonSpace(blockText) < positionInBlock) { + if (cursorWithinSnippet) + cursor.beginEditBlock(); + cursor.deletePreviousChar(); + handled = true; + } else { + if (cursorWithinSnippet) { + m_snippetOverlay->mangle(); + m_snippetOverlay->clear(); + cursorWithinSnippet = false; + } + 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) { + cursor.beginEditBlock(); + cursor.setPosition(currentBlock.position(), QTextCursor::KeepAnchor); + cursor.insertText(tabSettings.indentationString(previousNonEmptyBlockText)); + cursor.endEditBlock(); + handled = true; + break; + } + } + } + } else if (typingSettings.m_smartBackspaceBehavior == TypingSettings::BackspaceUnindents) { + const QChar c = q->document()->characterAt(pos - 1); + if (!(c == QLatin1Char(' ') || c == QLatin1Char('\t'))) { + if (cursorWithinSnippet) + cursor.beginEditBlock(); + cursor.deletePreviousChar(); + } else { + if (cursorWithinSnippet) { + m_snippetOverlay->mangle(); + m_snippetOverlay->clear(); + cursorWithinSnippet = false; + } + q->unindent(); + } + handled = true; + } + + if (!handled) { + if (cursorWithinSnippet) + cursor.beginEditBlock(); + cursor.deletePreviousChar(); + } + + if (cursorWithinSnippet) { + cursor.endEditBlock(); + m_snippetOverlay->updateEquivalentSelections(cursor); + } + + q->setTextCursor(cursor); +} + +void BaseTextEditorWidget::wheelEvent(QWheelEvent *e) +{ + d->clearVisibleFoldedBlock(); + if (scrollWheelZoomingEnabled() && e->modifiers() & Qt::ControlModifier) { + const int delta = e->delta(); + if (delta < 0) + zoomOut(); + else if (delta > 0) + zoomIn(); + return; + } + QPlainTextEdit::wheelEvent(e); +} + +void BaseTextEditorWidget::zoomIn() +{ + d->clearVisibleFoldedBlock(); + emit requestFontZoom(10); +} + +void BaseTextEditorWidget::zoomOut() +{ + d->clearVisibleFoldedBlock(); + emit requestFontZoom(-10); +} + +void BaseTextEditorWidget::zoomReset() +{ + emit requestZoomReset(); +} + +BaseTextEditorWidget::Link BaseTextEditorWidget::findLinkAt(const QTextCursor &, bool, bool) +{ + return Link(); +} + +bool BaseTextEditorWidget::openLink(const Link &link, bool inNextSplit) +{ + if (!link.hasValidTarget()) + return false; + + if (inNextSplit) { + EditorManager::gotoOtherSplit(); + } else if (textDocument()->filePath() == link.targetFileName) { + EditorManager::addCurrentPositionToNavigationHistory(); + gotoLine(link.targetLine, link.targetColumn); + setFocus(); + return true; + } + + return EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn); +} + +void BaseTextEditorWidgetPrivate::updateLink(QMouseEvent *e) +{ + bool linkFound = false; + + if (q->mouseNavigationEnabled() && e->modifiers() & Qt::ControlModifier) { + // Link emulation behaviour for 'go to definition' + const QTextCursor cursor = q->cursorForPosition(e->pos()); + + // Check that the mouse was actually on the text somewhere + bool onText = q->cursorRect(cursor).right() >= e->x(); + if (!onText) { + QTextCursor nextPos = cursor; + nextPos.movePosition(QTextCursor::Right); + onText = q->cursorRect(nextPos).right() >= e->x(); + } + + const BaseTextEditorWidget::Link link = q->findLinkAt(cursor, false); + + if (onText && link.hasValidLinkText()) { + showLink(link); + linkFound = true; + } + } + + if (!linkFound) + clearLink(); +} + +void BaseTextEditorWidgetPrivate::showLink(const BaseTextEditorWidget::Link &link) +{ + if (m_currentLink == link) + return; + + QTextEdit::ExtraSelection sel; + sel.cursor = q->textCursor(); + sel.cursor.setPosition(link.linkTextStart); + sel.cursor.setPosition(link.linkTextEnd, QTextCursor::KeepAnchor); + sel.format = q->textDocument()->fontSettings().toTextCharFormat(C_LINK); + sel.format.setFontUnderline(true); + q->setExtraSelections(BaseTextEditorWidget::OtherSelection, QList<QTextEdit::ExtraSelection>() << sel); + q->viewport()->setCursor(Qt::PointingHandCursor); + m_currentLink = link; + m_linkPressed = false; +} + +void BaseTextEditorWidgetPrivate::clearLink() +{ + if (!m_currentLink.hasValidLinkText()) + return; + + q->setExtraSelections(BaseTextEditorWidget::OtherSelection, QList<QTextEdit::ExtraSelection>()); + q->viewport()->setCursor(Qt::IBeamCursor); + m_currentLink = BaseTextEditorWidget::Link(); + m_linkPressed = false; +} + +void BaseTextEditorWidgetPrivate::highlightSearchResultsSlot(const QString &txt, FindFlags findFlags) +{ + if (m_searchExpr.pattern() == txt) + return; + m_searchExpr.setPattern(txt); + m_searchExpr.setPatternSyntax((findFlags & FindRegularExpression) ? + QRegExp::RegExp : QRegExp::FixedString); + m_searchExpr.setCaseSensitivity((findFlags & FindCaseSensitively) ? + Qt::CaseSensitive : Qt::CaseInsensitive); + m_findFlags = findFlags; + + m_delayedUpdateTimer.start(50); +} + +int BaseTextEditorWidget::verticalBlockSelectionFirstColumn() const +{ + return d->m_inBlockSelectionMode ? d->m_blockSelection.firstVisualColumn() : -1; +} + +int BaseTextEditorWidget::verticalBlockSelectionLastColumn() const +{ + return d->m_inBlockSelectionMode ? d->m_blockSelection.lastVisualColumn() : -1; +} + +QRegion BaseTextEditorWidget::translatedLineRegion(int lineStart, int lineEnd) const +{ + QRegion region; + for (int i = lineStart ; i <= lineEnd; i++) { + QTextBlock block = document()->findBlockByNumber(i); + QPoint topLeft = blockBoundingGeometry(block).translated(contentOffset()).topLeft().toPoint(); + + if (block.isValid()) { + QTextLayout *layout = block.layout(); + + for (int i = 0; i < layout->lineCount();i++) { + QTextLine line = layout->lineAt(i); + region += line.naturalTextRect().translated(topLeft).toRect(); + } + } + } + return region; +} + +void BaseTextEditorWidgetPrivate::setFindScope(const QTextCursor &start, const QTextCursor &end, + int verticalBlockSelectionFirstColumn, + int verticalBlockSelectionLastColumn) +{ + if (start != m_findScopeStart + || end != m_findScopeEnd + || verticalBlockSelectionFirstColumn != m_findScopeVerticalBlockSelectionFirstColumn + || verticalBlockSelectionLastColumn != m_findScopeVerticalBlockSelectionLastColumn) { + m_findScopeStart = start; + m_findScopeEnd = end; + m_findScopeVerticalBlockSelectionFirstColumn = verticalBlockSelectionFirstColumn; + m_findScopeVerticalBlockSelectionLastColumn = verticalBlockSelectionLastColumn; + q->viewport()->update(); + } +} + +void BaseTextEditorWidgetPrivate::_q_animateUpdate(int position, QPointF lastPos, QRectF rect) +{ + QTextCursor cursor = q->textCursor(); + cursor.setPosition(position); + q->viewport()->update(QRectF(q->cursorRect(cursor).topLeft() + rect.topLeft(), rect.size()).toAlignedRect()); + if (!lastPos.isNull()) + q->viewport()->update(QRectF(lastPos + rect.topLeft(), rect.size()).toAlignedRect()); +} + + +BaseTextEditorAnimator::BaseTextEditorAnimator(QObject *parent) + : QObject(parent), m_timeline(256) +{ + m_value = 0; + m_timeline.setCurveShape(QTimeLine::SineCurve); + connect(&m_timeline, &QTimeLine::valueChanged, this, &BaseTextEditorAnimator::step); + connect(&m_timeline, &QTimeLine::finished, this, &QObject::deleteLater); + m_timeline.start(); +} + +void BaseTextEditorAnimator::setData(const QFont &f, const QPalette &pal, const QString &text) +{ + m_font = f; + m_palette = pal; + m_text = text; + QFontMetrics fm(m_font); + m_size = QSizeF(fm.width(m_text), fm.height()); +} + +void BaseTextEditorAnimator::draw(QPainter *p, const QPointF &pos) +{ + m_lastDrawPos = pos; + p->setPen(m_palette.text().color()); + QFont f = m_font; + f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2)); + QFontMetrics fm(f); + int width = fm.width(m_text); + QRectF r((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height()); + r.translate(pos); + p->fillRect(r, m_palette.base()); + p->setFont(f); + p->drawText(r, m_text); +} + +bool BaseTextEditorAnimator::isRunning() const +{ + return m_timeline.state() == QTimeLine::Running; +} + +QRectF BaseTextEditorAnimator::rect() const +{ + QFont f = m_font; + f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2)); + QFontMetrics fm(f); + int width = fm.width(m_text); + return QRectF((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height()); +} + +void BaseTextEditorAnimator::step(qreal v) +{ + QRectF before = rect(); + m_value = v; + QRectF after = rect(); + emit updateRequest(m_position, m_lastDrawPos, before.united(after)); +} + +void BaseTextEditorAnimator::finish() +{ + m_timeline.stop(); + step(0); + deleteLater(); +} + +void BaseTextEditorWidgetPrivate::_q_matchParentheses() +{ + if (q->isReadOnly() + || !(m_displaySettings.m_highlightMatchingParentheses + || m_displaySettings.m_animateMatchingParentheses)) + return; + + QTextCursor backwardMatch = q->textCursor(); + QTextCursor forwardMatch = q->textCursor(); + if (q->overwriteMode()) + backwardMatch.movePosition(QTextCursor::Right); + const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch); + const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch); + + QList<QTextEdit::ExtraSelection> extraSelections; + + if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch) { + q->setExtraSelections(BaseTextEditorWidget::ParenthesesMatchingSelection, extraSelections); // clear + return; + } + + const QTextCharFormat &matchFormat + = q->textDocument()->fontSettings().toTextCharFormat(C_PARENTHESES); + int animatePosition = -1; + if (backwardMatch.hasSelection()) { + QTextEdit::ExtraSelection sel; + if (backwardMatchType == TextBlockUserData::Mismatch) { + sel.cursor = backwardMatch; + sel.format = m_mismatchFormat; + extraSelections.append(sel); + } else { + + sel.cursor = backwardMatch; + sel.format = matchFormat; + + sel.cursor.setPosition(backwardMatch.selectionStart()); + sel.cursor.setPosition(sel.cursor.position() + 1, QTextCursor::KeepAnchor); + extraSelections.append(sel); + + if (m_displaySettings.m_animateMatchingParentheses && sel.cursor.block().isVisible()) + animatePosition = backwardMatch.selectionStart(); + + sel.cursor.setPosition(backwardMatch.selectionEnd()); + sel.cursor.setPosition(sel.cursor.position() - 1, QTextCursor::KeepAnchor); + extraSelections.append(sel); + } + } + + if (forwardMatch.hasSelection()) { + QTextEdit::ExtraSelection sel; + if (forwardMatchType == TextBlockUserData::Mismatch) { + sel.cursor = forwardMatch; + sel.format = m_mismatchFormat; + extraSelections.append(sel); + } else { + + sel.cursor = forwardMatch; + sel.format = matchFormat; + + sel.cursor.setPosition(forwardMatch.selectionStart()); + sel.cursor.setPosition(sel.cursor.position() + 1, QTextCursor::KeepAnchor); + extraSelections.append(sel); + + sel.cursor.setPosition(forwardMatch.selectionEnd()); + sel.cursor.setPosition(sel.cursor.position() - 1, QTextCursor::KeepAnchor); + extraSelections.append(sel); + + if (m_displaySettings.m_animateMatchingParentheses && sel.cursor.block().isVisible()) + animatePosition = forwardMatch.selectionEnd() - 1; + } + } + + + if (animatePosition >= 0) { + foreach (const QTextEdit::ExtraSelection &sel, q->extraSelections(BaseTextEditorWidget::ParenthesesMatchingSelection)) { + if (sel.cursor.selectionStart() == animatePosition + || sel.cursor.selectionEnd() - 1 == animatePosition) { + animatePosition = -1; + break; + } + } + } + + if (animatePosition >= 0) { + if (m_animator) + m_animator->finish(); // one animation is enough + m_animator = new BaseTextEditorAnimator(this); + m_animator->setPosition(animatePosition); + QPalette pal; + pal.setBrush(QPalette::Text, matchFormat.foreground()); + pal.setBrush(QPalette::Base, matchFormat.background()); + m_animator->setData(q->font(), pal, q->document()->characterAt(m_animator->position())); + connect(m_animator.data(), &BaseTextEditorAnimator::updateRequest, + this, &BaseTextEditorWidgetPrivate::_q_animateUpdate); + } + if (m_displaySettings.m_highlightMatchingParentheses) + q->setExtraSelections(BaseTextEditorWidget::ParenthesesMatchingSelection, extraSelections); +} + +void BaseTextEditorWidgetPrivate::_q_highlightBlocks() +{ + BaseTextEditorPrivateHighlightBlocks highlightBlocksInfo; + + QTextBlock block; + if (extraAreaHighlightFoldedBlockNumber >= 0) { + block = q->document()->findBlockByNumber(extraAreaHighlightFoldedBlockNumber); + if (block.isValid() + && block.next().isValid() + && TextDocumentLayout::foldingIndent(block.next()) + > TextDocumentLayout::foldingIndent(block)) + block = block.next(); + } + + QTextBlock closeBlock = block; + while (block.isValid()) { + int foldingIndent = TextDocumentLayout::foldingIndent(block); + + while (block.previous().isValid() && TextDocumentLayout::foldingIndent(block) >= foldingIndent) + block = block.previous(); + int nextIndent = TextDocumentLayout::foldingIndent(block); + if (nextIndent == foldingIndent) + break; + highlightBlocksInfo.open.prepend(block.blockNumber()); + while (closeBlock.next().isValid() + && TextDocumentLayout::foldingIndent(closeBlock.next()) >= foldingIndent ) + closeBlock = closeBlock.next(); + highlightBlocksInfo.close.append(closeBlock.blockNumber()); + int indent = qMin(visualIndent(block), visualIndent(closeBlock)); + highlightBlocksInfo.visualIndent.prepend(indent); + } + +#if 0 + if (block.isValid()) { + QTextCursor cursor(block); + if (extraAreaHighlightCollapseColumn >= 0) + cursor.setPosition(cursor.position() + qMin(extraAreaHighlightCollapseColumn, + block.length()-1)); + QTextCursor closeCursor; + bool firstRun = true; + while (TextBlockUserData::findPreviousBlockOpenParenthesis(&cursor, firstRun)) { + firstRun = false; + highlightBlocksInfo.open.prepend(cursor.blockNumber()); + int visualIndent = visualIndent(cursor.block()); + if (closeCursor.isNull()) + closeCursor = cursor; + if (TextBlockUserData::findNextBlockClosingParenthesis(&closeCursor)) { + highlightBlocksInfo.close.append(closeCursor.blockNumber()); + visualIndent = qMin(visualIndent, visualIndent(closeCursor.block())); + } + highlightBlocksInfo.visualIndent.prepend(visualIndent); + } + } +#endif + if (m_highlightBlocksInfo != highlightBlocksInfo) { + m_highlightBlocksInfo = highlightBlocksInfo; + q->viewport()->update(); + m_extraArea->update(); + } +} + +void BaseTextEditorWidget::changeEvent(QEvent *e) +{ + QPlainTextEdit::changeEvent(e); + if (e->type() == QEvent::ApplicationFontChange + || e->type() == QEvent::FontChange) { + if (d->m_extraArea) { + QFont f = d->m_extraArea->font(); + f.setPointSizeF(font().pointSizeF()); + d->m_extraArea->setFont(f); + d->slotUpdateExtraAreaWidth(); + d->m_extraArea->update(); + } + } +} + +void BaseTextEditorWidget::focusInEvent(QFocusEvent *e) +{ + QPlainTextEdit::focusInEvent(e); + d->updateHighlights(); +} + +void BaseTextEditorWidget::focusOutEvent(QFocusEvent *e) +{ + QPlainTextEdit::focusOutEvent(e); + if (viewport()->cursor().shape() == Qt::BlankCursor) + viewport()->setCursor(Qt::IBeamCursor); +} + + +void BaseTextEditorWidgetPrivate::maybeSelectLine() +{ + QTextCursor cursor = q->textCursor(); + if (!cursor.hasSelection()) { + const QTextBlock &block = cursor.block(); + if (block.next().isValid()) { + cursor.setPosition(block.position()); + cursor.setPosition(block.next().position(), QTextCursor::KeepAnchor); + } else { + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + } + q->setTextCursor(cursor); + } +} + +// shift+del +void BaseTextEditorWidget::cutLine() +{ + d->maybeSelectLine(); + cut(); +} + +// ctrl+ins +void BaseTextEditorWidget::copyLine() +{ + QTextCursor prevCursor = textCursor(); + d->maybeSelectLine(); + copy(); + if (!prevCursor.hasSelection()) + prevCursor.movePosition(QTextCursor::StartOfBlock); + setTextCursor(prevCursor, d->m_inBlockSelectionMode); +} + +void BaseTextEditorWidget::deleteLine() +{ + d->maybeSelectLine(); + textCursor().removeSelectedText(); +} + +void BaseTextEditorWidget::deleteEndOfWord() +{ + moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + textCursor().removeSelectedText(); + setTextCursor(textCursor()); +} + +void BaseTextEditorWidget::deleteEndOfWordCamelCase() +{ + QTextCursor c = textCursor(); + d->camelCaseRight(c, QTextCursor::KeepAnchor); + c.removeSelectedText(); + setTextCursor(c); +} + +void BaseTextEditorWidget::deleteStartOfWord() +{ + moveCursor(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + textCursor().removeSelectedText(); + setTextCursor(textCursor()); +} + +void BaseTextEditorWidget::deleteStartOfWordCamelCase() +{ + QTextCursor c = textCursor(); + d->camelCaseLeft(c, QTextCursor::KeepAnchor); + c.removeSelectedText(); + setTextCursor(c); +} + +void BaseTextEditorWidget::setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections) +{ + if (selections.isEmpty() && d->m_extraSelections[kind].isEmpty()) + return; + d->m_extraSelections[kind] = selections; + + if (kind == CodeSemanticsSelection) { + d->m_overlay->clear(); + foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) { + d->m_overlay->addOverlaySelection(selection.cursor, + selection.format.background().color(), + selection.format.background().color(), + TextEditorOverlay::LockSize); + } + d->m_overlay->setVisible(!d->m_overlay->isEmpty()); + } else if (kind == SnippetPlaceholderSelection) { + d->m_snippetOverlay->mangle(); + d->m_snippetOverlay->clear(); + foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) { + d->m_snippetOverlay->addOverlaySelection(selection.cursor, + selection.format.background().color(), + selection.format.background().color(), + TextEditorOverlay::ExpandBegin); + } + d->m_snippetOverlay->mapEquivalentSelections(); + d->m_snippetOverlay->setVisible(!d->m_snippetOverlay->isEmpty()); + } else { + QList<QTextEdit::ExtraSelection> all; + for (int i = 0; i < NExtraSelectionKinds; ++i) { + if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection) + continue; + all += d->m_extraSelections[i]; + } + QPlainTextEdit::setExtraSelections(all); + } +} + +QList<QTextEdit::ExtraSelection> BaseTextEditorWidget::extraSelections(ExtraSelectionKind kind) const +{ + return d->m_extraSelections[kind]; +} + +QString BaseTextEditorWidget::extraSelectionTooltip(int pos) const +{ + QList<QTextEdit::ExtraSelection> all; + for (int i = 0; i < NExtraSelectionKinds; ++i) { + const QList<QTextEdit::ExtraSelection> &sel = d->m_extraSelections[i]; + for (int j = 0; j < sel.size(); ++j) { + const QTextEdit::ExtraSelection &s = sel.at(j); + if (s.cursor.selectionStart() <= pos + && s.cursor.selectionEnd() >= pos + && !s.format.toolTip().isEmpty()) + return s.format.toolTip(); + } + } + return QString(); +} + +// the blocks list must be sorted +void BaseTextEditorWidget::setIfdefedOutBlocks(const QList<BlockRange> &blocks) +{ + QTextDocument *doc = document(); + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return); + + bool needUpdate = false; + + QTextBlock block = doc->firstBlock(); + + int rangeNumber = 0; + int braceDepthDelta = 0; + while (block.isValid()) { + bool cleared = false; + bool set = false; + if (rangeNumber < blocks.size()) { + const BlockRange &range = blocks.at(rangeNumber); + if (block.position() >= range.first() + && ((block.position() + block.length() - 1) <= range.last() || !range.last())) + set = TextDocumentLayout::setIfdefedOut(block); + else + cleared = TextDocumentLayout::clearIfdefedOut(block); + if (block.contains(range.last())) + ++rangeNumber; + } else { + cleared = TextDocumentLayout::clearIfdefedOut(block); + } + + if (cleared || set) { + needUpdate = true; + int delta = TextDocumentLayout::braceDepthDelta(block); + if (cleared) + braceDepthDelta += delta; + else if (set) + braceDepthDelta -= delta; + } + + if (braceDepthDelta) { + TextDocumentLayout::changeBraceDepth(block,braceDepthDelta); + TextDocumentLayout::changeFoldingIndent(block, braceDepthDelta); // ### C++ only, refactor! + } + + block = block.next(); + } + + if (needUpdate) + documentLayout->requestUpdate(); +} + +void BaseTextEditorWidget::format() +{ + QTextCursor cursor = textCursor(); + cursor.beginEditBlock(); + d->m_document->autoIndent(cursor); + cursor.endEditBlock(); +} + +void BaseTextEditorWidget::rewrapParagraph() +{ + const int paragraphWidth = marginSettings().m_marginColumn; + const QRegExp anyLettersOrNumbers = QRegExp(QLatin1String("\\w")); + const int tabSize = d->m_document->tabSettings().m_tabSize; + + QTextCursor cursor = textCursor(); + cursor.beginEditBlock(); + + // Find start of paragraph. + + while (cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor)) { + QTextBlock block = cursor.block(); + QString text = block.text(); + + // If this block is empty, move marker back to previous and terminate. + if (!text.contains(anyLettersOrNumbers)) { + cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor); + break; + } + } + + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor); + + // Find indent level of current block. + + int indentLevel = 0; + QString text = cursor.block().text(); + + for (int i = 0; i < text.length(); i++) { + const QChar ch = text.at(i); + + if (ch == QLatin1Char(' ')) + indentLevel++; + else if (ch == QLatin1Char('\t')) + indentLevel += tabSize - (indentLevel % tabSize); + else + break; + } + + // If there is a common prefix, it should be kept and expanded to all lines. + // this allows nice reflowing of doxygen style comments. + QTextCursor nextBlock = cursor; + QString commonPrefix; + + if (nextBlock.movePosition(QTextCursor::NextBlock)) + { + QString nText = nextBlock.block().text(); + int maxLength = qMin(text.length(), nText.length()); + + for (int i = 0; i < maxLength; ++i) { + const QChar ch = text.at(i); + + if (ch != nText[i] || ch.isLetterOrNumber()) + break; + commonPrefix.append(ch); + } + } + + + // Find end of paragraph. + while (cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor)) { + QString text = cursor.block().text(); + + if (!text.contains(anyLettersOrNumbers)) + break; + } + + + QString selectedText = cursor.selectedText(); + + // Preserve initial indent level.or common prefix. + QString spacing; + + if (commonPrefix.isEmpty()) { + spacing = d->m_document->tabSettings().indentationString( + 0, indentLevel, textCursor().block()); + } else { + spacing = commonPrefix; + indentLevel = commonPrefix.length(); + } + + int currentLength = indentLevel; + QString result; + result.append(spacing); + + // Remove existing instances of any common prefix from paragraph to + // reflow. + selectedText.remove(0, commonPrefix.length()); + commonPrefix.prepend(QChar::ParagraphSeparator); + selectedText.replace(commonPrefix, QLatin1String("\n")); + + // remove any repeated spaces, trim lines to PARAGRAPH_WIDTH width and + // keep the same indentation level as first line in paragraph. + QString currentWord; + + for (int i = 0; i < selectedText.length(); ++i) { + QChar ch = selectedText.at(i); + if (ch.isSpace()) { + if (!currentWord.isEmpty()) { + currentLength += currentWord.length() + 1; + + if (currentLength > paragraphWidth) { + currentLength = currentWord.length() + 1 + indentLevel; + result.chop(1); // remove trailing space + result.append(QChar::ParagraphSeparator); + result.append(spacing); + } + + result.append(currentWord); + result.append(QLatin1Char(' ')); + currentWord.clear(); + } + + continue; + } + + currentWord.append(ch); + } + result.chop(1); + result.append(QChar::ParagraphSeparator); + + cursor.insertText(result); + cursor.endEditBlock(); +} + +void BaseTextEditorWidget::unCommentSelection() +{ + Utils::unCommentSelection(this, d->m_commentDefinition); +} + +void BaseTextEditorWidget::showEvent(QShowEvent* e) +{ + triggerPendingUpdates(); + QPlainTextEdit::showEvent(e); +} + + +void BaseTextEditorWidgetPrivate::applyFontSettingsDelayed() +{ + m_fontSettingsNeedsApply = true; + if (q->isVisible()) + q->triggerPendingUpdates(); +} + +void BaseTextEditorWidget::triggerPendingUpdates() +{ + if (d->m_fontSettingsNeedsApply) + applyFontSettings(); + textDocument()->triggerPendingUpdates(); +} + +void BaseTextEditorWidget::applyFontSettings() +{ + d->m_fontSettingsNeedsApply = false; + const FontSettings &fs = textDocument()->fontSettings(); + const QTextCharFormat textFormat = fs.toTextCharFormat(C_TEXT); + const QTextCharFormat selectionFormat = fs.toTextCharFormat(C_SELECTION); + const QTextCharFormat lineNumberFormat = fs.toTextCharFormat(C_LINE_NUMBER); + QFont font(textFormat.font()); + + const QColor foreground = textFormat.foreground().color(); + const QColor background = textFormat.background().color(); + QPalette p = palette(); + p.setColor(QPalette::Text, foreground); + p.setColor(QPalette::Foreground, foreground); + p.setColor(QPalette::Base, background); + p.setColor(QPalette::Highlight, (selectionFormat.background().style() != Qt::NoBrush) ? + selectionFormat.background().color() : + QApplication::palette().color(QPalette::Highlight)); + + p.setBrush(QPalette::HighlightedText, selectionFormat.foreground()); + + p.setBrush(QPalette::Inactive, QPalette::Highlight, p.highlight()); + p.setBrush(QPalette::Inactive, QPalette::HighlightedText, p.highlightedText()); + setPalette(p); + setFont(font); + d->updateTabStops(); // update tab stops, they depend on the font + + // Line numbers + QPalette ep; + ep.setColor(QPalette::Dark, lineNumberFormat.foreground().color()); + ep.setColor(QPalette::Background, lineNumberFormat.background().style() != Qt::NoBrush ? + lineNumberFormat.background().color() : background); + d->m_extraArea->setPalette(ep); + + d->slotUpdateExtraAreaWidth(); // Adjust to new font width + d->updateHighlights(); +} + +void BaseTextEditorWidget::setDisplaySettings(const DisplaySettings &ds) +{ + setLineWrapMode(ds.m_textWrapping ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap); + setLineNumbersVisible(ds.m_displayLineNumbers); + setHighlightCurrentLine(ds.m_highlightCurrentLine); + setRevisionsVisible(ds.m_markTextChanges); + setCenterOnScroll(ds.m_centerCursorOnScroll); + d->m_fileEncodingLabelAction->setVisible(ds.m_displayFileEncoding); + + if (d->m_displaySettings.m_visualizeWhitespace != ds.m_visualizeWhitespace) { + if (SyntaxHighlighter *highlighter = textDocument()->syntaxHighlighter()) + highlighter->rehighlight(); + QTextOption option = document()->defaultTextOption(); + if (ds.m_visualizeWhitespace) + option.setFlags(option.flags() | QTextOption::ShowTabsAndSpaces); + else + option.setFlags(option.flags() & ~QTextOption::ShowTabsAndSpaces); + option.setFlags(option.flags() | QTextOption::AddSpaceForLineAndParagraphSeparators); + document()->setDefaultTextOption(option); + } + + d->m_displaySettings = ds; + if (!ds.m_highlightBlocks) { + d->extraAreaHighlightFoldedBlockNumber = -1; + d->m_highlightBlocksInfo = BaseTextEditorPrivateHighlightBlocks(); + } + + d->updateCodeFoldingVisible(); + d->updateHighlights(); + viewport()->update(); + extraArea()->update(); +} + +void BaseTextEditorWidget::setMarginSettings(const MarginSettings &ms) +{ + setVisibleWrapColumn(ms.m_showMargin ? ms.m_marginColumn : 0); + d->m_marginSettings = ms; + + viewport()->update(); + extraArea()->update(); +} + +void BaseTextEditorWidget::setBehaviorSettings(const TextEditor::BehaviorSettings &bs) +{ + d->m_behaviorSettings = bs; +} + +void BaseTextEditorWidget::setTypingSettings(const TypingSettings &typingSettings) +{ + d->m_document->setTypingSettings(typingSettings); +} + +void BaseTextEditorWidget::setStorageSettings(const StorageSettings &storageSettings) +{ + d->m_document->setStorageSettings(storageSettings); +} + +void BaseTextEditorWidget::setCompletionSettings(const TextEditor::CompletionSettings &completionSettings) +{ + d->m_autoCompleter->setAutoParenthesesEnabled(completionSettings.m_autoInsertBrackets); + d->m_autoCompleter->setSurroundWithEnabled(completionSettings.m_autoInsertBrackets + && completionSettings.m_surroundingAutoBrackets); +} + +void BaseTextEditorWidget::setExtraEncodingSettings(const ExtraEncodingSettings &extraEncodingSettings) +{ + d->m_document->setExtraEncodingSettings(extraEncodingSettings); +} + +void BaseTextEditorWidget::fold() +{ + QTextDocument *doc = document(); + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return); + QTextBlock block = textCursor().block(); + if (!(TextDocumentLayout::canFold(block) && block.next().isVisible())) { + // find the closest previous block which can fold + int indent = TextDocumentLayout::foldingIndent(block); + while (block.isValid() && (TextDocumentLayout::foldingIndent(block) >= indent || !block.isVisible())) + block = block.previous(); + } + if (block.isValid()) { + TextDocumentLayout::doFoldOrUnfold(block, false); + d->moveCursorVisible(); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); + } +} + +void BaseTextEditorWidget::unfold() +{ + QTextDocument *doc = document(); + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return); + QTextBlock block = textCursor().block(); + while (block.isValid() && !block.isVisible()) + block = block.previous(); + TextDocumentLayout::doFoldOrUnfold(block, true); + d->moveCursorVisible(); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); +} + +void BaseTextEditorWidget::unfoldAll() +{ + QTextDocument *doc = document(); + TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); + QTC_ASSERT(documentLayout, return); + + QTextBlock block = doc->firstBlock(); + bool makeVisible = true; + while (block.isValid()) { + if (block.isVisible() && TextDocumentLayout::canFold(block) && block.next().isVisible()) { + makeVisible = false; + break; + } + block = block.next(); + } + + block = doc->firstBlock(); + + while (block.isValid()) { + if (TextDocumentLayout::canFold(block)) + TextDocumentLayout::doFoldOrUnfold(block, makeVisible); + block = block.next(); + } + + d->moveCursorVisible(); + documentLayout->requestUpdate(); + documentLayout->emitDocumentSizeChanged(); + centerCursor(); +} + +void BaseTextEditorWidget::setReadOnly(bool b) +{ + QPlainTextEdit::setReadOnly(b); + emit readOnlyChanged(); + if (b) + setTextInteractionFlags(textInteractionFlags() | Qt::TextSelectableByKeyboard); +} + +void BaseTextEditorWidget::cut() +{ + if (d->m_inBlockSelectionMode) { + copy(); + d->removeBlockSelection(); + return; + } + QPlainTextEdit::cut(); + d->collectToCircularClipboard(); +} + +void BaseTextEditorWidget::selectAll() +{ + d->disableBlockSelection(); + QPlainTextEdit::selectAll(); +} + +void BaseTextEditorWidget::copy() +{ + if (!textCursor().hasSelection() || (d->m_inBlockSelectionMode + && d->m_blockSelection.anchorColumn == d->m_blockSelection.positionColumn)) { + return; + } + + QPlainTextEdit::copy(); + d->collectToCircularClipboard(); +} + +void BaseTextEditorWidget::paste() +{ + QPlainTextEdit::paste(); +} + +void BaseTextEditorWidgetPrivate::collectToCircularClipboard() +{ + const QMimeData *mimeData = QApplication::clipboard()->mimeData(); + if (!mimeData) + return; + CircularClipboard *circularClipBoard = CircularClipboard::instance(); + circularClipBoard->collect(BaseTextEditorWidget::duplicateMimeData(mimeData)); + // We want the latest copied content to be the first one to appear on circular paste. + circularClipBoard->toLastCollect(); +} + +void BaseTextEditorWidget::circularPaste() +{ + CircularClipboard *circularClipBoard = CircularClipboard::instance(); + if (const QMimeData *clipboardData = QApplication::clipboard()->mimeData()) { + circularClipBoard->collect(BaseTextEditorWidget::duplicateMimeData(clipboardData)); + circularClipBoard->toLastCollect(); + } + + if (circularClipBoard->size() > 1) + return invokeAssist(QuickFix, d->m_clipboardAssistProvider.data()); + + if (const QMimeData *mimeData = circularClipBoard->next().data()) { + QApplication::clipboard()->setMimeData(BaseTextEditorWidget::duplicateMimeData(mimeData)); + paste(); + } +} + +void BaseTextEditorWidget::switchUtf8bom() +{ + textDocument()->switchUtf8Bom(); +} + +QMimeData *BaseTextEditorWidget::createMimeDataFromSelection() const +{ + if (d->m_inBlockSelectionMode) { + QMimeData *mimeData = new QMimeData; + mimeData->setText(d->copyBlockSelection()); + return mimeData; + } else if (textCursor().hasSelection()) { + QTextCursor cursor = textCursor(); + QMimeData *mimeData = new QMimeData; + + QString text = plainTextFromSelection(cursor); + mimeData->setText(text); + + // Copy the selected text as HTML + { + // Create a new document from the selected text document fragment + QTextDocument *tempDocument = new QTextDocument; + QTextCursor tempCursor(tempDocument); + tempCursor.insertFragment(cursor.selection()); + + // Apply the additional formats set by the syntax highlighter + QTextBlock start = document()->findBlock(cursor.selectionStart()); + QTextBlock last = document()->findBlock(cursor.selectionEnd()); + QTextBlock end = last.next(); + + const int selectionStart = cursor.selectionStart(); + const int endOfDocument = tempDocument->characterCount() - 1; + int removedCount = 0; + for (QTextBlock current = start; current.isValid() && current != end; current = current.next()) { + if (selectionVisible(current.blockNumber())) { + const QTextLayout *layout = current.layout(); + foreach (const QTextLayout::FormatRange &range, layout->additionalFormats()) { + const int startPosition = current.position() + range.start - selectionStart - removedCount; + const int endPosition = startPosition + range.length; + if (endPosition <= 0 || startPosition >= endOfDocument - removedCount) + continue; + tempCursor.setPosition(qMax(startPosition, 0)); + tempCursor.setPosition(qMin(endPosition, endOfDocument - removedCount), QTextCursor::KeepAnchor); + tempCursor.setCharFormat(range.format); + } + } else { + const int startPosition = current.position() - start.position() - removedCount; + int endPosition = startPosition + current.text().count(); + if (current != last) + endPosition++; + removedCount += endPosition - startPosition; + tempCursor.setPosition(startPosition); + tempCursor.setPosition(endPosition, QTextCursor::KeepAnchor); + tempCursor.deleteChar(); + } + } + + // Reset the user states since they are not interesting + for (QTextBlock block = tempDocument->begin(); block.isValid(); block = block.next()) + block.setUserState(-1); + + // Make sure the text appears pre-formatted + tempCursor.setPosition(0); + tempCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + QTextBlockFormat blockFormat = tempCursor.blockFormat(); + blockFormat.setNonBreakableLines(true); + tempCursor.setBlockFormat(blockFormat); + + mimeData->setHtml(tempCursor.selection().toHtml()); + delete tempDocument; + } + + /* + Try to figure out whether we are copying an entire block, and store the complete block + including indentation in the qtcreator.blocktext mimetype. + */ + QTextCursor selstart = cursor; + selstart.setPosition(cursor.selectionStart()); + QTextCursor selend = cursor; + selend.setPosition(cursor.selectionEnd()); + + bool startOk = TabSettings::cursorIsAtBeginningOfLine(selstart); + bool multipleBlocks = (selend.block() != selstart.block()); + + if (startOk && multipleBlocks) { + selstart.movePosition(QTextCursor::StartOfBlock); + if (TabSettings::cursorIsAtBeginningOfLine(selend)) + selend.movePosition(QTextCursor::StartOfBlock); + cursor.setPosition(selstart.position()); + cursor.setPosition(selend.position(), QTextCursor::KeepAnchor); + text = plainTextFromSelection(cursor); + mimeData->setData(QLatin1String(kTextBlockMimeType), text.toUtf8()); + } + return mimeData; + } + return 0; +} + +bool BaseTextEditorWidget::canInsertFromMimeData(const QMimeData *source) const +{ + return QPlainTextEdit::canInsertFromMimeData(source); +} + +void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source) +{ + if (isReadOnly()) + return; + + QString text = source->text(); + if (text.isEmpty()) + return; + + if (d->m_codeAssistant.hasContext()) + d->m_codeAssistant.destroyContext(); + + if (d->m_inBlockSelectionMode) { + d->insertIntoBlockSelection(text); + return; + } + + if (d->m_snippetOverlay->isVisible() && (text.contains(QLatin1Char('\n')) + || text.contains(QLatin1Char('\t')))) { + d->m_snippetOverlay->hide(); + d->m_snippetOverlay->mangle(); + d->m_snippetOverlay->clear(); + } + + const TypingSettings &tps = d->m_document->typingSettings(); + QTextCursor cursor = textCursor(); + if (!tps.m_autoIndent) { + cursor.beginEditBlock(); + cursor.insertText(text); + cursor.endEditBlock(); + setTextCursor(cursor); + return; + } + + cursor.beginEditBlock(); + cursor.removeSelectedText(); + + bool insertAtBeginningOfLine = TabSettings::cursorIsAtBeginningOfLine(cursor); + + if (insertAtBeginningOfLine + && source->hasFormat(QLatin1String(kTextBlockMimeType))) { + text = QString::fromUtf8(source->data(QLatin1String(kTextBlockMimeType))); + if (text.isEmpty()) + return; + } + + int reindentBlockStart = cursor.blockNumber() + (insertAtBeginningOfLine?0:1); + + bool hasFinalNewline = (text.endsWith(QLatin1Char('\n')) + || text.endsWith(QChar::ParagraphSeparator) + || text.endsWith(QLatin1Char('\r'))); + + if (insertAtBeginningOfLine + && hasFinalNewline) // since we'll add a final newline, preserve current line's indentation + cursor.setPosition(cursor.block().position()); + + int cursorPosition = cursor.position(); + cursor.insertText(text); + + int reindentBlockEnd = cursor.blockNumber() - (hasFinalNewline?1:0); + + if (reindentBlockStart < reindentBlockEnd + || (reindentBlockStart == reindentBlockEnd + && (!insertAtBeginningOfLine || hasFinalNewline))) { + if (insertAtBeginningOfLine && !hasFinalNewline) { + QTextCursor unnecessaryWhitespace = cursor; + unnecessaryWhitespace.setPosition(cursorPosition); + unnecessaryWhitespace.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + unnecessaryWhitespace.removeSelectedText(); + } + QTextCursor c = cursor; + c.setPosition(cursor.document()->findBlockByNumber(reindentBlockStart).position()); + c.setPosition(cursor.document()->findBlockByNumber(reindentBlockEnd).position(), + QTextCursor::KeepAnchor); + d->m_document->autoReindent(c); + } + + cursor.endEditBlock(); + setTextCursor(cursor); +} + +QMimeData *BaseTextEditorWidget::duplicateMimeData(const QMimeData *source) +{ + Q_ASSERT(source); + + QMimeData *mimeData = new QMimeData; + mimeData->setText(source->text()); + mimeData->setHtml(source->html()); + if (source->hasFormat(QLatin1String(kTextBlockMimeType))) { + mimeData->setData(QLatin1String(kTextBlockMimeType), + source->data(QLatin1String(kTextBlockMimeType))); + } + + return mimeData; +} + +QString BaseTextEditorWidget::lineNumber(int blockNumber) const +{ + return QString::number(blockNumber + 1); +} + +int BaseTextEditorWidget::lineNumberDigits() const +{ + int digits = 2; + int max = qMax(1, blockCount()); + while (max >= 100) { + max /= 10; + ++digits; + } + return digits; +} + +bool BaseTextEditorWidget::selectionVisible(int blockNumber) const +{ + Q_UNUSED(blockNumber); + return true; +} + +bool BaseTextEditorWidget::replacementVisible(int blockNumber) const +{ + Q_UNUSED(blockNumber) + return true; +} + +QColor BaseTextEditorWidget::replacementPenColor(int blockNumber) const +{ + Q_UNUSED(blockNumber) + return QColor(); +} + +void BaseTextEditorWidget::setupFallBackEditor(Id id) +{ + BaseTextDocumentPtr doc(new TextDocument(id)); + doc->setFontSettings(TextEditorSettings::fontSettings()); + setTextDocument(doc); +} + +void BaseTextEditorWidget::appendStandardContextMenuActions(QMenu *menu) +{ + menu->addSeparator(); + + QAction *a = ActionManager::command(Core::Constants::CUT)->action(); + if (a && a->isEnabled()) + menu->addAction(a); + a = ActionManager::command(Core::Constants::COPY)->action(); + if (a && a->isEnabled()) + menu->addAction(a); + a = ActionManager::command(Core::Constants::PASTE)->action(); + if (a && a->isEnabled()) + menu->addAction(a); + a = ActionManager::command(Constants::CIRCULAR_PASTE)->action(); + if (a && a->isEnabled()) + menu->addAction(a); + + TextDocument *doc = textDocument(); + if (doc->codec()->name() == QByteArray("UTF-8") && doc->supportsUtf8Bom()) { + a = ActionManager::command(Constants::SWITCH_UTF8BOM)->action(); + if (a && a->isEnabled()) { + a->setText(doc->format().hasUtf8Bom ? tr("Delete UTF-8 BOM on Save") + : tr("Add UTF-8 BOM on Save")); + menu->addSeparator(); + menu->addAction(a); + } + } +} + + +BaseTextEditor::BaseTextEditor() + : d(new BaseTextEditorPrivate) +{ + addContext(TextEditor::Constants::C_TEXTEDITOR); +} + +BaseTextEditor::~BaseTextEditor() +{ + delete m_widget; + delete d; +} + +TextDocument *BaseTextEditor::textDocument() const +{ + BaseTextEditorWidget *widget = editorWidget(); + QTC_CHECK(!widget->d->m_document.isNull()); + return widget->d->m_document.data(); +} + +void BaseTextEditor::addContext(Id id) +{ + m_context.add(id); +} + +IDocument *BaseTextEditor::document() +{ + return textDocument(); +} + +QWidget *BaseTextEditor::toolBar() +{ + return editorWidget()->d->m_toolBar; +} + +void BaseTextEditorWidget::insertExtraToolBarWidget(BaseTextEditorWidget::Side side, + QWidget *widget) +{ + if (widget->sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag) { + if (d->m_stretchWidget) + d->m_stretchWidget->deleteLater(); + d->m_stretchWidget = 0; + } + + if (side == Right) + d->m_toolBar->insertWidget(d->m_cursorPositionLabelAction, widget); + else + d->m_toolBar->insertWidget(d->m_toolBar->actions().first(), widget); +} + +int BaseTextEditor::currentLine() const +{ + return editorWidget()->textCursor().blockNumber() + 1; +} + +int BaseTextEditor::currentColumn() const +{ + QTextCursor cursor = editorWidget()->textCursor(); + return cursor.position() - cursor.block().position() + 1; +} + +void BaseTextEditor::gotoLine(int line, int column, bool centerLine) +{ + editorWidget()->gotoLine(line, column, centerLine); +} + +int BaseTextEditor::columnCount() const +{ + return editorWidget()->columnCount(); +} + +int BaseTextEditor::rowCount() const +{ + return editorWidget()->rowCount(); +} + +int BaseTextEditor::position(BaseTextEditor::PositionOperation posOp, int at) const +{ + return editorWidget()->position(posOp, at); +} + +void BaseTextEditor::convertPosition(int pos, int *line, int *column) const +{ + editorWidget()->convertPosition(pos, line, column); +} + +QRect BaseTextEditor::cursorRect(int pos) const +{ + return editorWidget()->cursorRect(pos); +} + +QString BaseTextEditor::selectedText() const +{ + return editorWidget()->selectedText(); +} + +void BaseTextEditor::remove(int length) +{ + editorWidget()->remove(length); +} + +void BaseTextEditorWidget::remove(int length) +{ + QTextCursor tc = textCursor(); + tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor); + tc.removeSelectedText(); +} + +void BaseTextEditor::insert(const QString &string) +{ + editorWidget()->insertPlainText(string); +} + +void BaseTextEditor::replace(int length, const QString &string) +{ + editorWidget()->replace(length, string); +} + +void BaseTextEditorWidget::replace(int length, const QString &string) +{ + QTextCursor tc = textCursor(); + tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor); + tc.insertText(string); +} + +void BaseTextEditor::setCursorPosition(int pos) +{ + editorWidget()->setCursorPosition(pos); +} + +void BaseTextEditorWidget::setCursorPosition(int pos) +{ + setBlockSelection(false); + QTextCursor tc = textCursor(); + tc.setPosition(pos); + setTextCursor(tc); +} + +void BaseTextEditor::select(int toPos) +{ + editorWidget()->setBlockSelection(false); + QTextCursor tc = editorWidget()->textCursor(); + tc.setPosition(toPos, QTextCursor::KeepAnchor); + editorWidget()->setTextCursor(tc); +} + +CompletionAssistProvider *BaseTextEditorWidget::completionAssistProvider() const +{ + return d->m_completionAssistProvider; +} + +void BaseTextEditorWidgetPrivate::updateCursorPosition() +{ + const QTextCursor cursor = q->textCursor(); + const QTextBlock block = cursor.block(); + const int line = block.blockNumber() + 1; + const int column = cursor.position() - block.position(); + m_cursorPositionLabel->show(); + m_cursorPositionLabel->setText(tr("Line: %1, Col: %2").arg(line) + .arg(q->textDocument()->tabSettings().columnAt(block.text(), + column)+1), + tr("Line: 9999, Col: 999")); + q->clearContentsHelpId(); + + if (!block.isVisible()) + q->ensureCursorVisible(); +} + +QString BaseTextEditor::contextHelpId() const +{ + if (m_contextHelpId.isEmpty()) + emit const_cast<BaseTextEditor*>(this)->contextHelpIdRequested(const_cast<BaseTextEditor*>(this), + editorWidget()->textCursor().position()); + return m_contextHelpId; +} + +RefactorMarkers BaseTextEditorWidget::refactorMarkers() const +{ + return d->m_refactorOverlay->markers(); +} + +void BaseTextEditorWidget::setRefactorMarkers(const RefactorMarkers &markers) +{ + foreach (const RefactorMarker &marker, d->m_refactorOverlay->markers()) + requestBlockUpdate(marker.cursor.block()); + d->m_refactorOverlay->setMarkers(markers); + foreach (const RefactorMarker &marker, markers) + requestBlockUpdate(marker.cursor.block()); +} + +void BaseTextEditorWidget::doFoo() +{ +#ifdef DO_FOO + qDebug() << Q_FUNC_INFO; + RefactorMarkers markers = d->m_refactorOverlay->markers(); + RefactorMarker marker; + marker.tooltip = "Hello World"; + marker.cursor = textCursor(); + markers += marker; + setRefactorMarkers(markers); +#endif +} + +BaseTextBlockSelection::BaseTextBlockSelection(const BaseTextBlockSelection &other) +{ + positionBlock = other.positionBlock; + positionColumn = other.positionColumn; + anchorBlock = other.anchorBlock; + anchorColumn = other.anchorColumn; +} + +void BaseTextBlockSelection::clear() +{ + positionColumn = positionBlock = anchorColumn = anchorBlock = 0; +} + +// returns a cursor which always has the complete selection +QTextCursor BaseTextBlockSelection::selection(const TextDocument *baseTextDocument) const +{ + return cursor(baseTextDocument, true); +} + +// returns a cursor which always has the correct position and anchor +QTextCursor BaseTextBlockSelection::cursor(const TextDocument *baseTextDocument) const +{ + return cursor(baseTextDocument, false); +} + +QTextCursor BaseTextBlockSelection::cursor(const TextDocument *baseTextDocument, + bool fullSelection) const +{ + if (!baseTextDocument) + return QTextCursor(); + QTextDocument *document = baseTextDocument->document(); + const TabSettings &ts = baseTextDocument->tabSettings(); + + int selectionAnchorColumn; + int selectionPositionColumn; + if (anchorBlock == positionBlock || !fullSelection) { + selectionAnchorColumn = anchorColumn; + selectionPositionColumn = positionColumn; + } else if (anchorBlock == firstBlockNumber()){ + selectionAnchorColumn = firstVisualColumn(); + selectionPositionColumn = lastVisualColumn(); + } else { + selectionAnchorColumn = lastVisualColumn(); + selectionPositionColumn = firstVisualColumn(); + } + + QTextCursor cursor(document); + + const QTextBlock &anchorTextBlock = document->findBlockByNumber(anchorBlock); + const int anchorPosition = anchorTextBlock.position() + + ts.positionAtColumn(anchorTextBlock.text(), selectionAnchorColumn); + + const QTextBlock &positionTextBlock = document->findBlockByNumber(positionBlock); + const int cursorPosition = positionTextBlock.position() + + ts.positionAtColumn(positionTextBlock.text(), selectionPositionColumn); + + cursor.setPosition(anchorPosition); + cursor.setPosition(cursorPosition, QTextCursor::KeepAnchor); + return cursor; +} + +void BaseTextBlockSelection::fromPostition(int positionBlock, int positionColumn, + int anchorBlock, int anchorColumn) +{ + this->positionBlock = positionBlock; + this->positionColumn = positionColumn; + this->anchorBlock = anchorBlock; + this->anchorColumn = anchorColumn; +} + +bool BaseTextEditorWidgetPrivate::inFindScope(const QTextCursor &cursor) +{ + if (cursor.isNull()) + return false; + return inFindScope(cursor.selectionStart(), cursor.selectionEnd()); +} + +bool BaseTextEditorWidgetPrivate::inFindScope(int selectionStart, int selectionEnd) +{ + if (m_findScopeStart.isNull()) + return true; // no scope, everything is included + if (selectionStart < m_findScopeStart.position()) + return false; + if (selectionEnd > m_findScopeEnd.position()) + return false; + if (m_findScopeVerticalBlockSelectionFirstColumn < 0) + return true; + QTextBlock block = q->document()->findBlock(selectionStart); + if (block != q->document()->findBlock(selectionEnd)) + return false; + QString text = block.text(); + const TabSettings &ts = m_document->tabSettings(); + int startPosition = ts.positionAtColumn(text, m_findScopeVerticalBlockSelectionFirstColumn); + int endPosition = ts.positionAtColumn(text, m_findScopeVerticalBlockSelectionLastColumn); + if (selectionStart - block.position() < startPosition) + return false; + if (selectionEnd - block.position() > endPosition) + return false; + return true; +} + +void BaseTextEditorWidget::setBlockSelection(bool on) +{ + if (d->m_inBlockSelectionMode == on) + return; + + if (on) + d->enableBlockSelection(textCursor()); + else + d->disableBlockSelection(false); +} + +void BaseTextEditorWidget::setBlockSelection(int positionBlock, int positionColumn, + int anchhorBlock, int anchorColumn) +{ + d->enableBlockSelection(positionBlock, positionColumn, anchhorBlock, anchorColumn); +} + +void BaseTextEditorWidget::setBlockSelection(const QTextCursor &cursor) +{ + d->enableBlockSelection(cursor); +} + +QTextCursor BaseTextEditorWidget::blockSelection() const +{ + return d->m_blockSelection.cursor(d->m_document.data()); +} + +bool BaseTextEditorWidget::hasBlockSelection() const +{ + return d->m_inBlockSelectionMode; +} + +void BaseTextEditorWidgetPrivate::updateTabStops() +{ + // Although the tab stop is stored as qreal the API from QPlainTextEdit only allows it + // to be set as an int. A work around is to access directly the QTextOption. + qreal charWidth = QFontMetricsF(q->font()).width(QLatin1Char(' ')); + QTextOption option = q->document()->defaultTextOption(); + option.setTabStop(charWidth * m_document->tabSettings().m_tabSize); + q->document()->setDefaultTextOption(option); +} + +int BaseTextEditorWidget::columnCount() const +{ + QFontMetricsF fm(font()); + return viewport()->rect().width() / fm.width(QLatin1Char('x')); +} + +int BaseTextEditorWidget::rowCount() const +{ + QFontMetricsF fm(font()); + return viewport()->rect().height() / fm.lineSpacing(); +} + +/** + Helper function to transform a selected text. If nothing is selected at the moment + the word under the cursor is used. + The type of the transformation is determined by the function pointer given. + + @param method pointer to the QString function to use for the transformation + + @see uppercaseSelection, lowercaseSelection +*/ +void BaseTextEditorWidgetPrivate::transformSelection(TransformationMethod method) +{ + if (q->hasBlockSelection()) { + transformBlockSelection(method); + return; + } + + QTextCursor cursor = q->textCursor(); + int pos = cursor.position(); + int anchor = cursor.anchor(); + + if (!cursor.hasSelection()) { + // if nothing is selected, select the word over the cursor + cursor.select(QTextCursor::WordUnderCursor); + } + + QString text = cursor.selectedText(); + QString transformedText = method(text); + + if (transformedText == text) { + // if the transformation does not do anything to the selection, do no create an undo step + return; + } + + cursor.insertText(transformedText); + + // (re)select the changed text + // Note: this assumes the transformation did not change the length, + cursor.setPosition(anchor); + cursor.setPosition(pos, QTextCursor::KeepAnchor); + q->setTextCursor(cursor); +} + +void BaseTextEditorWidgetPrivate::transformBlockSelection(TransformationMethod method) +{ + QTextCursor cursor = q->textCursor(); + const TabSettings &ts = m_document->tabSettings(); + + // saved to restore the blockselection + const int positionColumn = m_blockSelection.positionColumn; + const int positionBlock = m_blockSelection.positionBlock; + const int anchorColumn = m_blockSelection.anchorColumn; + const int anchorBlock = m_blockSelection.anchorBlock; + + QTextBlock block = m_document->document()->findBlockByNumber( + m_blockSelection.firstBlockNumber()); + const QTextBlock &lastBlock = m_document->document()->findBlockByNumber( + m_blockSelection.lastBlockNumber()); + + cursor.beginEditBlock(); + for (;;) { + // get position of the selection + const QString &blockText = block.text(); + const int startPos = block.position() + + ts.positionAtColumn(blockText, m_blockSelection.firstVisualColumn()); + const int endPos = block.position() + + ts.positionAtColumn(blockText, m_blockSelection.lastVisualColumn()); + + // check if the selection is inside the text block + if (startPos < endPos) { + cursor.setPosition(startPos); + cursor.setPosition(endPos, QTextCursor::KeepAnchor); + const QString &transformedText = method(m_document->textAt(startPos, endPos - startPos)); + if (transformedText != cursor.selectedText()) + cursor.insertText(transformedText); + } + if (block == lastBlock) + break; + block = block.next(); + } + cursor.endEditBlock(); + + // restore former block selection + enableBlockSelection(positionBlock, anchorColumn, anchorBlock, positionColumn); +} + +void BaseTextEditorWidget::inSnippetMode(bool *active) +{ + *active = d->m_snippetOverlay->isVisible(); +} + +void BaseTextEditorWidget::setCompletionAssistProvider(CompletionAssistProvider *provider) +{ + d->m_completionAssistProvider = provider; +} + +void BaseTextEditorWidget::invokeAssist(AssistKind kind, IAssistProvider *provider) +{ + bool previousMode = overwriteMode(); + setOverwriteMode(false); + ensureCursorVisible(); + d->m_codeAssistant.invoke(kind, provider); + setOverwriteMode(previousMode); +} + +AssistInterface *BaseTextEditorWidget::createAssistInterface(AssistKind kind, + AssistReason reason) const +{ + Q_UNUSED(kind); + return new AssistInterface(document(), position(), d->m_document->filePath(), reason); +} + +QString BaseTextEditorWidget::foldReplacementText(const QTextBlock &) const +{ + return QLatin1String("..."); +} + +bool BaseTextEditor::open(QString *errorString, const QString &fileName, const QString &realFileName) +{ + if (!editorWidget()->open(errorString, fileName, realFileName)) + return false; + textDocument()->setMimeType(Core::MimeDatabase::findByFile(QFileInfo(fileName)).type()); + return true; +} + +QByteArray BaseTextEditor::saveState() const +{ + return editorWidget()->saveState(); +} + +bool BaseTextEditor::restoreState(const QByteArray &state) +{ + return editorWidget()->restoreState(state); +} + +BaseTextEditor *BaseTextEditor::currentTextEditor() +{ + return qobject_cast<BaseTextEditor *>(EditorManager::currentEditor()); +} + +BaseTextEditorWidget *BaseTextEditor::editorWidget() const +{ + QTC_ASSERT(qobject_cast<BaseTextEditorWidget *>(m_widget.data()), return 0); + return static_cast<BaseTextEditorWidget *>(m_widget.data()); +} + +QTextDocument *BaseTextEditor::qdocument() const +{ + return textDocument()->document(); +} + +void BaseTextEditor::setTextCursor(const QTextCursor &cursor) +{ + editorWidget()->setTextCursor(cursor); +} + +QTextCursor BaseTextEditor::textCursor() const +{ + return editorWidget()->textCursor(); +} + +QChar BaseTextEditor::characterAt(int pos) const +{ + return textDocument()->characterAt(pos); +} + +QString BaseTextEditor::textAt(int from, int to) const +{ + return textDocument()->textAt(from, to); +} + +QChar BaseTextEditorWidget::characterAt(int pos) const +{ + return textDocument()->characterAt(pos); +} + +QString BaseTextEditorWidget::textAt(int from, int to) const +{ + return textDocument()->textAt(from, to); +} + +void BaseTextEditorWidget::configureMimeType(const QString &mimeType) +{ + configureMimeType(MimeDatabase::findByType(mimeType)); +} + +void BaseTextEditorWidget::configureMimeType(const MimeType &mimeType) +{ + Highlighter *highlighter = new Highlighter(); + highlighter->setTabSettings(textDocument()->tabSettings()); + textDocument()->setSyntaxHighlighter(highlighter); + + setCodeFoldingSupported(false); + + if (!mimeType.isNull()) { + d->m_isMissingSyntaxDefinition = true; + + setMimeTypeForHighlighter(highlighter, mimeType); + const QString &type = mimeType.type(); + textDocument()->setMimeType(type); + + QString definitionId = Manager::instance()->definitionIdByMimeType(type); + if (definitionId.isEmpty()) + definitionId = findDefinitionId(mimeType, true); + + if (!definitionId.isEmpty()) { + d->m_isMissingSyntaxDefinition = false; + const QSharedPointer<HighlightDefinition> &definition = + Manager::instance()->definition(definitionId); + if (!definition.isNull() && definition->isValid()) { + d->m_commentDefinition.isAfterWhiteSpaces = definition->isCommentAfterWhiteSpaces(); + d->m_commentDefinition.singleLine = definition->singleLineComment(); + d->m_commentDefinition.multiLineStart = definition->multiLineCommentStart(); + d->m_commentDefinition.multiLineEnd = definition->multiLineCommentEnd(); + + setCodeFoldingSupported(true); + } + } else { + const QString &fileName = textDocument()->filePath(); + if (TextEditorSettings::highlighterSettings().isIgnoredFilePattern(fileName)) + d->m_isMissingSyntaxDefinition = false; + } + } + + textDocument()->setFontSettings(TextEditorSettings::fontSettings()); + + updateEditorInfoBar(this); +} + +bool BaseTextEditorWidget::isMissingSyntaxDefinition() const +{ + return d->m_isMissingSyntaxDefinition; +} + +// The remnants of PlainTextEditor. +void BaseTextEditorWidget::setupAsPlainEditor() +{ + setRevisionsVisible(true); + setMarksVisible(true); + setLineSeparatorsAllowed(true); + + textDocument()->setMimeType(QLatin1String(TextEditor::Constants::C_TEXTEDITOR_MIMETYPE_TEXT)); + + connect(textDocument(), &IDocument::filePathChanged, + d, &BaseTextEditorWidgetPrivate::reconfigure); + + connect(Manager::instance(), &Manager::mimeTypesRegistered, + d, &BaseTextEditorWidgetPrivate::reconfigure); + + updateEditorInfoBar(this); +} + +IEditor *BaseTextEditor::duplicate() +{ + // Use new standard setup if that's available. + if (d->m_origin) + return d->m_origin->duplicateTextEditor(this); + + // If neither is sufficient, you need to implement 'YourEditor::duplicate'. + QTC_CHECK(false); + return 0; +} + + +// +// BaseTextEditorFactory +// + +BaseTextEditorFactory::BaseTextEditorFactory(QObject *parent) + : IEditorFactory(parent) +{ + m_editorCreator = []() { return new BaseTextEditor; }; + m_widgetCreator = []() { return new BaseTextEditorWidget; }; + m_commentStyle = CommentDefinition::NoStyle; + m_duplicatedSupported = true; +} + +void BaseTextEditorFactory::setDocumentCreator(const DocumentCreator &creator) +{ + m_documentCreator = creator; +} + +void BaseTextEditorFactory::setEditorWidgetCreator(const EditorWidgetCreator &creator) +{ + m_widgetCreator = creator; +} + +void BaseTextEditorFactory::setEditorCreator(const EditorCreator &creator) +{ + m_editorCreator = creator; +} + +void BaseTextEditorFactory::setIndenterCreator(const IndenterCreator &creator) +{ + m_indenterCreator = creator; +} + +void BaseTextEditorFactory::setSyntaxHighlighterCreator(const SyntaxHighLighterCreator &creator) +{ + m_syntaxHighlighterCreator = creator; +} + +void BaseTextEditorFactory::setGenericSyntaxHighlighter(const QString &mimeType) +{ + m_syntaxHighlighterCreator = [this, mimeType]() -> SyntaxHighlighter * { + Highlighter *highlighter = new TextEditor::Highlighter(); + setMimeTypeForHighlighter(highlighter, Core::MimeDatabase::findByType(mimeType)); + return highlighter; + }; +} + +void BaseTextEditorFactory::setAutoCompleterCreator(const AutoCompleterCreator &creator) +{ + m_autoCompleterCreator = creator; +} + +void BaseTextEditorFactory::setEditorActionHandlers(Id contextId, uint optionalActions) +{ + new TextEditorActionHandler(this, contextId, optionalActions); +} + +void BaseTextEditorFactory::setEditorActionHandlers(uint optionalActions) +{ + new TextEditorActionHandler(this, id(), optionalActions); +} + +void BaseTextEditorFactory::setCommentStyle(CommentDefinition::Style style) +{ + m_commentStyle = style; +} + +void BaseTextEditorFactory::setDuplicatedSupported(bool on) +{ + m_duplicatedSupported = on; +} + +BaseTextEditor *BaseTextEditorFactory::duplicateTextEditor(BaseTextEditor *other) +{ + BaseTextEditor *editor = createEditorHelper(other->editorWidget()->textDocumentPtr()); + editor->editorWidget()->finalizeInitializationAfterDuplication(other->editorWidget()); + return editor; +} + +IEditor *BaseTextEditorFactory::createEditor() +{ + BaseTextDocumentPtr doc(m_documentCreator()); + + if (m_indenterCreator) + doc->setIndenter(m_indenterCreator()); + + if (m_syntaxHighlighterCreator) { + SyntaxHighlighter *highlighter = m_syntaxHighlighterCreator(); + highlighter->setParent(doc.data()); + doc->setSyntaxHighlighter(highlighter); + } + + return createEditorHelper(doc); +} + +BaseTextEditor *BaseTextEditorFactory::createEditorHelper(const BaseTextDocumentPtr &document) +{ + BaseTextEditorWidget *widget = m_widgetCreator(); + BaseTextEditor *editor = m_editorCreator(); + editor->setDuplicateSupported(m_duplicatedSupported); + editor->addContext(id()); + editor->d->m_origin = this; + + editor->m_widget = widget; + + // Needs to go before setTextDocument as this copies the current settings. + if (m_autoCompleterCreator) + widget->setAutoCompleter(m_autoCompleterCreator()); + + widget->setTextDocument(document); + + widget->d->m_codeAssistant.configure(widget); + widget->d->m_commentDefinition.setStyle(m_commentStyle); + + connect(widget, &BaseTextEditorWidget::markRequested, editor, + [editor](int line, BaseTextEditor::MarkRequestKind kind) { + editor->markRequested(editor, line, kind); + }); + + connect(widget, &BaseTextEditorWidget::markContextMenuRequested, editor, + [editor](int line, QMenu *menu) { + editor->markContextMenuRequested(editor, line, menu); + }); + + connect(widget, &BaseTextEditorWidget::tooltipRequested, editor, + [editor](const QPoint &globalPos, int position) { + editor->tooltipRequested(editor, globalPos, position); + }); + + connect(widget, &BaseTextEditorWidget::markTooltipRequested, editor, + [editor](const QPoint &globalPos, int line) { + editor->markTooltipRequested(editor, globalPos, line); + }); + + connect(widget, &BaseTextEditorWidget::activateEditor, + [editor]() { Core::EditorManager::activateEditor(editor); }); + + connect(widget, &BaseTextEditorWidget::clearContentsHelpId, + [editor]() { editor->setContextHelpId(QString()); }); + + widget->finalizeInitialization(); + editor->finalizeInitialization(); + + connect(widget->d->m_cursorPositionLabel, &LineColumnLabel::clicked, [editor] { + EditorManager::activateEditor(editor, EditorManager::IgnoreNavigationHistory); + if (Core::Command *cmd = ActionManager::command(Core::Constants::GOTO)) { + if (QAction *act = cmd->action()) + act->trigger(); + } + }); + return editor; +} + +} // namespace TextEditor + +#include "texteditor.moc" |