Files
qt-creator/src/plugins/texteditor/basetexteditor.cpp

6930 lines
239 KiB
C++
Raw Normal View History

/****************************************************************************
2008-12-02 12:01:29 +01:00
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
2008-12-02 12:01:29 +01:00
**
** This file is part of Qt Creator.
2008-12-02 12:01:29 +01:00
**
** 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
2010-12-17 16:01:08 +01:00
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
2008-12-02 16:19:05 +01:00
#include "basetexteditor.h"
#include "basetexteditor_p.h"
2008-12-02 16:19:05 +01:00
2008-12-02 12:01:29 +01:00
#include "basetextdocument.h"
#include "basetextdocumentlayout.h"
#include "behaviorsettings.h"
2008-12-02 12:01:29 +01:00
#include "codecselector.h"
#include "completionsettings.h"
#include "snippets/snippet.h"
2009-12-08 17:43:01 +01:00
#include "tabsettings.h"
#include "typingsettings.h"
#include "icodestylepreferences.h"
#include "syntaxhighlighter.h"
#include "indenter.h"
#include "autocompleter.h"
#include "convenience.h"
#include "texteditorsettings.h"
#include "texteditoroverlay.h"
#include "circularclipboard.h"
#include "circularclipboardassist.h"
#include <texteditor/codeassist/codeassistant.h>
#include <texteditor/codeassist/defaultassistinterface.h>
2008-12-02 12:01:29 +01:00
#include <aggregation/aggregate.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/actioncontainer.h>
2008-12-02 12:01:29 +01:00
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/infobar.h>
#include <coreplugin/manhattanstyle.h>
#include <coreplugin/find/basetextfind.h>
2008-12-02 12:01:29 +01:00
#include <utils/linecolumnlabel.h>
#include <utils/hostosinfo.h>
2008-12-09 15:25:01 +01:00
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
#include <utils/tooltip/tooltip.h>
#include <utils/tooltip/tipcontents.h>
#include <utils/uncommentselection.h>
2008-12-02 12:01:29 +01:00
#include <QCoreApplication>
#include <QTextCodec>
#include <QDebug>
#include <QTime>
#include <QTimer>
#include <QTimeLine>
#include <QMimeData>
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QKeyEvent>
#include <QPainter>
#include <QPrinter>
#include <QPrintDialog>
#include <QScrollBar>
#include <QShortcut>
#include <QStyle>
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QTextBlock>
#include <QTextLayout>
#include <QToolBar>
#include <QMenu>
#include <QMessageBox>
#include <QClipboard>
2008-12-02 12:01:29 +01:00
//#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 a base class for QPlainTextEdit-based text editors.
The corresponding document base class is BaseTextDocument, the corresponding widget base class
is BaseTextEditorWidget.
*/
using namespace Core;
using namespace Utils;
2008-12-02 12:01:29 +01:00
namespace TextEditor {
namespace Internal {
2008-12-02 12:01:29 +01:00
typedef QString (QString::*TransformationMethod)() const;
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 slots:
void step(qreal v);
private:
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 BaseTextEditorPrivate
{
public:
// Note: This is always a copy of IContext::m_widget.
BaseTextEditorWidget *m_editorWidget;
QToolBar *m_toolBar;
QWidget *m_stretchWidget;
QAction *m_cursorPositionLabelAction;
Utils::LineColumnLabel *m_cursorPositionLabel;
QAction *m_fileEncodingLabelAction;
Utils::LineColumnLabel *m_fileEncodingLabel;
};
class BaseTextEditorWidgetPrivate
{
BaseTextEditorWidgetPrivate(const BaseTextEditorWidgetPrivate &);
BaseTextEditorWidgetPrivate &operator=(const BaseTextEditorWidgetPrivate &);
public:
BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent);
~BaseTextEditorWidgetPrivate();
void setupDocumentSignals();
void updateLineSelectionColor();
void print(QPrinter *printer);
void maybeSelectLine();
void updateCannotDecodeInfo();
void collectToCircularClipboard();
void ctor(const QSharedPointer<BaseTextDocument> &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);
BaseTextEditorWidget *q;
bool m_contentsChanged;
bool m_lastCursorChangeWasInteresting;
QSharedPointer<BaseTextDocument> 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;
Core::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;
Core::FindFlags m_findFlags;
void highlightSearchResults(const QTextBlock &block, TextEditorOverlay *overlay);
QTimer m_delayedUpdateTimer;
BaseTextEditor *m_editor;
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;
QScopedPointer<CodeAssistant> m_codeAssistant;
bool m_assistRelevantContentAdded;
QPointer<BaseTextEditorAnimator> m_animator;
int m_cursorBlockNumber;
int m_blockCount;
QPoint m_markDragStart;
bool m_markDragging;
QScopedPointer<AutoCompleter> m_autoCompleter;
QScopedPointer<Internal::ClipboardAssistProvider> m_clipboardAssistProvider;
};
class TextEditExtraArea : public QWidget
{
2008-12-02 12:01:29 +01:00
public:
TextEditExtraArea(BaseTextEditorWidget *edit)
: QWidget(edit)
{
2008-12-02 12:01:29 +01:00
textEdit = edit;
setAutoFillBackground(true);
}
protected:
2008-12-02 12:01:29 +01:00
QSize sizeHint() const {
return QSize(textEdit->extraAreaWidth(), 0);
}
void paintEvent(QPaintEvent *event) {
2008-12-02 12:01:29 +01:00
textEdit->extraAreaPaintEvent(event);
}
void mousePressEvent(QMouseEvent *event) {
2008-12-02 12:01:29 +01:00
textEdit->extraAreaMouseEvent(event);
}
void mouseMoveEvent(QMouseEvent *event) {
2008-12-02 12:01:29 +01:00
textEdit->extraAreaMouseEvent(event);
}
void mouseReleaseEvent(QMouseEvent *event) {
2008-12-02 12:01:29 +01:00
textEdit->extraAreaMouseEvent(event);
}
void leaveEvent(QEvent *event) {
2008-12-02 12:01:29 +01:00
textEdit->extraAreaLeaveEvent(event);
}
void contextMenuEvent(QContextMenuEvent *event) {
textEdit->extraAreaContextMenuEvent(event);
}
2008-12-02 12:01:29 +01:00
void wheelEvent(QWheelEvent *event) {
QCoreApplication::sendEvent(textEdit->viewport(), event);
}
private:
BaseTextEditorWidget *textEdit;
2008-12-02 12:01:29 +01:00
};
} // namespace Internal
using namespace Internal;
2008-12-02 12:01:29 +01:00
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)
2008-12-02 12:01:29 +01:00
: QPlainTextEdit(parent)
{
d = new BaseTextEditorWidgetPrivate(this);
d->ctor(QSharedPointer<BaseTextDocument>(new BaseTextDocument));
}
BaseTextEditorWidget::BaseTextEditorWidget(BaseTextDocument *doc, QWidget *parent)
: QPlainTextEdit(parent)
{
d = new BaseTextEditorWidgetPrivate(this);
d->ctor(QSharedPointer<BaseTextDocument>(doc));
}
BaseTextEditorWidget::BaseTextEditorWidget(BaseTextEditorWidget *other)
{
d = new BaseTextEditorWidgetPrivate(this);
d->ctor(other->d->m_document);
d->m_revisionsVisible = other->d->m_revisionsVisible;
}
void BaseTextEditorWidgetPrivate::ctor(const QSharedPointer<BaseTextDocument> &doc)
2008-12-02 12:01:29 +01:00
{
m_extraArea = new TextEditExtraArea(q);
m_extraArea->setMouseTracking(true);
q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
2008-12-02 12:01:29 +01:00
m_overlay = new TextEditorOverlay(q);
m_snippetOverlay = new TextEditorOverlay(q);
m_searchResultOverlay = new TextEditorOverlay(q);
m_refactorOverlay = new RefactorOverlay(q);
m_document = doc;
setupDocumentSignals();
2008-12-02 12:01:29 +01:00
// from RESEARCH
q->setLayoutDirection(Qt::LeftToRight);
q->viewport()->setMouseTracking(true);
2008-12-02 12:01:29 +01:00
extraAreaSelectionAnchorBlockNumber = -1;
extraAreaToggleMarkBlockNumber = -1;
extraAreaHighlightFoldedBlockNumber = -1;
visibleFoldedBlockNumber = -1;
suggestedVisibleFoldedBlockNumber = -1;
2008-12-02 12:01:29 +01:00
QObject::connect(m_codeAssistant.data(), SIGNAL(finished()), q, SIGNAL(assistFinished()));
QObject::connect(q, SIGNAL(blockCountChanged(int)), q, SLOT(slotUpdateExtraAreaWidth()));
QObject::connect(q, SIGNAL(modificationChanged(bool)), m_extraArea, SLOT(update()));
QObject::connect(q, SIGNAL(cursorPositionChanged()), q, SLOT(slotCursorPositionChanged()));
QObject::connect(q, SIGNAL(updateRequest(QRect,int)), q, SLOT(slotUpdateRequest(QRect,int)));
QObject::connect(q, SIGNAL(selectionChanged()), q, SLOT(slotSelectionChanged()));
2008-12-02 12:01:29 +01:00
// (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
2008-12-02 12:01:29 +01:00
// 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, SIGNAL(timeout()), q, SLOT(_q_matchParentheses()));
2008-12-02 12:01:29 +01:00
m_highlightBlocksTimer.setSingleShot(true);
QObject::connect(&m_highlightBlocksTimer, SIGNAL(timeout()), q, SLOT(_q_highlightBlocks()));
m_animator = 0;
2008-12-02 12:01:29 +01:00
q->slotUpdateExtraAreaWidth();
updateHighlights();
q->setFrameStyle(QFrame::NoFrame);
2008-12-02 12:01:29 +01:00
m_delayedUpdateTimer.setSingleShot(true);
QObject::connect(&m_delayedUpdateTimer, SIGNAL(timeout()), q->viewport(), SLOT(update()));
m_moveLineUndoHack = false;
2008-12-02 12:01:29 +01:00
}
BaseTextEditorWidget::~BaseTextEditorWidget()
2008-12-02 12:01:29 +01:00
{
delete d;
d = 0;
}
void BaseTextEditorWidget::print(QPrinter *printer)
2008-12-02 12:01:29 +01:00
{
const bool oldFullPage = printer->fullPage();
2008-12-02 12:01:29 +01:00
printer->setFullPage(true);
QPrintDialog *dlg = new QPrintDialog(printer, this);
dlg->setWindowTitle(tr("Print Document"));
if (dlg->exec() == QDialog::Accepted)
2008-12-02 12:01:29 +01:00
d->print(printer);
printer->setFullPage(oldFullPage);
delete dlg;
}
static int foldBoxWidth(const QFontMetrics &fm)
{
const int lineSpacing = fm.lineSpacing();
return lineSpacing + lineSpacing % 2 + 1;
}
2008-12-02 12:01:29 +01:00
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)
2008-12-02 12:01:29 +01:00
{
QTextDocument *doc = q->document();
QString title = m_document->displayName();
if (!title.isEmpty())
2008-12-02 12:01:29 +01:00
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) {
2008-12-02 12:01:29 +01:00
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) {
2008-12-02 12:01:29 +01:00
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;
}
2008-12-02 12:01:29 +01:00
BaseTextEditor *BaseTextEditorWidget::editor() const
2008-12-02 12:01:29 +01:00
{
if (!d->m_editor) {
d->m_editor = const_cast<BaseTextEditorWidget *>(this)->createEditor();
d->m_codeAssistant->configure(d->m_editor);
2008-12-02 12:01:29 +01:00
}
return d->m_editor;
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::selectEncoding()
2008-12-02 12:01:29 +01:00
{
BaseTextDocument *doc = d->m_document.data();
2008-12-02 12:01:29 +01:00
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; }
2008-12-02 12:01:29 +01:00
case CodecSelector::Save:
doc->setCodec(codecSelector.selectedCodec());
Core::EditorManager::saveEditor(editor());
updateTextCodecLabel();
2008-12-02 12:01:29 +01:00
break;
case CodecSelector::Cancel:
break;
}
}
void BaseTextEditorWidget::updateTextCodecLabel()
{
editor()->setFileEncodingLabelText(QString::fromLatin1(d->m_document->codec()->name()));
}
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());
Core::InfoBar *infoBar = m_document->infoBar();
Core::Id selectEncodingId(Constants::SELECT_ENCODING);
if (m_document->hasDecodingError()) {
if (!infoBar->canInfoBeAdded(selectEncodingId))
return;
Core::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"), q, SLOT(selectEncoding()));
infoBar->addInfo(info);
} else {
infoBar->removeInfo(selectEncodingId);
}
}
bool BaseTextEditorWidget::open(QString *errorString, const QString &fileName, const QString &realFileName)
2008-12-02 12:01:29 +01:00
{
if (d->m_document->open(errorString, fileName, realFileName)) {
2008-12-02 12:01:29 +01:00
moveCursor(QTextCursor::Start);
d->updateCannotDecodeInfo();
if (editor()->fileEncodingLabel()) {
connect(editor()->fileEncodingLabel(), SIGNAL(clicked()), this,
SLOT(selectEncoding()), Qt::UniqueConnection);
connect(d->m_document->document(), SIGNAL(modificationChanged(bool)), this,
SLOT(updateTextCodecLabel()), Qt::UniqueConnection);
updateTextCodecLabel();
}
2008-12-02 12:01:29 +01:00
return true;
}
return false;
}
/*
Collapses the first comment in a file, if there is only whitespace above
*/
void BaseTextEditorWidgetPrivate::foldLicenseHeader()
{
QTextDocument *doc = q->document();
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return);
QTextBlock block = doc->firstBlock();
while (block.isValid() && block.isVisible()) {
QString text = block.text();
if (BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
if (text.trimmed().startsWith(QLatin1String("/*"))) {
BaseTextDocumentLayout::doFoldOrUnfold(block, false);
moveCursorVisible();
documentLayout->requestUpdate();
documentLayout->emitDocumentSizeChanged();
break;
}
}
if (TabSettings::firstNonSpace(text) < text.size())
break;
block = block.next();
}
}
BaseTextDocument *BaseTextEditorWidget::baseTextDocument() const
2008-12-02 12:01:29 +01:00
{
return d->m_document.data();
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::editorContentsChange(int position, int charsRemoved, int charsAdded)
2008-12-02 12:01:29 +01:00
{
if (d->m_animator)
d->m_animator->finish();
2008-12-02 12:01:29 +01:00
d->m_contentsChanged = true;
QTextDocument *doc = document();
BaseTextDocumentLayout *documentLayout = static_cast<BaseTextDocumentLayout*>(doc->documentLayout());
const QTextBlock posBlock = doc->findBlock(position);
2008-12-02 12:01:29 +01:00
// Keep the line numbers and the block information for the text marks updated
if (charsRemoved != 0) {
documentLayout->updateMarksLineNumber();
documentLayout->updateMarksBlock(posBlock);
2008-12-02 12:01:29 +01:00
} else {
const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
2008-12-02 12:01:29 +01:00
if (posBlock != nextBlock) {
documentLayout->updateMarksLineNumber();
documentLayout->updateMarksBlock(posBlock);
documentLayout->updateMarksBlock(nextBlock);
2008-12-02 12:01:29 +01:00
} else {
documentLayout->updateMarksBlock(posBlock);
2008-12-02 12:01:29 +01:00
}
}
if (d->m_snippetOverlay->isVisible()) {
QTextCursor cursor = textCursor();
cursor.setPosition(position);
d->snippetCheckCursor(cursor);
}
if (charsAdded != 0 && document()->characterAt(position + charsAdded - 1).isPrint())
d->m_assistRelevantContentAdded = true;
int newBlockCount = doc->blockCount();
if (!hasFocus() && newBlockCount != d->m_blockCount) {
// lines were inserted or removed from outside, keep viewport on same part of text
if (firstVisibleBlock().blockNumber() > posBlock.blockNumber())
verticalScrollBar()->setValue(verticalScrollBar()->value() + newBlockCount - d->m_blockCount);
}
d->m_blockCount = newBlockCount;
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::slotSelectionChanged()
2008-12-02 12:01:29 +01:00
{
if (!textCursor().hasSelection() && !d->m_selectBlockAnchor.isNull())
d->m_selectBlockAnchor = QTextCursor();
// Clear any link which might be showing when the selection changes
d->clearLink();
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::gotoBlockStart()
{
QTextCursor cursor = textCursor();
if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
setTextCursor(cursor);
_q_matchParentheses();
}
}
void BaseTextEditorWidget::gotoBlockEnd()
{
QTextCursor cursor = textCursor();
if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
setTextCursor(cursor);
_q_matchParentheses();
}
}
void BaseTextEditorWidget::gotoBlockStartWithSelection()
{
QTextCursor cursor = textCursor();
if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
setTextCursor(cursor);
_q_matchParentheses();
}
}
void BaseTextEditorWidget::gotoBlockEndWithSelection()
{
QTextCursor cursor = textCursor();
if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
setTextCursor(cursor);
_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));
_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));
_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(baseTextDocument()->indent(textCursor()));
}
void BaseTextEditorWidget::unindent()
{
setTextCursor(baseTextDocument()->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;
const CommentDefinition *commentDefinition = q->editor()->commentDefinition();
if (commentDefinition) {
QString trimmedText(text.trimmed());
if (commentDefinition->hasSingleLineStyle()) {
if (trimmedText.startsWith(commentDefinition->singleLine))
shouldReindent = false;
}
if (shouldReindent && commentDefinition->hasMultiLineStyle()) {
// Don't have any single line comments; try multi line.
if (trimmedText.startsWith(commentDefinition->multiLineStart)
&& trimmedText.endsWith(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)
2008-12-02 12:01:29 +01:00
{
if (!isModifier(e) && mouseHidingEnabled())
viewport()->setCursor(Qt::BlankCursor);
ToolTip::hide();
d->m_moveLineUndoHack = false;
d->clearVisibleFoldedBlock();
2008-12-02 12:01:29 +01:00
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;
}
2008-12-02 12:01:29 +01:00
}
bool ro = isReadOnly();
const bool inOverwriteMode = overwriteMode();
2008-12-02 12:01:29 +01:00
if (!ro && d->m_inBlockSelectionMode) {
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
}
d->removeBlockSelection();
e->accept();
return;
} else if (e == QKeySequence::Paste) {
d->removeBlockSelection();
// continue
2008-12-02 12:01:29 +01:00
}
}
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;
}
2008-12-02 12:01:29 +01:00
QTextCursor cursor = textCursor();
const TabSettings &ts = d->m_document->tabSettings();
const TypingSettings &tps = d->m_document->typingSettings();
2009-09-15 17:04:17 +02:00
cursor.beginEditBlock();
int extraBlocks =
d->m_autoCompleter->paragraphSeparatorAboutToBeInserted(cursor,
d->m_document->tabSettings());
2009-09-15 17:04:17 +02:00
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);
2008-12-02 12:01:29 +01:00
}
2009-09-15 17:04:17 +02:00
cursor.endEditBlock();
2008-12-02 12:01:29 +01:00
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);
}
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
} 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();
2008-12-02 12:01:29 +01:00
return;
} break;
#endif
case Qt::Key_Tab:
2009-05-18 12:21:30 -05:00
case Qt::Key_Backtab: {
2008-12-02 12:01:29 +01:00
if (ro) break;
if (d->m_snippetOverlay->isVisible() && !d->m_snippetOverlay->isEmpty()) {
d->snippetTabOrBacktab(e->key() == Qt::Key_Tab);
e->accept();
return;
}
2009-05-18 12:21:30 -05:00
QTextCursor cursor = textCursor();
int newPosition;
if (d->m_document->typingSettings().tabShouldIndent(document(), cursor, &newPosition)) {
2009-05-18 12:21:30 -05:00
if (newPosition != cursor.position() && !cursor.hasSelection()) {
cursor.setPosition(newPosition);
setTextCursor(cursor);
}
d->m_document->autoIndent(cursor);
2009-05-18 12:21:30 -05:00
} else {
if (e->key() == Qt::Key_Tab)
indent();
else
unindent();
2009-05-18 12:21:30 -05:00
}
2008-12-02 12:01:29 +01:00
e->accept();
return;
2009-05-18 12:21:30 -05:00
} break;
2008-12-02 12:01:29 +01:00
case Qt::Key_Backspace:
if (ro) break;
if ((e->modifiers() & (Qt::ControlModifier
2008-12-02 12:01:29 +01:00
| Qt::ShiftModifier
| Qt::AltModifier
| Qt::MetaModifier)) == Qt::NoModifier
&& !textCursor().hasSelection()) {
d->handleBackspaceKey();
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
}
break;
case Qt::Key_Insert:
if (ro) break;
if (e->modifiers() == Qt::NoModifier) {
if (inOverwriteMode) {
d->m_autoCompleter->setAutoParenthesesEnabled(d->autoParenthesisOverwriteBackup);
d->m_autoCompleter->setSurroundWithEnabled(d->surroundWithEnabledOverwriteBackup);
setOverwriteMode(false);
viewport()->update();
} else {
d->autoParenthesisOverwriteBackup = d->m_autoCompleter->isAutoParenthesesEnabled();
d->surroundWithEnabledOverwriteBackup = d->m_autoCompleter->isSurroundWithEnabled();
d->m_autoCompleter->setAutoParenthesesEnabled(false);
d->m_autoCompleter->setSurroundWithEnabled(false);
setOverwriteMode(true);
}
e->accept();
return;
}
break;
2008-12-02 12:01:29 +01:00
default:
break;
}
const QString eventText = e->text();
if (!ro && d->m_inBlockSelectionMode) {
if (isPrintableText(eventText)) {
d->insertIntoBlockSelection(eventText);
2008-12-02 12:01:29 +01:00
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
2009-09-15 17:04:17 +02:00
QTextCursor cursor = textCursor();
const QString &autoText = d->m_autoCompleter->autoComplete(cursor, eventText);
2009-09-15 17:04:17 +02:00
QChar electricChar;
if (d->m_document->typingSettings().m_autoIndent) {
foreach (QChar c, eventText) {
if (d->m_document->indenter()->isElectricCharacter(c)) {
2009-09-15 17:04:17 +02:00
electricChar = c;
break;
}
}
}
bool cursorWithinSnippet = false;
if (d->m_snippetOverlay->isVisible())
cursorWithinSnippet = d->snippetCheckCursor(cursor);
bool doEditBlock = !electricChar.isNull() || !autoText.isEmpty() || cursorWithinSnippet;
if (doEditBlock)
2009-09-15 17:04:17 +02:00
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()) {
2009-09-15 17:04:17 +02:00
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);
2009-09-15 17:04:17 +02:00
}
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) {
2009-09-15 17:04:17 +02:00
cursor.endEditBlock();
if (cursorWithinSnippet)
d->m_snippetOverlay->updateEquivalentSelections(textCursor());
}
2009-09-15 17:04:17 +02:00
setTextCursor(cursor);
}
2008-12-02 12:01:29 +01:00
2010-07-19 14:06:00 +02:00
skip_event:
2008-12-02 12:01:29 +01:00
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();
2010-07-19 14:06:00 +02:00
}
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;
2010-01-19 16:18:28 +01:00
QTextCursor tc(document());
tc.setPosition(position);
tc.setPosition(position + length, QTextCursor::KeepAnchor);
QTextEdit::ExtraSelection selection;
selection.cursor = tc;
selection.format = (length
? baseTextDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES)
: baseTextDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES_RENAME));
2010-01-19 16:18:28 +01:00
selections.append(selection);
manglers << data.ranges.at(i).mangler;
2010-01-19 16:18:28 +01:00
}
cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
d->m_document->autoIndent(cursor);
2010-01-19 16:18:28 +01:00
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)
slotSelectionChanged();
}
void BaseTextEditorWidget::setTextCursor(const QTextCursor &cursor)
{
setTextCursor(cursor, false);
}
void BaseTextEditorWidget::gotoLine(int line, int column, bool centerLine)
2008-12-02 12:01:29 +01:00
{
d->m_lastCursorChangeWasInteresting = false; // avoid adding the previous position to history
2008-12-02 12:01:29 +01:00
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) {
2008-12-02 12:01:29 +01:00
++pos;
}
cursor.setPosition(pos);
}
setTextCursor(cursor);
if (centerLine)
centerCursor();
else
ensureCursorVisible();
2008-12-02 12:01:29 +01:00
}
d->saveCurrentCursorPositionForNavigation();
2008-12-02 12:01:29 +01:00
}
int BaseTextEditorWidget::position(BaseTextEditor::PositionOperation posOp, int at) const
2008-12-02 12:01:29 +01:00
{
QTextCursor tc = textCursor();
if (at != -1)
tc.setPosition(at);
if (posOp == BaseTextEditor::Current)
2008-12-02 12:01:29 +01:00
return tc.position();
switch (posOp) {
case BaseTextEditor::EndOfLine:
2008-12-02 12:01:29 +01:00
tc.movePosition(QTextCursor::EndOfLine);
return tc.position();
case BaseTextEditor::StartOfLine:
2008-12-02 12:01:29 +01:00
tc.movePosition(QTextCursor::StartOfLine);
return tc.position();
case BaseTextEditor::Anchor:
2008-12-02 12:01:29 +01:00
if (tc.hasSelection())
return tc.anchor();
break;
case BaseTextEditor::EndOfDoc:
2008-12-02 12:01:29 +01:00
tc.movePosition(QTextCursor::End);
return tc.position();
default:
break;
}
return -1;
}
void BaseTextEditorWidget::convertPosition(int pos, int *line, int *column) const
2008-12-02 12:01:29 +01:00
{
Convenience::convertPosition(document(), pos, line, column);
2008-12-02 12:01:29 +01:00
}
bool BaseTextEditorWidget::event(QEvent *e)
2008-12-02 12:01:29 +01:00
{
#if QT_VERSION >= 0x050000
if (e->type() != QEvent::InputMethodQuery)
d->m_contentsChanged = false;
#else
d->m_contentsChanged = false;
#endif
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;
2008-12-02 12:01:29 +01:00
return true;
break;
default:
break;
2008-12-02 12:01:29 +01:00
}
2008-12-02 12:01:29 +01:00
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 BaseTextEditorWidget::documentAboutToBeReloaded()
2008-12-02 12:01:29 +01:00
{
//memorize cursor position
2008-12-02 12:01:29 +01:00
d->m_tempState = saveState();
// remove extra selections (loads of QTextCursor objects)
for (int i = 0; i < NExtraSelectionKinds; ++i)
d->m_extraSelections[i].clear();
QPlainTextEdit::setExtraSelections(QList<QTextEdit::ExtraSelection>());
// clear all overlays
d->m_overlay->clear();
d->m_snippetOverlay->clear();
d->m_searchResultOverlay->clear();
d->m_refactorOverlay->clear();
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::documentReloadFinished(bool success)
2008-12-02 12:01:29 +01:00
{
if (!success)
return;
// restore cursor position
2008-12-02 12:01:29 +01:00
restoreState(d->m_tempState);
d->updateCannotDecodeInfo();
2008-12-02 12:01:29 +01:00
}
QByteArray BaseTextEditorWidget::saveState() const
2008-12-02 12:01:29 +01:00
{
QByteArray state;
QDataStream stream(&state, QIODevice::WriteOnly);
stream << 1; // version number
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
return state;
}
bool BaseTextEditorWidget::restoreState(const QByteArray &state)
2008-12-02 12:01:29 +01:00
{
if (state.isEmpty()) {
if (d->m_displaySettings.m_autoFoldFirstComment)
d->foldLicenseHeader();
return false;
}
2008-12-02 12:01:29 +01:00
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()) {
BaseTextDocumentLayout::doFoldOrUnfold(block, false);
layoutChanged = true;
}
}
if (layoutChanged) {
BaseTextDocumentLayout *documentLayout =
qobject_cast<BaseTextDocumentLayout*>(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);
2008-12-02 12:01:29 +01:00
verticalScrollBar()->setValue(vval);
horizontalScrollBar()->setValue(hval);
d->saveCurrentCursorPositionForNavigation();
2008-12-02 12:01:29 +01:00
return true;
}
void BaseTextEditorWidget::setParenthesesMatchingEnabled(bool b)
2008-12-02 12:01:29 +01:00
{
d->m_parenthesesMatchingEnabled = b;
}
bool BaseTextEditorWidget::isParenthesesMatchingEnabled() const
2008-12-02 12:01:29 +01:00
{
return d->m_parenthesesMatchingEnabled;
}
void BaseTextEditorWidget::setHighlightCurrentLine(bool b)
2008-12-02 12:01:29 +01:00
{
d->m_highlightCurrentLine = b;
d->updateCurrentLineHighlight();
2008-12-02 12:01:29 +01:00
}
bool BaseTextEditorWidget::highlightCurrentLine() const
2008-12-02 12:01:29 +01:00
{
return d->m_highlightCurrentLine;
}
void BaseTextEditorWidget::setLineNumbersVisible(bool b)
2008-12-02 12:01:29 +01:00
{
d->m_lineNumbersVisible = b;
slotUpdateExtraAreaWidth();
}
bool BaseTextEditorWidget::lineNumbersVisible() const
2008-12-02 12:01:29 +01:00
{
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)
2008-12-02 12:01:29 +01:00
{
d->m_marksVisible = b;
slotUpdateExtraAreaWidth();
}
bool BaseTextEditorWidget::marksVisible() const
2008-12-02 12:01:29 +01:00
{
return d->m_marksVisible;
}
void BaseTextEditorWidget::setRequestMarkEnabled(bool b)
2008-12-02 12:01:29 +01:00
{
d->m_requestMarkEnabled = b;
}
bool BaseTextEditorWidget::requestMarkEnabled() const
2008-12-02 12:01:29 +01:00
{
return d->m_requestMarkEnabled;
}
void BaseTextEditorWidget::setLineSeparatorsAllowed(bool b)
2008-12-02 12:01:29 +01:00
{
d->m_lineSeparatorsAllowed = b;
}
bool BaseTextEditorWidget::lineSeparatorsAllowed() const
2008-12-02 12:01:29 +01:00
{
return d->m_lineSeparatorsAllowed;
}
void BaseTextEditorWidget::updateCodeFoldingVisible()
2008-12-02 12:01:29 +01:00
{
const bool visible = d->m_codeFoldingSupported && d->m_displaySettings.m_displayFoldingMarkers;
if (d->m_codeFoldingVisible != visible) {
d->m_codeFoldingVisible = visible;
slotUpdateExtraAreaWidth();
}
2008-12-02 12:01:29 +01:00
}
bool BaseTextEditorWidget::codeFoldingVisible() const
2008-12-02 12:01:29 +01:00
{
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;
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)
2008-12-02 12:01:29 +01:00
{
d->m_revisionsVisible = b;
slotUpdateExtraAreaWidth();
}
bool BaseTextEditorWidget::revisionsVisible() const
2008-12-02 12:01:29 +01:00
{
return d->m_revisionsVisible;
}
void BaseTextEditorWidget::setVisibleWrapColumn(int column)
2008-12-02 12:01:29 +01:00
{
d->m_visibleWrapColumn = column;
viewport()->update();
}
int BaseTextEditorWidget::visibleWrapColumn() const
2008-12-02 12:01:29 +01:00
{
return d->m_visibleWrapColumn;
}
void BaseTextEditorWidget::setAutoCompleter(AutoCompleter *autoCompleter)
{
d->m_autoCompleter.reset(autoCompleter);
}
AutoCompleter *BaseTextEditorWidget::autoCompleter() const
{
return d->m_autoCompleter.data();
}
2008-12-02 12:01:29 +01:00
//--------- BaseTextEditorPrivate -----------
BaseTextEditorWidgetPrivate::BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent)
: q(parent),
2008-12-02 12:01:29 +01:00
m_contentsChanged(false),
m_lastCursorChangeWasInteresting(false),
2008-12-02 12:01:29 +01:00
m_parenthesesMatchingEnabled(false),
2010-06-04 11:47:39 +02:00
m_formatRange(false),
m_parenthesesMatchingTimer(0),
2008-12-02 12:01:29 +01:00
m_extraArea(0),
m_codeStylePreferences(0),
m_fontSettingsNeedsApply(true), // apply when making visible the first time, for the split case
2010-06-04 11:47:39 +02:00
extraAreaSelectionAnchorBlockNumber(-1),
extraAreaToggleMarkBlockNumber(-1),
extraAreaHighlightFoldedBlockNumber(-1),
m_overlay(0),
m_snippetOverlay(0),
m_searchResultOverlay(0),
m_refactorOverlay(0),
2010-06-04 11:47:39 +02:00
visibleFoldedBlockNumber(-1),
suggestedVisibleFoldedBlockNumber(-1),
m_mouseOnFoldedMarker(false),
2008-12-02 12:01:29 +01:00
m_marksVisible(false),
m_codeFoldingVisible(false),
m_codeFoldingSupported(false),
2008-12-02 12:01:29 +01:00
m_revisionsVisible(false),
m_lineNumbersVisible(true),
m_highlightCurrentLine(true),
m_requestMarkEnabled(true),
m_lineSeparatorsAllowed(false),
m_maybeFakeTooltipEvent(false),
2008-12-02 12:01:29 +01:00
m_visibleWrapColumn(0),
m_linkPressed(false),
2010-06-04 11:47:39 +02:00
m_delayedUpdateTimer(0),
m_editor(0),
2008-12-02 12:01:29 +01:00
m_inBlockSelectionMode(false),
m_moveLineUndoHack(false),
m_findScopeVerticalBlockSelectionFirstColumn(-1),
m_findScopeVerticalBlockSelectionLastColumn(-1),
m_highlightBlocksTimer(0),
m_codeAssistant(new CodeAssistant),
m_assistRelevantContentAdded(false),
m_cursorBlockNumber(-1),
m_blockCount(0),
m_markDragging(false),
m_autoCompleter(new AutoCompleter),
m_clipboardAssistProvider(new Internal::ClipboardAssistProvider)
2008-12-02 12:01:29 +01:00
{
}
BaseTextEditorWidgetPrivate::~BaseTextEditorWidgetPrivate()
2008-12-02 12:01:29 +01:00
{
}
void BaseTextEditorWidgetPrivate::setupDocumentSignals()
2008-12-02 12:01:29 +01:00
{
QTextDocument *doc = m_document->document();
2008-12-02 12:01:29 +01:00
q->setDocument(doc);
q->setCursorWidth(2); // Applies to the document layout
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
QTC_CHECK(documentLayout);
2008-12-02 12:01:29 +01:00
QObject::connect(documentLayout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(slotUpdateBlockNotify(QTextBlock)));
QObject::connect(documentLayout, SIGNAL(updateExtraArea()), q, SLOT(slotUpdateExtraArea()));
2008-12-02 12:01:29 +01:00
QObject::connect(q, SIGNAL(requestBlockUpdate(QTextBlock)), documentLayout, SIGNAL(updateBlock(QTextBlock)));
QObject::connect(doc, SIGNAL(contentsChange(int,int,int)), q,
SLOT(editorContentsChange(int,int,int)), Qt::DirectConnection);
QObject::connect(m_document.data(), SIGNAL(aboutToReload()), q, SLOT(documentAboutToBeReloaded()));
QObject::connect(m_document.data(), SIGNAL(reloadFinished(bool)), q, SLOT(documentReloadFinished(bool)));
QObject::connect(m_document.data(), SIGNAL(tabSettingsChanged()), q, SLOT(updateTabStops()));
QObject::connect(m_document.data(), SIGNAL(fontSettingsChanged()), q, SLOT(applyFontSettingsDelayed()));
2008-12-02 12:01:29 +01:00
q->slotUpdateExtraAreaWidth();
}
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;
BaseTextEditor *ed = q->editor();
emit ed->tooltipOverrideRequested(ed, toolTipPoint, c.position(), &handled);
if (!handled)
emit ed->tooltipRequested(ed, toolTipPoint, c.position());
}
bool BaseTextEditorWidget::viewportEvent(QEvent *event)
2008-12-02 12:01:29 +01:00
{
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;
}
2008-12-02 12:01:29 +01:00
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;
}
2008-12-02 12:01:29 +01:00
}
return QPlainTextEdit::viewportEvent(event);
}
void BaseTextEditorWidget::resizeEvent(QResizeEvent *e)
2008-12-02 12:01:29 +01:00
{
QPlainTextEdit::resizeEvent(e);
QRect cr = rect();
2008-12-02 12:01:29 +01:00
d->m_extraArea->setGeometry(
QStyle::visualRect(layoutDirection(), cr,
QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height())));
}
QRect BaseTextEditorWidgetPrivate::foldBox()
2008-12-02 12:01:29 +01:00
{
if (m_highlightBlocksInfo.isEmpty() || extraAreaHighlightFoldedBlockNumber < 0)
return QRect();
2008-12-02 12:01:29 +01:00
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());
2008-12-02 12:01:29 +01:00
}
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();
2008-12-02 12:01:29 +01:00
int viewportHeight = q->viewport()->height();
2008-12-02 12:01:29 +01:00
while (block.isValid() && top <= viewportHeight) {
QTextBlock nextBlock = block.next();
if (block.isVisible() && bottom >= 0 && q->replacementVisible(block.blockNumber())) {
2008-12-02 12:01:29 +01:00
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("}; ");
2008-12-02 12:01:29 +01:00
QRectF collapseRect(lineRect.right() + 12,
lineRect.top(),
q->fontMetrics().width(replacement),
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
}
return QTextBlock();
}
void BaseTextEditorWidgetPrivate::highlightSearchResults(const QTextBlock &block,
TextEditorOverlay *overlay)
2008-12-02 12:01:29 +01:00
{
if (m_searchExpr.isEmpty())
return;
int blockPosition = block.position();
QTextCursor cursor = q->textCursor();
2008-12-02 12:01:29 +01:00
QString text = block.text();
text.replace(QChar::Nbsp, QLatin1Char(' '));
int idx = -1;
int l = 1;
2008-12-02 12:01:29 +01:00
while (idx < text.length()) {
idx = m_searchExpr.indexIn(text, idx + l);
2008-12-02 12:01:29 +01:00
if (idx < 0)
break;
l = m_searchExpr.matchedLength();
if (l == 0)
break;
if ((m_findFlags & Core::FindWholeWords)
2008-12-02 12:01:29 +01:00
&& ((idx && text.at(idx-1).isLetterOrNumber())
|| (idx + l < text.length() && text.at(idx + l).isLetterOrNumber())))
continue;
if (!q->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);
2008-12-02 12:01:29 +01:00
}
}
QString BaseTextEditorWidgetPrivate::copyBlockSelection()
2008-12-02 12:01:29 +01:00
{
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;
2008-12-02 12:01:29 +01:00
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)
2008-12-02 12:01:29 +01:00
break;
2008-12-02 12:01:29 +01:00
block = block.next();
}
return selection;
2008-12-02 12:01:29 +01:00
}
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()
2008-12-02 12:01:29 +01:00
{
QTextCursor cursor = q->textCursor();
if (!cursor.hasSelection() || !m_inBlockSelectionMode)
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
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());
2008-12-02 12:01:29 +01:00
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)
2008-12-02 12:01:29 +01:00
break;
block = block.next();
}
cursor.setPosition(cursorPosition);
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
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);
}
2008-12-02 12:01:29 +01:00
}
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)
2008-12-02 12:01:29 +01:00
{
/*
Here comes an almost verbatim copy of
QPlainTextEdit::paintEvent() so we can adjust the extra
selections dynamically to indicate all search results.
*/
//begin QPlainTextEdit::paintEvent()
QPainter painter(viewport());
QTextDocument *doc = document();
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2008-12-09 15:25:01 +01:00
QTC_ASSERT(documentLayout, return);
const FontSettings &fs = baseTextDocument()->fontSettings();
const QTextCharFormat &searchScopeFormat = fs.toTextCharFormat(C_SEARCH_SCOPE);
const QTextCharFormat &ifdefedOutFormat = fs.toTextCharFormat(C_DISABLED_CODE);
2008-12-02 12:01:29 +01:00
QPointF offset(contentOffset());
QTextBlock textCursorBlock = textCursor().block();
2008-12-02 12:01:29 +01:00
bool hasMainSelection = textCursor().hasSelection();
bool suppressSyntaxInIfdefedOutBlock = (ifdefedOutFormat.foreground()
!= palette().foreground());
2008-12-02 12:01:29 +01:00
QRect er = e->rect();
QRect viewportRect = viewport()->rect();
qreal lineX = 0;
if (d->m_visibleWrapColumn > 0) {
// 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()) {
const QBrush background = ifdefedOutFormat.background();
painter.fillRect(QRectF(lineX, er.top(), viewportRect.width() - lineX, er.height()),
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);
}
}
// 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);
2008-12-02 12:01:29 +01:00
bool editable = !isReadOnly();
QTextBlock block = firstVisibleBlock();
QAbstractTextDocumentLayout::PaintContext context = getPaintContext();
if (!d->m_highlightBlocksInfo.isEmpty()) {
const QColor baseColor = palette().base().color();
// extra pass for the block highlight
const int margin = 5;
QTextBlock blockFP = block;
QPointF offsetFP = offset;
while (blockFP.isValid()) {
QRectF r = blockBoundingRect(blockFP).translated(offsetFP);
int n = blockFP.blockNumber();
int depth = 0;
foreach (int i, d->m_highlightBlocksInfo.open)
if (n >= i)
++depth;
foreach (int i, d->m_highlightBlocksInfo.close)
if (n > i)
--depth;
int count = d->m_highlightBlocksInfo.count();
if (count) {
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(viewport()->width());
oneRect.adjust(vi, 0, -8*i, 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;
2008-12-02 12:01:29 +01:00
if (d->m_inBlockSelectionMode && context.selections.count()
&& context.selections.last().cursor == textCursor()) {
blockSelectionIndex = context.selections.size()-1;
context.selections[blockSelectionIndex].format.clearBackground();
2008-12-02 12:01:29 +01:00
}
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);
2008-12-02 12:01:29 +01:00
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;
2009-05-28 11:11:31 +02:00
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 (BaseTextDocumentLayout::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());
}
}
}
// 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();
2008-12-02 12:01:29 +01:00
QTextOption option = layout->textOption();
if (suppressSyntaxInIfdefedOutBlock && BaseTextDocumentLayout::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!
2008-12-02 12:01:29 +01:00
int blpos = block.position();
int bllen = block.length();
QVector<QTextLayout::FormatRange> selections;
QVector<QTextLayout::FormatRange> prioritySelections;
2008-12-02 12:01:29 +01:00
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) {
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
}
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);
}
2008-12-02 12:01:29 +01:00
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())) {
2008-12-02 12:01:29 +01:00
// 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);
2008-12-02 12:01:29 +01:00
}
#endif
2008-12-02 12:01:29 +01:00
}
selections += prioritySelections;
2008-12-02 12:01:29 +01:00
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()));
}
2008-12-02 12:01:29 +01:00
layout->draw(&painter, offset, selections, er);
if ((drawCursor && !drawCursorAsBlock)
|| (editable && context.cursorPosition < -1 && !layout->preeditAreaText().isEmpty())) {
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
}
if ((!HostOsInfo::isMacHost()
|| d->m_blockSelection.positionColumn == d->m_blockSelection.anchorColumn)
&& blockSelectionCursorRect.isValid())
painter.fillRect(blockSelectionCursorRect, palette().text());
2008-12-02 12:01:29 +01:00
}
offset.ry() += r.height();
if (offset.y() > viewportRect.height())
break;
2008-12-02 12:01:29 +01:00
block = block.next();
2008-12-02 12:01:29 +01:00
if (!block.isVisible()) {
if (block.blockNumber() == d->visibleFoldedBlockNumber) {
2008-12-02 12:01:29 +01:00
visibleCollapsedBlock = block;
2009-04-28 12:55:31 +02:00
visibleCollapsedBlockOffset = offset + QPointF(0,1);
2008-12-02 12:01:29 +01:00
}
// invisible blocks do have zero line count
block = doc->findBlockByLineNumber(block.firstLineNumber());
}
}
painter.setPen(context.palette.text().color());
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
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()) {
2008-12-02 12:01:29 +01:00
// 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();
}
2008-12-02 12:01:29 +01:00
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);
2008-12-02 12:01:29 +01:00
}
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())) {
2008-12-02 12:01:29 +01:00
bool selectThis = (hasSelection
&& nextBlock.position() >= selectionStart
&& nextBlock.position() < selectionEnd);
painter.save();
2008-12-02 12:01:29 +01:00
if (selectThis) {
painter.setBrush(palette().highlight());
} else {
QColor rc = replacementPenColor(block.blockNumber());
if (rc.isValid())
painter.setPen(rc);
2008-12-02 12:01:29 +01:00
}
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("}; ");
2008-12-02 12:01:29 +01:00
QRectF collapseRect(lineRect.right() + 12,
lineRect.top(),
fontMetrics().width(rectReplacement),
2008-12-02 12:01:29 +01:00
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 = BaseTextDocumentLayout::testUserData(nextBlock)) {
if (nextBlockUserData->foldingStartIncluded())
replacement.prepend(nextBlock.text().trimmed().left(1));
2008-12-02 12:01:29 +01:00
}
block = nextVisibleBlock.previous();
if (!block.isValid())
block = doc->lastBlock();
if (TextBlockUserData *blockUserData = BaseTextDocumentLayout::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));
}
2008-12-02 12:01:29 +01:00
}
}
2008-12-02 12:01:29 +01:00
if (selectThis)
painter.setPen(palette().highlightedText().color());
painter.drawText(collapseRect, Qt::AlignCenter, replacement);
painter.restore();
2008-12-02 12:01:29 +01:00
}
}
block = nextVisibleBlock;
top = bottom;
bottom = top + blockBoundingRect(block).height();
2008-12-02 12:01:29 +01:00
}
2009-04-28 19:02:58 +02:00
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
= baseTextDocument()->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();
}
2008-12-02 12:01:29 +01:00
}
QWidget *BaseTextEditorWidget::extraArea() const
2008-12-02 12:01:29 +01:00
{
return d->m_extraArea;
}
int BaseTextEditorWidget::extraAreaWidth(int *markWidthPtr) const
2008-12-02 12:01:29 +01:00
{
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());
2008-12-02 12:01:29 +01:00
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 &currentLineNumberFormat
= baseTextDocument()->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();
2008-12-02 12:01:29 +01:00
}
int markWidth = 0;
if (d->m_marksVisible) {
markWidth += documentLayout->maxMarkWidthFactor * fm.lineSpacing() + 2;
2008-12-02 12:01:29 +01:00
// 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);
2008-12-02 12:01:29 +01:00
return space;
}
void BaseTextEditorWidget::slotUpdateExtraAreaWidth()
2008-12-02 12:01:29 +01:00
{
if (isLeftToRight())
setViewportMargins(extraAreaWidth(), 0, 0, 0);
else
setViewportMargins(0, 0, extraAreaWidth(), 0);
2008-12-02 12:01:29 +01:00
}
static void drawRectBox(QPainter *painter, const QRect &rect, bool start, bool end,
const QPalette &pal)
{
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
QRgb b = pal.base().color().rgb();
QRgb h = pal.highlight().color().rgb();
QColor c = Utils::StyleHelper::mergedColors(b,h, 50);
QLinearGradient grad(rect.topLeft(), rect.topRight());
grad.setColorAt(0, c.lighter(110));
grad.setColorAt(1, c.lighter(130));
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)
2008-12-02 12:01:29 +01:00
{
QTextDocument *doc = document();
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2008-12-09 15:25:01 +01:00
QTC_ASSERT(documentLayout, return);
2008-12-02 12:01:29 +01:00
2009-04-28 12:55:31 +02:00
int selStart = textCursor().selectionStart();
int selEnd = textCursor().selectionEnd();
2008-12-02 12:01:29 +01:00
QPalette pal = d->m_extraArea->palette();
pal.setCurrentColorGroup(QPalette::Active);
QPainter painter(d->m_extraArea);
const QFontMetrics fm(d->m_extraArea->font());
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
painter.fillRect(e->rect(), pal.color(QPalette::Background));
2008-12-02 12:01:29 +01:00
QTextBlock block = firstVisibleBlock();
int blockNumber = block.blockNumber();
qreal top = blockBoundingGeometry(block).translated(contentOffset()).top();
qreal bottom = top;
2008-12-02 12:01:29 +01:00
while (block.isValid() && top <= e->rect().bottom()) {
top = bottom;
const qreal height = blockBoundingRect(block).height();
bottom = top + height;
2008-12-02 12:01:29 +01:00
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();
}
2009-11-09 15:59:48 +01:00
if (bottom < e->rect().top()) {
block = nextVisibleBlock;
blockNumber = nextVisibleBlockNumber;
continue;
}
2008-12-02 12:01:29 +01:00
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);
2008-12-02 12:01:29 +01:00
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 = BaseTextDocumentLayout::testUserData(nextBlock);
bool drawBox = nextBlockUserData
&& BaseTextDocumentLayout::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);
}
2008-12-02 12:01:29 +01:00
}
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);
2008-12-02 12:01:29 +01:00
painter.restore();
}
if (d->m_lineNumbersVisible) {
const QString &number = lineNumber(blockNumber);
2009-04-28 12:55:31 +02:00
bool selected = (
(selStart < block.position() + block.length()
&& selEnd > block.position())
|| (selStart == selEnd && selStart == block.position())
2009-04-28 12:55:31 +02:00
);
if (selected) {
painter.save();
QFont f = painter.font();
const QTextCharFormat &currentLineNumberFormat
= baseTextDocument()->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);
2009-04-28 12:55:31 +02:00
if (selected)
painter.restore();
2008-12-02 12:01:29 +01:00
}
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);
}
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::slotUpdateRequest(const QRect &r, int dy)
{
if (dy) {
d->m_extraArea->scroll(0, dy);
} else if (r.width() > 4) { // wider than cursor width, not just cursor blinking
d->m_extraArea->update(0, r.y(), d->m_extraArea->width(), r.height());
2009-12-02 11:29:38 +01:00
if (!d->m_searchExpr.isEmpty()) {
const int m = d->m_searchResultOverlay->dropShadowWidth();
viewport()->update(r.adjusted(-m, -m, m, m));
}
}
if (r.contains(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->baseTextDocument()->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) {
Core::EditorManager::addCurrentPositionToNavigationHistory(editor(), 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 BaseTextEditorWidget::slotUpdateBlockNotify(const QTextBlock &block)
{
static bool blockRecursion = false;
if (blockRecursion)
return;
blockRecursion = true;
if (d->m_overlay->isVisible()) {
/* an overlay might draw outside the block bounderies, force
complete viewport update */
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 requestBlockUpdate(block.previous());
}
if (!d->m_findScopeStart.isNull()) {
if (block.position() < d->m_findScopeEnd.position()
&& block.position()+block.length() >= d->m_findScopeStart.position()) {
QTextBlock b = block.document()->findBlock(d->m_findScopeStart.position());
do {
emit requestBlockUpdate(b);
b = b.next();
} while (b.isValid() && b.position() < d->m_findScopeEnd.position());
}
}
}
blockRecursion = false;
}
void BaseTextEditorWidget::slotUpdateExtraArea()
{
d->m_extraArea->update();
}
void BaseTextEditorWidget::timerEvent(QTimerEvent *e)
2008-12-02 12:01:29 +01:00
{
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();
2008-12-02 12:01:29 +01:00
viewport()->update();
} else if (e->timerId() == d->m_cursorFlashTimer.timerId()) {
d->m_cursorVisible = !d->m_cursorVisible;
viewport()->update();
2008-12-02 12:01:29 +01:00
}
QPlainTextEdit::timerEvent(e);
}
void BaseTextEditorWidgetPrivate::clearVisibleFoldedBlock()
2008-12-02 12:01:29 +01:00
{
if (suggestedVisibleFoldedBlockNumber) {
suggestedVisibleFoldedBlockNumber = -1;
foldedBlockTimer.stop();
2008-12-02 12:01:29 +01:00
}
if (visibleFoldedBlockNumber >= 0) {
visibleFoldedBlockNumber = -1;
2008-12-02 12:01:29 +01:00
q->viewport()->update();
}
}
void BaseTextEditorWidget::mouseMoveEvent(QMouseEvent *e)
2008-12-02 12:01:29 +01:00
{
d->updateLink(e);
if (e->buttons() == Qt::NoButton) {
const QTextBlock collapsedBlock = d->foldedBlockAt(e->pos());
const int blockNumber = collapsedBlock.next().blockNumber();
2008-12-02 12:01:29 +01:00
if (blockNumber < 0) {
d->clearVisibleFoldedBlock();
} else if (blockNumber != d->visibleFoldedBlockNumber) {
d->suggestedVisibleFoldedBlockNumber = blockNumber;
d->foldedBlockTimer.start(40, this);
2008-12-02 12:01:29 +01:00
}
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);
}
2008-12-02 12:01:29 +01:00
} 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();
2008-12-02 12:01:29 +01:00
}
}
if (viewport()->cursor().shape() == Qt::BlankCursor)
viewport()->setCursor(Qt::IBeamCursor);
2008-12-02 12:01:29 +01:00
}
static bool handleForwardBackwardMouseButtons(QMouseEvent *e)
{
if (e->button() == Qt::XButton1) {
Core::EditorManager::goBackInNavigationHistory();
return true;
}
if (e->button() == Qt::XButton2) {
Core::EditorManager::goForwardInNavigationHistory();
return true;
}
return false;
}
void BaseTextEditorWidget::mousePressEvent(QMouseEvent *e)
2008-12-02 12:01:29 +01:00
{
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()) {
emit refactorMarkerClicked(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()));
}
2008-12-02 12:01:29 +01:00
}
if (HostOsInfo::isLinuxHost() && handleForwardBackwardMouseButtons(e))
return;
2008-12-02 12:01:29 +01:00
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
) {
Core::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, Core::Id menuContextId)
{
Core::ActionContainer *mcontext = Core::ActionManager::actionContainer(menuContextId);
QMenu *contextMenu = mcontext->menu();
foreach (QAction *action, contextMenu->actions())
menu->addAction(action);
}
void BaseTextEditorWidget::showDefaultContextMenu(QContextMenuEvent *e, Core::Id menuContextId)
{
QMenu menu;
appendMenuActionsFromContext(&menu, menuContextId);
appendStandardContextMenuActions(&menu);
menu.exec(e->globalPos());
}
void BaseTextEditorWidget::extraAreaLeaveEvent(QEvent *)
2008-12-02 12:01:29 +01:00
{
// fake missing mouse move event from Qt
QMouseEvent me(QEvent::MouseMove, QPoint(-1, -1), Qt::NoButton, 0, 0);
extraAreaMouseEvent(&me);
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::extraAreaContextMenuEvent(QContextMenuEvent *e)
{
QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
if (d->m_marksVisible) {
QMenu * contextMenu = new QMenu(this);
emit editor()->markContextMenuRequested(editor(), 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)
2008-12-02 12:01:29 +01:00
{
QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
int markWidth;
extraAreaWidth(&markWidth);
const bool inMarkArea = e->pos().x() <= markWidth && e->pos().x() >= 0;
2008-12-02 12:01:29 +01:00
if (d->m_codeFoldingVisible
&& e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking
updateFoldingHighlight(e->pos());
}
2008-12-02 12:01:29 +01:00
// 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 editor()->markTooltipRequested(editor(), 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);
}
2008-12-02 12:01:29 +01:00
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) {
2008-12-02 12:01:29 +01:00
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;
}
}
}
2008-12-02 12:01:29 +01:00
}
}
} 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;
}
}
}
2008-12-02 12:01:29 +01:00
}
int line = n + 1;
BaseTextEditor::MarkRequestKind kind;
if (QApplication::keyboardModifiers() & Qt::ShiftModifier)
kind = BaseTextEditor::BookmarkRequest;
else
kind = BaseTextEditor::BreakpointRequest;
emit editor()->markRequested(editor(), line, kind);
2008-12-02 12:01:29 +01:00
}
}
}
void BaseTextEditorWidget::ensureCursorVisible()
2008-12-02 12:01:29 +01:00
{
QTextBlock block = textCursor().block();
if (!block.isVisible()) {
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());
QTC_ASSERT(documentLayout, return);
// Open all parent folds of current line.
int indent = BaseTextDocumentLayout::foldingIndent(block);
block = block.previous();
while (block.isValid()) {
const int indent2 = BaseTextDocumentLayout::foldingIndent(block);
if (BaseTextDocumentLayout::canFold(block) && indent2 < indent) {
BaseTextDocumentLayout::doFoldOrUnfold(block, /* unfold = */ true);
if (block.isVisible())
break;
indent = indent2;
}
2008-12-02 12:01:29 +01:00
block = block.previous();
}
documentLayout->requestUpdate();
documentLayout->emitDocumentSizeChanged();
2008-12-02 12:01:29 +01:00
}
QPlainTextEdit::ensureCursorVisible();
}
void BaseTextEditorWidgetPrivate::toggleBlockVisible(const QTextBlock &block)
2008-12-02 12:01:29 +01:00
{
auto documentLayout = qobject_cast<BaseTextDocumentLayout*>(q->document()->documentLayout());
2008-12-09 15:25:01 +01:00
QTC_ASSERT(documentLayout, return);
2008-12-02 12:01:29 +01:00
BaseTextDocumentLayout::doFoldOrUnfold(block, BaseTextDocumentLayout::isFolded(block));
2008-12-02 12:01:29 +01:00
documentLayout->requestUpdate();
documentLayout->emitDocumentSizeChanged();
}
void BaseTextEditorWidget::setLanguageSettingsId(Core::Id settingsId)
{
d->m_tabSettingsId = settingsId;
}
Core::Id BaseTextEditorWidget::languageSettingsId() const
{
return d->m_tabSettingsId;
}
void BaseTextEditorWidget::setCodeStyle(ICodeStylePreferences *preferences)
{
baseTextDocument()->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
2008-12-02 12:01:29 +01:00
{
return d->m_displaySettings;
}
const MarginSettings &BaseTextEditorWidget::marginSettings() const
{
return d->m_marginSettings;
}
2008-12-02 12:01:29 +01:00
void BaseTextEditorWidgetPrivate::handleHomeKey(bool anchor)
2008-12-02 12:01:29 +01:00
{
QTextCursor cursor = q->textCursor();
2008-12-02 12:01:29 +01:00
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);
2008-12-02 12:01:29 +01:00
const QLatin1Char tab = QLatin1Char('\t');
while (character == tab || character.category() == QChar::Separator_Space) {
++pos;
if (pos == initpos)
break;
character = q->document()->characterAt(pos);
2008-12-02 12:01:29 +01:00
}
// 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);
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidgetPrivate::handleBackspaceKey()
2008-12-02 12:01:29 +01:00
{
QTextCursor cursor = q->textCursor();
2008-12-09 15:25:01 +01:00
QTC_ASSERT(!cursor.hasSelection(), return);
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
cursor.deletePreviousChar();
}
if (cursorWithinSnippet) {
cursor.endEditBlock();
m_snippetOverlay->updateEquivalentSelections(cursor);
2008-12-02 12:01:29 +01:00
}
q->setTextCursor(cursor);
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::wheelEvent(QWheelEvent *e)
2008-12-02 12:01:29 +01:00
{
d->clearVisibleFoldedBlock();
if (scrollWheelZoomingEnabled() && e->modifiers() & Qt::ControlModifier) {
2008-12-02 12:01:29 +01:00
const int delta = e->delta();
if (delta < 0)
zoomOut();
else if (delta > 0)
zoomIn();
return;
}
QPlainTextEdit::wheelEvent(e);
}
void BaseTextEditorWidget::zoomIn(int range)
2008-12-02 12:01:29 +01:00
{
d->clearVisibleFoldedBlock();
emit requestFontZoom(range*10);
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::zoomOut(int range)
2008-12-02 12:01:29 +01:00
{
zoomIn(-range);
}
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) {
Core::EditorManager::gotoOtherSplit();
} else if (baseTextDocument()->filePath() == link.targetFileName) {
Core::EditorManager::addCurrentPositionToNavigationHistory();
gotoLine(link.targetLine, link.targetColumn);
setFocus();
return true;
}
return Core::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->baseTextDocument()->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 BaseTextEditorWidget::highlightSearchResults(const QString &txt, Core::FindFlags findFlags)
2008-12-02 12:01:29 +01:00
{
if (d->m_searchExpr.pattern() == txt)
2008-12-02 12:01:29 +01:00
return;
d->m_searchExpr.setPattern(txt);
d->m_searchExpr.setPatternSyntax((findFlags & FindRegularExpression) ?
QRegExp::RegExp : QRegExp::FixedString);
d->m_searchExpr.setCaseSensitivity((findFlags & FindCaseSensitively) ?
2008-12-02 12:01:29 +01:00
Qt::CaseSensitive : Qt::CaseInsensitive);
d->m_findFlags = findFlags;
d->m_delayedUpdateTimer.start(50);
}
2008-12-02 12:01:29 +01:00
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();
2010-07-14 10:41:57 +02:00
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 BaseTextEditorWidget::setFindScope(const QTextCursor &start, const QTextCursor &end,
int verticalBlockSelectionFirstColumn,
int verticalBlockSelectionLastColumn)
2008-12-02 12:01:29 +01:00
{
if (start != d->m_findScopeStart
|| end != d->m_findScopeEnd
|| verticalBlockSelectionFirstColumn != d->m_findScopeVerticalBlockSelectionFirstColumn
|| verticalBlockSelectionLastColumn != d->m_findScopeVerticalBlockSelectionLastColumn) {
d->m_findScopeStart = start;
d->m_findScopeEnd = end;
d->m_findScopeVerticalBlockSelectionFirstColumn = verticalBlockSelectionFirstColumn;
d->m_findScopeVerticalBlockSelectionLastColumn = verticalBlockSelectionLastColumn;
2008-12-02 12:01:29 +01:00
viewport()->update();
}
}
void BaseTextEditorWidget::_q_animateUpdate(int position, QPointF lastPos, QRectF rect)
{
QTextCursor cursor(textCursor());
cursor.setPosition(position);
viewport()->update(QRectF(cursorRect(cursor).topLeft() + rect.topLeft(), rect.size()).toAlignedRect());
if (!lastPos.isNull())
viewport()->update(QRectF(lastPos + rect.topLeft(), rect.size()).toAlignedRect());
}
BaseTextEditorAnimator::BaseTextEditorAnimator(QObject *parent)
:QObject(parent)
{
m_value = 0;
m_timeline = new QTimeLine(256, this);
m_timeline->setCurveShape(QTimeLine::SineCurve);
connect(m_timeline, SIGNAL(valueChanged(qreal)), this, SLOT(step(qreal)));
connect(m_timeline, SIGNAL(finished()), this, SLOT(deleteLater()));
m_timeline->start();
}
void BaseTextEditorAnimator::setData(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);
}
2009-04-28 19:02:58 +02:00
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 BaseTextEditorWidget::_q_matchParentheses()
2008-12-02 12:01:29 +01:00
{
if (isReadOnly()
|| !(d->m_displaySettings.m_highlightMatchingParentheses
|| d->m_displaySettings.m_animateMatchingParentheses))
2008-12-02 12:01:29 +01:00
return;
QTextCursor backwardMatch = textCursor();
QTextCursor forwardMatch = textCursor();
if (overwriteMode())
backwardMatch.movePosition(QTextCursor::Right);
2008-12-02 12:01:29 +01:00
const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);
QList<QTextEdit::ExtraSelection> extraSelections;
2008-12-02 12:01:29 +01:00
if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch) {
setExtraSelections(ParenthesesMatchingSelection, extraSelections); // clear
return;
}
const QTextCharFormat &matchFormat
= baseTextDocument()->fontSettings().toTextCharFormat(C_PARENTHESES);
int animatePosition = -1;
2008-12-02 12:01:29 +01:00
if (backwardMatch.hasSelection()) {
QTextEdit::ExtraSelection sel;
if (backwardMatchType == TextBlockUserData::Mismatch) {
sel.cursor = backwardMatch;
sel.format = d->m_mismatchFormat;
extraSelections.append(sel);
2008-12-02 12:01:29 +01:00
} else {
sel.cursor = backwardMatch;
sel.format = matchFormat;
2008-12-02 12:01:29 +01:00
sel.cursor.setPosition(backwardMatch.selectionStart());
sel.cursor.setPosition(sel.cursor.position() + 1, QTextCursor::KeepAnchor);
2008-12-02 12:01:29 +01:00
extraSelections.append(sel);
if (d->m_displaySettings.m_animateMatchingParentheses && sel.cursor.block().isVisible())
animatePosition = backwardMatch.selectionStart();
2008-12-02 12:01:29 +01:00
sel.cursor.setPosition(backwardMatch.selectionEnd());
sel.cursor.setPosition(sel.cursor.position() - 1, QTextCursor::KeepAnchor);
extraSelections.append(sel);
2008-12-02 12:01:29 +01:00
}
}
if (forwardMatch.hasSelection()) {
QTextEdit::ExtraSelection sel;
if (forwardMatchType == TextBlockUserData::Mismatch) {
sel.cursor = forwardMatch;
sel.format = d->m_mismatchFormat;
extraSelections.append(sel);
2008-12-02 12:01:29 +01:00
} else {
sel.cursor = forwardMatch;
sel.format = matchFormat;
2008-12-02 12:01:29 +01:00
sel.cursor.setPosition(forwardMatch.selectionStart());
sel.cursor.setPosition(sel.cursor.position() + 1, QTextCursor::KeepAnchor);
2008-12-02 12:01:29 +01:00
extraSelections.append(sel);
sel.cursor.setPosition(forwardMatch.selectionEnd());
sel.cursor.setPosition(sel.cursor.position() - 1, QTextCursor::KeepAnchor);
extraSelections.append(sel);
if (d->m_displaySettings.m_animateMatchingParentheses && sel.cursor.block().isVisible())
animatePosition = forwardMatch.selectionEnd() - 1;
2008-12-02 12:01:29 +01:00
}
}
if (animatePosition >= 0) {
foreach (const QTextEdit::ExtraSelection &sel, this->extraSelections(ParenthesesMatchingSelection)) {
if (sel.cursor.selectionStart() == animatePosition
|| sel.cursor.selectionEnd() - 1 == animatePosition) {
animatePosition = -1;
break;
}
}
}
if (animatePosition >= 0) {
if (d->m_animator)
d->m_animator->finish(); // one animation is enough
d->m_animator = new BaseTextEditorAnimator(this);
d->m_animator->setPosition(animatePosition);
QPalette pal;
pal.setBrush(QPalette::Text, matchFormat.foreground());
pal.setBrush(QPalette::Base, matchFormat.background());
d->m_animator->setData(font(), pal, document()->characterAt(d->m_animator->position()));
connect(d->m_animator, SIGNAL(updateRequest(int,QPointF,QRectF)),
this, SLOT(_q_animateUpdate(int,QPointF,QRectF)));
}
if (d->m_displaySettings.m_highlightMatchingParentheses)
setExtraSelections(ParenthesesMatchingSelection, extraSelections);
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::_q_highlightBlocks()
{
BaseTextEditorPrivateHighlightBlocks highlightBlocksInfo;
QTextBlock block;
if (d->extraAreaHighlightFoldedBlockNumber >= 0) {
block = document()->findBlockByNumber(d->extraAreaHighlightFoldedBlockNumber);
if (block.isValid()
&& block.next().isValid()
&& BaseTextDocumentLayout::foldingIndent(block.next())
> BaseTextDocumentLayout::foldingIndent(block))
block = block.next();
}
QTextBlock closeBlock = block;
while (block.isValid()) {
int foldingIndent = BaseTextDocumentLayout::foldingIndent(block);
while (block.previous().isValid() && BaseTextDocumentLayout::foldingIndent(block) >= foldingIndent)
block = block.previous();
int nextIndent = BaseTextDocumentLayout::foldingIndent(block);
if (nextIndent == foldingIndent)
break;
highlightBlocksInfo.open.prepend(block.blockNumber());
while (closeBlock.next().isValid()
&& BaseTextDocumentLayout::foldingIndent(closeBlock.next()) >= foldingIndent )
closeBlock = closeBlock.next();
highlightBlocksInfo.close.append(closeBlock.blockNumber());
int visualIndent = qMin(d->visualIndent(block), d->visualIndent(closeBlock));
highlightBlocksInfo.visualIndent.prepend(visualIndent);
}
#if 0
if (block.isValid()) {
QTextCursor cursor(block);
if (d->extraAreaHighlightCollapseColumn >= 0)
cursor.setPosition(cursor.position() + qMin(d->extraAreaHighlightCollapseColumn,
block.length()-1));
QTextCursor closeCursor;
bool firstRun = true;
while (TextBlockUserData::findPreviousBlockOpenParenthesis(&cursor, firstRun)) {
firstRun = false;
highlightBlocksInfo.open.prepend(cursor.blockNumber());
int visualIndent = d->visualIndent(cursor.block());
if (closeCursor.isNull())
closeCursor = cursor;
if (TextBlockUserData::findNextBlockClosingParenthesis(&closeCursor)) {
highlightBlocksInfo.close.append(closeCursor.blockNumber());
visualIndent = qMin(visualIndent, d->visualIndent(closeCursor.block()));
}
highlightBlocksInfo.visualIndent.prepend(visualIndent);
}
}
#endif
if (d->m_highlightBlocksInfo != highlightBlocksInfo) {
d->m_highlightBlocksInfo = highlightBlocksInfo;
viewport()->update();
d->m_extraArea->update();
}
}
void BaseTextEditorWidget::changeEvent(QEvent *e)
2008-12-02 12:01:29 +01:00
{
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());
2008-12-02 12:01:29 +01:00
d->m_extraArea->setFont(f);
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()
2008-12-02 12:01:29 +01:00
{
QTextCursor cursor = q->textCursor();
2008-12-02 12:01:29 +01:00
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);
2008-12-02 12:01:29 +01:00
}
}
// shift+del
void BaseTextEditorWidget::cutLine()
{
d->maybeSelectLine();
2008-12-02 12:01:29 +01:00
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)
2008-12-02 12:01:29 +01:00
{
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);
}
2008-12-02 12:01:29 +01:00
}
QList<QTextEdit::ExtraSelection> BaseTextEditorWidget::extraSelections(ExtraSelectionKind kind) const
2008-12-02 12:01:29 +01:00
{
return d->m_extraSelections[kind];
2008-12-02 12:01:29 +01:00
}
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();
}
2008-12-02 12:01:29 +01:00
// the blocks list must be sorted
void BaseTextEditorWidget::setIfdefedOutBlocks(const QList<BlockRange> &blocks)
2008-12-02 12:01:29 +01:00
{
QTextDocument *doc = document();
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2008-12-09 15:25:01 +01:00
QTC_ASSERT(documentLayout, return);
2008-12-02 12:01:29 +01:00
bool needUpdate = false;
QTextBlock block = doc->firstBlock();
int rangeNumber = 0;
int braceDepthDelta = 0;
2008-12-02 12:01:29 +01:00
while (block.isValid()) {
bool cleared = false;
bool set = false;
2008-12-02 12:01:29 +01:00
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 = BaseTextDocumentLayout::setIfdefedOut(block);
else
cleared = BaseTextDocumentLayout::clearIfdefedOut(block);
if (block.contains(range.last()))
2008-12-02 12:01:29 +01:00
++rangeNumber;
} else {
cleared = BaseTextDocumentLayout::clearIfdefedOut(block);
2008-12-02 12:01:29 +01:00
}
if (cleared || set) {
needUpdate = true;
int delta = BaseTextDocumentLayout::braceDepthDelta(block);
if (cleared)
braceDepthDelta += delta;
else if (set)
braceDepthDelta -= delta;
}
if (braceDepthDelta) {
BaseTextDocumentLayout::changeBraceDepth(block,braceDepthDelta);
BaseTextDocumentLayout::changeFoldingIndent(block, braceDepthDelta); // ### C++ only, refactor!
}
2008-12-02 12:01:29 +01:00
block = block.next();
}
if (needUpdate)
documentLayout->requestUpdate();
}
void BaseTextEditorWidget::format()
{
QTextCursor cursor = textCursor();
cursor.beginEditBlock();
d->m_document->autoIndent(cursor);
cursor.endEditBlock();
}
2008-12-02 12:01:29 +01:00
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()
2008-12-02 12:01:29 +01:00
{
}
void BaseTextEditorWidget::showEvent(QShowEvent* e)
2009-06-17 19:12:19 +02:00
{
triggerPendingUpdates();
2009-06-17 19:12:19 +02:00
QPlainTextEdit::showEvent(e);
}
void BaseTextEditorWidget::applyFontSettingsDelayed()
2009-06-17 19:12:19 +02:00
{
d->m_fontSettingsNeedsApply = true;
if (isVisible())
triggerPendingUpdates();
}
void BaseTextEditorWidget::triggerPendingUpdates()
{
if (d->m_fontSettingsNeedsApply)
applyFontSettings();
baseTextDocument()->triggerPendingUpdates();
2009-06-17 19:12:19 +02:00
}
void BaseTextEditorWidget::applyFontSettings()
{
d->m_fontSettingsNeedsApply = false;
const FontSettings &fs = baseTextDocument()->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);
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);
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);
editor()->setFileEncodingLabelVisible(ds.m_displayFileEncoding);
if (d->m_displaySettings.m_visualizeWhitespace != ds.m_visualizeWhitespace) {
if (SyntaxHighlighter *highlighter = baseTextDocument()->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);
2008-12-02 12:01:29 +01:00
}
d->m_displaySettings = ds;
if (!ds.m_highlightBlocks) {
d->extraAreaHighlightFoldedBlockNumber = -1;
d->m_highlightBlocksInfo = BaseTextEditorPrivateHighlightBlocks();
}
2009-05-28 11:11:31 +02:00
updateCodeFoldingVisible();
d->updateHighlights();
2009-04-29 16:46:54 +02:00
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);
2008-12-02 12:01:29 +01:00
}
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()
2008-12-02 12:01:29 +01:00
{
QTextDocument *doc = document();
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2008-12-09 15:25:01 +01:00
QTC_ASSERT(documentLayout, return);
2008-12-02 12:01:29 +01:00
QTextBlock block = textCursor().block();
if (!(BaseTextDocumentLayout::canFold(block) && block.next().isVisible())) {
// find the closest previous block which can fold
int indent = BaseTextDocumentLayout::foldingIndent(block);
while (block.isValid() && (BaseTextDocumentLayout::foldingIndent(block) >= indent || !block.isVisible()))
block = block.previous();
2008-12-02 12:01:29 +01:00
}
if (block.isValid()) {
BaseTextDocumentLayout::doFoldOrUnfold(block, false);
2008-12-02 12:01:29 +01:00
d->moveCursorVisible();
documentLayout->requestUpdate();
documentLayout->emitDocumentSizeChanged();
}
}
void BaseTextEditorWidget::unfold()
2008-12-02 12:01:29 +01:00
{
QTextDocument *doc = document();
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2008-12-09 15:25:01 +01:00
QTC_ASSERT(documentLayout, return);
2008-12-02 12:01:29 +01:00
QTextBlock block = textCursor().block();
while (block.isValid() && !block.isVisible())
block = block.previous();
BaseTextDocumentLayout::doFoldOrUnfold(block, true);
2008-12-02 12:01:29 +01:00
d->moveCursorVisible();
documentLayout->requestUpdate();
documentLayout->emitDocumentSizeChanged();
}
void BaseTextEditorWidget::unfoldAll()
2008-12-02 12:01:29 +01:00
{
QTextDocument *doc = document();
BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2008-12-09 15:25:01 +01:00
QTC_ASSERT(documentLayout, return);
2008-12-02 12:01:29 +01:00
QTextBlock block = doc->firstBlock();
bool makeVisible = true;
while (block.isValid()) {
if (block.isVisible() && BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
2008-12-02 12:01:29 +01:00
makeVisible = false;
break;
}
block = block.next();
}
block = doc->firstBlock();
while (block.isValid()) {
if (BaseTextDocumentLayout::canFold(block))
BaseTextDocumentLayout::doFoldOrUnfold(block, makeVisible);
2008-12-02 12:01:29 +01:00
block = block.next();
}
d->moveCursorVisible();
documentLayout->requestUpdate();
documentLayout->emitDocumentSizeChanged();
centerCursor();
2008-12-02 12:01:29 +01:00
}
void BaseTextEditorWidget::setReadOnly(bool b)
2008-12-02 12:01:29 +01:00
{
QPlainTextEdit::setReadOnly(b);
emit readOnlyChanged();
2008-12-02 12:01:29 +01:00
if (b)
setTextInteractionFlags(textInteractionFlags() | Qt::TextSelectableByKeyboard);
}
void BaseTextEditorWidget::cut()
2008-12-02 12:01:29 +01:00
{
if (d->m_inBlockSelectionMode) {
copy();
d->removeBlockSelection();
return;
}
QPlainTextEdit::cut();
d->collectToCircularClipboard();
2008-12-02 12:01:29 +01:00
}
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()
{
baseTextDocument()->switchUtf8Bom();
}
QMimeData *BaseTextEditorWidget::createMimeDataFromSelection() const
2008-12-02 12:01:29 +01:00
{
if (d->m_inBlockSelectionMode) {
QMimeData *mimeData = new QMimeData;
mimeData->setText(d->copyBlockSelection());
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
}
return 0;
2008-12-02 12:01:29 +01:00
}
bool BaseTextEditorWidget::canInsertFromMimeData(const QMimeData *source) const
2008-12-02 12:01:29 +01:00
{
return QPlainTextEdit::canInsertFromMimeData(source);
}
void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source)
2008-12-02 12:01:29 +01:00
{
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);
2008-12-02 12:01:29 +01:00
}
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::appendStandardContextMenuActions(QMenu *menu)
{
menu->addSeparator();
QAction *a = Core::ActionManager::command(Core::Constants::CUT)->action();
if (a && a->isEnabled())
menu->addAction(a);
a = Core::ActionManager::command(Core::Constants::COPY)->action();
if (a && a->isEnabled())
menu->addAction(a);
a = Core::ActionManager::command(Core::Constants::PASTE)->action();
if (a && a->isEnabled())
menu->addAction(a);
a = Core::ActionManager::command(Constants::CIRCULAR_PASTE)->action();
if (a && a->isEnabled())
menu->addAction(a);
BaseTextDocument *doc = baseTextDocument();
if (doc->codec()->name() == QByteArray("UTF-8") && doc->supportsUtf8Bom()) {
a = Core::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(BaseTextEditorWidget *editor)
: d(new BaseTextEditorPrivate)
2008-12-02 12:01:29 +01:00
{
setWidget(editor);
d->m_editorWidget = editor;
2008-12-02 12:01:29 +01:00
Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
BaseTextFind *baseTextFind = new BaseTextFind(editor);
connect(baseTextFind, SIGNAL(highlightAll(QString,Core::FindFlags)),
editor, SLOT(highlightSearchResults(QString,Core::FindFlags)));
2011-02-16 10:02:30 +01:00
connect(baseTextFind, SIGNAL(findScopeChanged(QTextCursor,QTextCursor,int,int)),
editor, SLOT(setFindScope(QTextCursor,QTextCursor,int,int)));
2008-12-02 12:01:29 +01:00
aggregate->add(baseTextFind);
aggregate->add(editor);
d->m_cursorPositionLabel = new Utils::LineColumnLabel;
const int spacing = editor->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2;
d->m_cursorPositionLabel->setContentsMargins(spacing, 0, spacing, 0);
2008-12-02 12:01:29 +01:00
d->m_fileEncodingLabel = new Utils::LineColumnLabel;
d->m_fileEncodingLabel->setContentsMargins(spacing, 0, spacing, 0);
d->m_stretchWidget = new QWidget;
d->m_stretchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
2008-12-02 12:01:29 +01:00
d->m_toolBar = new QToolBar;
d->m_toolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
d->m_toolBar->addWidget(d->m_stretchWidget);
d->m_cursorPositionLabelAction = d->m_toolBar->addWidget(d->m_cursorPositionLabel);
d->m_fileEncodingLabelAction = d->m_toolBar->addWidget(d->m_fileEncodingLabel);
2008-12-02 12:01:29 +01:00
setFileEncodingLabelVisible(editor->displaySettings().m_displayFileEncoding);
2008-12-02 12:01:29 +01:00
connect(editor, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition()));
connect(d->m_cursorPositionLabel, SIGNAL(clicked()), this, SLOT(openGotoLocator()));
2008-12-02 12:01:29 +01:00
}
BaseTextEditor::~BaseTextEditor()
2008-12-02 12:01:29 +01:00
{
delete d->m_toolBar;
delete d->m_editorWidget;
delete d;
2008-12-02 12:01:29 +01:00
}
BaseTextDocument *BaseTextEditor::baseTextDocument()
{
return d->m_editorWidget->baseTextDocument();
}
IDocument *BaseTextEditor::document()
{
return d->m_editorWidget->baseTextDocument();
}
QWidget *BaseTextEditor::toolBar()
2008-12-02 12:01:29 +01:00
{
return d->m_toolBar;
2008-12-02 12:01:29 +01:00
}
void BaseTextEditor::insertExtraToolBarWidget(BaseTextEditor::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
2008-12-02 12:01:29 +01:00
{
return d->m_editorWidget->textCursor().blockNumber() + 1;
2008-12-02 12:01:29 +01:00
}
int BaseTextEditor::currentColumn() const
2008-12-02 12:01:29 +01:00
{
QTextCursor cursor = d->m_editorWidget->textCursor();
2008-12-02 12:01:29 +01:00
return cursor.position() - cursor.block().position() + 1;
}
void BaseTextEditor::gotoLine(int line, int column, bool centerLine)
{
d->m_editorWidget->gotoLine(line, column, centerLine);
}
int BaseTextEditor::columnCount() const
{
return d->m_editorWidget->columnCount();
}
int BaseTextEditor::rowCount() const
{
return d->m_editorWidget->rowCount();
}
int BaseTextEditor::position(BaseTextEditor::PositionOperation posOp, int at) const
{
return d->m_editorWidget->position(posOp, at);
}
void BaseTextEditor::convertPosition(int pos, int *line, int *column) const
{
d->m_editorWidget->convertPosition(pos, line, column);
}
QRect BaseTextEditor::cursorRect(int pos) const
2008-12-02 12:01:29 +01:00
{
QTextCursor tc = d->m_editorWidget->textCursor();
2008-12-02 12:01:29 +01:00
if (pos >= 0)
tc.setPosition(pos);
QRect result = d->m_editorWidget->cursorRect(tc);
result.moveTo(d->m_editorWidget->viewport()->mapToGlobal(result.topLeft()));
2008-12-02 12:01:29 +01:00
return result;
}
QString BaseTextEditor::selectedText() const
2008-12-02 12:01:29 +01:00
{
return d->m_editorWidget->selectedText();
2008-12-02 12:01:29 +01:00
}
void BaseTextEditor::remove(int length)
2008-12-02 12:01:29 +01:00
{
QTextCursor tc = d->m_editorWidget->textCursor();
2008-12-02 12:01:29 +01:00
tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
tc.removeSelectedText();
}
void BaseTextEditor::insert(const QString &string)
2008-12-02 12:01:29 +01:00
{
d->m_editorWidget->insertPlainText(string);
2008-12-02 12:01:29 +01:00
}
void BaseTextEditor::replace(int length, const QString &string)
2008-12-02 12:01:29 +01:00
{
QTextCursor tc = d->m_editorWidget->textCursor();
2008-12-02 12:01:29 +01:00
tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
tc.insertText(string);
}
void BaseTextEditor::setCursorPosition(int pos)
2008-12-02 12:01:29 +01:00
{
d->m_editorWidget->setBlockSelection(false);
QTextCursor tc = d->m_editorWidget->textCursor();
2008-12-02 12:01:29 +01:00
tc.setPosition(pos);
d->m_editorWidget->setTextCursor(tc);
2008-12-02 12:01:29 +01:00
}
void BaseTextEditor::select(int toPos)
2008-12-02 12:01:29 +01:00
{
d->m_editorWidget->setBlockSelection(false);
QTextCursor tc = d->m_editorWidget->textCursor();
2008-12-02 12:01:29 +01:00
tc.setPosition(toPos, QTextCursor::KeepAnchor);
d->m_editorWidget->setTextCursor(tc);
2008-12-02 12:01:29 +01:00
}
const CommentDefinition *BaseTextEditor::commentDefinition() const
{
return 0;
}
CompletionAssistProvider *BaseTextEditor::completionAssistProvider()
{
return 0;
}
QObject *BaseTextEditor::fileEncodingLabel() const
{
return d->m_fileEncodingLabel;
}
void BaseTextEditor::updateCursorPosition()
2008-12-02 12:01:29 +01:00
{
const QTextCursor cursor = d->m_editorWidget->textCursor();
2008-12-02 12:01:29 +01:00
const QTextBlock block = cursor.block();
const int line = block.blockNumber() + 1;
const int column = cursor.position() - block.position();
d->m_cursorPositionLabel->setText(tr("Line: %1, Col: %2").arg(line)
.arg(baseTextDocument()->tabSettings().columnAt(block.text(),
column)+1),
tr("Line: 9999, Col: 999"));
2008-12-02 12:01:29 +01:00
m_contextHelpId.clear();
if (!block.isVisible())
d->m_editorWidget->ensureCursorVisible();
2008-12-02 12:01:29 +01:00
}
void BaseTextEditor::openGotoLocator()
{
Core::EditorManager::activateEditor(this, Core::EditorManager::IgnoreNavigationHistory);
if (Core::Command *cmd = Core::ActionManager::command(Core::Constants::GOTO)) {
if (QAction *act = cmd->action())
act->trigger();
}
}
void BaseTextEditor::setFileEncodingLabelVisible(bool visible)
{
d->m_fileEncodingLabelAction->setVisible(visible);
}
void BaseTextEditor::setFileEncodingLabelText(const QString &text)
{
d->m_fileEncodingLabel->setText(text, text);
}
QString BaseTextEditor::contextHelpId() const
2008-12-02 12:01:29 +01:00
{
if (m_contextHelpId.isEmpty())
emit const_cast<BaseTextEditor*>(this)->contextHelpIdRequested(const_cast<BaseTextEditor*>(this),
d->m_editorWidget->textCursor().position());
2008-12-02 12:01:29 +01:00
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 BaseTextDocument *baseTextDocument) const
{
return cursor(baseTextDocument, true);
}
// returns a cursor which always has the correct position and anchor
QTextCursor BaseTextBlockSelection::cursor(const BaseTextDocument *baseTextDocument) const
{
return cursor(baseTextDocument, false);
}
QTextCursor BaseTextBlockSelection::cursor(const BaseTextDocument *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 BaseTextEditorWidget::inFindScope(const QTextCursor &cursor)
{
if (cursor.isNull())
return false;
return inFindScope(cursor.selectionStart(), cursor.selectionEnd());
}
bool BaseTextEditorWidget::inFindScope(int selectionStart, int selectionEnd)
{
if (d->m_findScopeStart.isNull())
return true; // no scope, everything is included
if (selectionStart < d->m_findScopeStart.position())
return false;
if (selectionEnd > d->m_findScopeEnd.position())
return false;
if (d->m_findScopeVerticalBlockSelectionFirstColumn < 0)
return true;
QTextBlock block = document()->findBlock(selectionStart);
if (block != document()->findBlock(selectionEnd))
return false;
QString text = block.text();
const TabSettings &ts = d->m_document->tabSettings();
int startPosition = ts.positionAtColumn(text, d->m_findScopeVerticalBlockSelectionFirstColumn);
int endPosition = ts.positionAtColumn(text, d->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 BaseTextEditorWidget::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(font()).width(QLatin1Char(' '));
QTextOption option = document()->defaultTextOption();
option.setTabStop(charWidth * d->m_document->tabSettings().m_tabSize);
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 = (text.*method)();
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 = (m_document->textAt(startPos, endPos - startPos).*method)();
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::invokeAssist(AssistKind kind, IAssistProvider *provider)
{
bool previousMode = overwriteMode();
setOverwriteMode(false);
ensureCursorVisible();
d->m_codeAssistant->invoke(kind, provider);
setOverwriteMode(previousMode);
}
IAssistInterface *BaseTextEditorWidget::createAssistInterface(AssistKind kind,
AssistReason reason) const
{
Q_UNUSED(kind);
return new DefaultAssistInterface(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)
{
return d->m_editorWidget->open(errorString, fileName, realFileName);
}
QByteArray BaseTextEditor::saveState() const
{
return d->m_editorWidget->saveState();
}
bool BaseTextEditor::restoreState(const QByteArray &state)
{
return d->m_editorWidget->restoreState(state);
}
BaseTextDocument *BaseTextEditor::textDocument()
{
return qobject_cast<BaseTextDocument *>(document());
}
BaseTextEditor *BaseTextEditor::currentTextEditor()
{
return qobject_cast<BaseTextEditor *>(Core::EditorManager::currentEditor());
}
BaseTextEditorWidget *BaseTextEditor::editorWidget() const
{
return d->m_editorWidget;
}
} // namespace TextEditor
#include "basetexteditor.moc"