1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "texteditor_global.h"
35 #include "basetextdocument.h"
36 #include "basetextdocumentlayout.h"
37 #include "basetexteditor_p.h"
38 #include "behaviorsettings.h"
39 #include "codecselector.h"
40 #include "completionsettings.h"
41 #include "completionsupport.h"
42 #include "tabsettings.h"
43 #include "texteditorconstants.h"
44 #include "texteditorplugin.h"
45 #include "syntaxhighlighter.h"
47 #include "tipcontents.h"
49 #include "autocompleter.h"
52 #include <aggregation/aggregate.h>
53 #include <coreplugin/actionmanager/actionmanager.h>
54 #include <coreplugin/actionmanager/actioncontainer.h>
55 #include <coreplugin/actionmanager/command.h>
56 #include <coreplugin/coreconstants.h>
57 #include <coreplugin/editormanager/editormanager.h>
58 #include <coreplugin/icore.h>
59 #include <coreplugin/manhattanstyle.h>
60 #include <coreplugin/uniqueidmanager.h>
61 #include <extensionsystem/pluginmanager.h>
62 #include <find/basetextfind.h>
63 #include <utils/linecolumnlabel.h>
64 #include <utils/qtcassert.h>
65 #include <utils/stylehelper.h>
67 #include <QtCore/QCoreApplication>
68 #include <QtCore/QTextCodec>
69 #include <QtCore/QFile>
70 #include <QtCore/QDebug>
71 #include <QtCore/QTimer>
72 #include <QtCore/QTimeLine>
73 #include <QtCore/QTime>
74 #include <QtGui/QAbstractTextDocumentLayout>
75 #include <QtGui/QApplication>
76 #include <QtGui/QKeyEvent>
77 #include <QtGui/QLabel>
78 #include <QtGui/QLayout>
79 #include <QtGui/QPainter>
80 #include <QtGui/QPrinter>
81 #include <QtGui/QPrintDialog>
82 #include <QtGui/QScrollBar>
83 #include <QtGui/QShortcut>
84 #include <QtGui/QStyle>
85 #include <QtGui/QSyntaxHighlighter>
86 #include <QtGui/QTextCursor>
87 #include <QtGui/QTextDocumentFragment>
88 #include <QtGui/QTextBlock>
89 #include <QtGui/QTextLayout>
90 #include <QtGui/QToolBar>
91 #include <QtGui/QInputDialog>
92 #include <QtGui/QMenu>
96 using namespace TextEditor;
97 using namespace TextEditor::Internal;
99 namespace TextEditor {
102 class TextEditExtraArea : public QWidget {
103 BaseTextEditorWidget *textEdit;
105 TextEditExtraArea(BaseTextEditorWidget *edit):QWidget(edit) {
107 setAutoFillBackground(true);
111 QSize sizeHint() const {
112 return QSize(textEdit->extraAreaWidth(), 0);
115 void paintEvent(QPaintEvent *event){
116 textEdit->extraAreaPaintEvent(event);
118 void mousePressEvent(QMouseEvent *event){
119 textEdit->extraAreaMouseEvent(event);
121 void mouseMoveEvent(QMouseEvent *event){
122 textEdit->extraAreaMouseEvent(event);
124 void mouseReleaseEvent(QMouseEvent *event){
125 textEdit->extraAreaMouseEvent(event);
127 void leaveEvent(QEvent *event){
128 textEdit->extraAreaLeaveEvent(event);
131 void wheelEvent(QWheelEvent *event) {
132 QCoreApplication::sendEvent(textEdit->viewport(), event);
136 } // namespace Internal
137 } // namespace TextEditor
139 ITextEditor *BaseTextEditorWidget::openEditorAt(const QString &fileName, int line, int column,
140 const QString &editorKind,
141 Core::EditorManager::OpenEditorFlags flags,
144 Core::EditorManager *editorManager = Core::EditorManager::instance();
145 editorManager->cutForwardNavigationHistory();
146 editorManager->addCurrentPositionToNavigationHistory();
147 Core::IEditor *editor = editorManager->openEditor(fileName, editorKind,
149 TextEditor::ITextEditor *texteditor = qobject_cast<TextEditor::ITextEditor *>(editor);
151 texteditor->gotoLine(line, column);
158 static void convertToPlainText(QString &txt)
160 QChar *uc = txt.data();
161 QChar *e = uc + txt.size();
163 for (; uc != e; ++uc) {
164 switch (uc->unicode()) {
165 case 0xfdd0: // QTextBeginningOfFrame
166 case 0xfdd1: // QTextEndOfFrame
167 case QChar::ParagraphSeparator:
168 case QChar::LineSeparator:
169 *uc = QLatin1Char('\n');
172 *uc = QLatin1Char(' ');
180 BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent)
181 : QPlainTextEdit(parent)
183 d = new BaseTextEditorPrivate;
185 d->m_extraArea = new TextEditExtraArea(this);
186 d->m_extraArea->setMouseTracking(true);
187 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
189 d->m_overlay = new TextEditorOverlay(this);
190 d->m_snippetOverlay = new TextEditorOverlay(this);
191 d->m_searchResultOverlay = new TextEditorOverlay(this);
192 d->m_refactorOverlay = new RefactorOverlay(this);
194 d->setupDocumentSignals(d->m_document);
196 d->m_lastScrollPos = -1;
200 setLayoutDirection(Qt::LeftToRight);
201 viewport()->setMouseTracking(true);
202 d->extraAreaSelectionAnchorBlockNumber
203 = d->extraAreaToggleMarkBlockNumber
204 = d->extraAreaHighlightFoldedBlockNumber
207 d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber = -1;
209 connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth()));
210 connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(slotModificationChanged(bool)));
211 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
212 connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(slotUpdateRequest(QRect, int)));
213 connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
215 // (void) new QShortcut(tr("CTRL+L"), this, SLOT(centerCursor()), 0, Qt::WidgetShortcut);
216 // (void) new QShortcut(tr("F9"), this, SLOT(slotToggleMark()), 0, Qt::WidgetShortcut);
217 // (void) new QShortcut(tr("F11"), this, SLOT(slotToggleBlockVisible()));
220 (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo()));
224 // parentheses matcher
225 d->m_formatRange = true;
226 d->m_matchFormat.setForeground(Qt::red);
227 d->m_rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
228 d->m_mismatchFormat.setBackground(Qt::magenta);
229 d->m_parenthesesMatchingTimer = new QTimer(this);
230 d->m_parenthesesMatchingTimer->setSingleShot(true);
231 connect(d->m_parenthesesMatchingTimer, SIGNAL(timeout()), this, SLOT(_q_matchParentheses()));
233 d->m_highlightBlocksTimer = new QTimer(this);
234 d->m_highlightBlocksTimer->setSingleShot(true);
235 connect(d->m_highlightBlocksTimer, SIGNAL(timeout()), this, SLOT(_q_highlightBlocks()));
237 d->m_requestAutoCompletionTimer = new QTimer(this);
238 d->m_requestAutoCompletionTimer->setSingleShot(true);
239 d->m_requestAutoCompletionTimer->setInterval(500);
240 connect(d->m_requestAutoCompletionTimer, SIGNAL(timeout()), this, SLOT(_q_requestAutoCompletion()));
244 d->m_searchResultFormat.setBackground(QColor(0xffef0b));
246 slotUpdateExtraAreaWidth();
248 setFrameStyle(QFrame::NoFrame);
250 d->m_delayedUpdateTimer = new QTimer(this);
251 d->m_delayedUpdateTimer->setSingleShot(true);
252 connect(d->m_delayedUpdateTimer, SIGNAL(timeout()), viewport(), SLOT(update()));
254 connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
255 this, SLOT(currentEditorChanged(Core::IEditor*)));
257 d->m_moveLineUndoHack = false;
260 BaseTextEditorWidget::~BaseTextEditorWidget()
266 QString BaseTextEditorWidget::mimeType() const
268 return d->m_document->mimeType();
271 void BaseTextEditorWidget::setMimeType(const QString &mt)
273 d->m_document->setMimeType(mt);
276 void BaseTextEditorWidget::print(QPrinter *printer)
278 const bool oldFullPage = printer->fullPage();
279 printer->setFullPage(true);
280 QPrintDialog *dlg = new QPrintDialog(printer, this);
281 dlg->setWindowTitle(tr("Print Document"));
282 if (dlg->exec() == QDialog::Accepted) {
285 printer->setFullPage(oldFullPage);
289 static int foldBoxWidth(const QFontMetrics &fm)
291 const int lineSpacing = fm.lineSpacing();
292 return lineSpacing + lineSpacing%2 + 1;
295 static void printPage(int index, QPainter *painter, const QTextDocument *doc,
296 const QRectF &body, const QRectF &titleBox,
297 const QString &title)
301 painter->translate(body.left(), body.top() - (index - 1) * body.height());
302 QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
304 QAbstractTextDocumentLayout *layout = doc->documentLayout();
305 QAbstractTextDocumentLayout::PaintContext ctx;
307 painter->setFont(QFont(doc->defaultFont()));
308 QRectF box = titleBox.translated(0, view.top());
309 int dpix = painter->device()->logicalDpiX();
310 int dpiy = painter->device()->logicalDpiY();
311 int mx = 5 * dpix / 72.0;
312 int my = 2 * dpiy / 72.0;
313 painter->fillRect(box.adjusted(-mx, -my, mx, my), QColor(210, 210, 210));
314 if (!title.isEmpty())
315 painter->drawText(box, Qt::AlignCenter, title);
316 const QString pageString = QString::number(index);
317 painter->drawText(box, Qt::AlignRight, pageString);
319 painter->setClipRect(view);
321 // don't use the system palette text as default text color, on HP/UX
322 // for example that's white, and white text on white paper doesn't
324 ctx.palette.setColor(QPalette::Text, Qt::black);
326 layout->draw(painter, ctx);
331 void BaseTextEditorPrivate::print(QPrinter *printer)
333 QTextDocument *doc = q->document();
335 QString title = q->displayName();
337 printer->setDocName(title);
342 // Check that there is a valid device to print to.
346 doc = doc->clone(doc);
348 QTextOption opt = doc->defaultTextOption();
349 opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
350 doc->setDefaultTextOption(opt);
352 (void)doc->documentLayout(); // make sure that there is a layout
355 QColor background = q->palette().color(QPalette::Base);
356 bool backgroundIsDark = background.value() < 128;
358 for (QTextBlock srcBlock = q->document()->firstBlock(), dstBlock = doc->firstBlock();
359 srcBlock.isValid() && dstBlock.isValid();
360 srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
363 QList<QTextLayout::FormatRange> formatList = srcBlock.layout()->additionalFormats();
364 if (backgroundIsDark) {
365 // adjust syntax highlighting colors for better contrast
366 for (int i = formatList.count() - 1; i >=0; --i) {
367 QTextCharFormat &format = formatList[i].format;
368 if (format.background().color() == background) {
369 QBrush brush = format.foreground();
370 QColor color = brush.color();
372 color.getHsv(&h, &s, &v, &a);
373 color.setHsv(h, s, qMin(128, v), a);
374 brush.setColor(color);
375 format.setForeground(brush);
377 format.setBackground(Qt::white);
381 dstBlock.layout()->setAdditionalFormats(formatList);
384 QAbstractTextDocumentLayout *layout = doc->documentLayout();
385 layout->setPaintDevice(p.device());
387 int dpiy = p.device()->logicalDpiY();
388 int margin = (int) ((2/2.54)*dpiy); // 2 cm margins
390 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
391 fmt.setMargin(margin);
392 doc->rootFrame()->setFrameFormat(fmt);
394 QRectF pageRect(printer->pageRect());
395 QRectF body = QRectF(0, 0, pageRect.width(), pageRect.height());
396 QFontMetrics fontMetrics(doc->defaultFont(), p.device());
398 QRectF titleBox(margin,
400 - fontMetrics.height()
402 body.width() - 2*margin,
403 fontMetrics.height());
404 doc->setPageSize(body.size());
408 if (printer->collateCopies() == true){
410 pageCopies = printer->numCopies();
412 docCopies = printer->numCopies();
416 int fromPage = printer->fromPage();
417 int toPage = printer->toPage();
418 bool ascending = true;
420 if (fromPage == 0 && toPage == 0) {
422 toPage = doc->pageCount();
425 fromPage = qMax(1, fromPage);
426 toPage = qMin(doc->pageCount(), toPage);
428 if (printer->pageOrder() == QPrinter::LastPageFirst) {
435 for (int i = 0; i < docCopies; ++i) {
439 for (int j = 0; j < pageCopies; ++j) {
440 if (printer->printerState() == QPrinter::Aborted
441 || printer->printerState() == QPrinter::Error)
443 printPage(page, &p, doc, body, titleBox, title);
444 if (j < pageCopies - 1)
459 if ( i < docCopies - 1)
468 int BaseTextEditorPrivate::visualIndent(const QTextBlock &block) const
470 if (!block.isValid())
472 const QTextDocument *document = block.document();
474 while (i < block.length()) {
475 if (!document->characterAt(block.position() + i).isSpace()) {
476 QTextCursor cursor(block);
477 cursor.setPosition(block.position() + i);
478 return q->cursorRect(cursor).x();
486 ITextMarkable *BaseTextEditorWidget::markableInterface() const
488 return baseTextDocument()->documentMarker();
491 BaseTextEditor *BaseTextEditorWidget::editor() const
494 d->m_editor = const_cast<BaseTextEditorWidget*>(this)->createEditor();
495 connect(this, SIGNAL(textChanged()),
496 d->m_editor, SIGNAL(contentsChanged()));
497 connect(this, SIGNAL(changed()),
498 d->m_editor, SIGNAL(changed()));
504 void BaseTextEditorWidget::currentEditorChanged(Core::IEditor *ed)
506 if (ed == editor()) {
507 if (d->m_document->hasDecodingError()) {
508 Core::EditorManager::instance()->showEditorInfoBar(QLatin1String(Constants::SELECT_ENCODING),
509 tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.")
510 .arg(displayName()).arg(QString::fromLatin1(d->m_document->codec()->name())),
511 tr("Select Encoding"),
512 this, SLOT(selectEncoding()));
517 void BaseTextEditorWidget::selectEncoding()
519 BaseTextDocument *doc = d->m_document;
520 CodecSelector codecSelector(this, doc);
522 switch (codecSelector.exec()) {
523 case CodecSelector::Reload:
524 doc->reload(codecSelector.selectedCodec());
525 setReadOnly(d->m_document->hasDecodingError());
526 if (doc->hasDecodingError())
527 currentEditorChanged(Core::EditorManager::instance()->currentEditor());
529 Core::EditorManager::instance()->hideEditorInfoBar(QLatin1String(Constants::SELECT_ENCODING));
531 case CodecSelector::Save:
532 doc->setCodec(codecSelector.selectedCodec());
533 Core::EditorManager::instance()->saveEditor(editor());
535 case CodecSelector::Cancel:
540 QString BaseTextEditorWidget::msgTextTooLarge(quint64 size)
542 return tr("The text is too large to be displayed (%1 MB).").
546 bool BaseTextEditorWidget::createNew(const QString &contents)
548 if (contents.size() > Core::EditorManager::maxTextFileSize()) {
549 setPlainText(msgTextTooLarge(contents.size()));
550 document()->setModified(false);
553 setPlainText(contents);
554 document()->setModified(false);
558 bool BaseTextEditorWidget::open(const QString &fileName)
560 if (d->m_document->open(fileName)) {
561 moveCursor(QTextCursor::Start);
562 setReadOnly(d->m_document->hasDecodingError());
569 Collapses the first comment in a file, if there is only whitespace above
571 void BaseTextEditorPrivate::foldLicenseHeader()
573 QTextDocument *doc = q->document();
574 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
575 QTC_ASSERT(documentLayout, return);
576 QTextBlock block = doc->firstBlock();
577 const TabSettings &ts = m_document->tabSettings();
578 while (block.isValid() && block.isVisible()) {
579 QString text = block.text();
580 if (BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
581 if (text.trimmed().startsWith(QLatin1String("/*"))) {
582 BaseTextDocumentLayout::doFoldOrUnfold(block, false);
584 documentLayout->requestUpdate();
585 documentLayout->emitDocumentSizeChanged();
589 if (ts.firstNonSpace(text) < text.size())
591 block = block.next();
595 const Utils::ChangeSet &BaseTextEditorWidget::changeSet() const
597 return d->m_changeSet;
600 void BaseTextEditorWidget::setChangeSet(const Utils::ChangeSet &changeSet)
602 using namespace Utils;
604 d->m_changeSet = changeSet;
606 foreach (const ChangeSet::EditOp &op, changeSet.operationList()) {
607 // ### TODO: process the edit operation
610 case ChangeSet::EditOp::Replace:
613 case ChangeSet::EditOp::Move:
616 case ChangeSet::EditOp::Insert:
619 case ChangeSet::EditOp::Remove:
622 case ChangeSet::EditOp::Flip:
625 case ChangeSet::EditOp::Copy:
634 Core::IFile *BaseTextEditorWidget::file()
636 return d->m_document;
639 void BaseTextEditorWidget::editorContentsChange(int position, int charsRemoved, int charsAdded)
642 d->m_animator->finish();
644 d->m_contentsChanged = true;
645 QTextDocument *doc = document();
647 // Keep the line numbers and the block information for the text marks updated
648 if (charsRemoved != 0) {
649 d->updateMarksLineNumber();
650 d->updateMarksBlock(document()->findBlock(position));
652 const QTextBlock posBlock = doc->findBlock(position);
653 const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
654 if (posBlock != nextBlock) {
655 d->updateMarksLineNumber();
656 d->updateMarksBlock(posBlock);
657 d->updateMarksBlock(nextBlock);
659 d->updateMarksBlock(posBlock);
663 if (d->m_snippetOverlay->isVisible()) {
664 QTextCursor cursor = textCursor();
665 cursor.setPosition(position);
666 d->snippetCheckCursor(cursor);
669 if (doc->isRedoAvailable())
670 emit editor()->contentsChangedBecauseOfUndo();
673 void BaseTextEditorWidget::slotSelectionChanged()
675 if (d->m_inBlockSelectionMode && !textCursor().hasSelection()) {
676 d->m_inBlockSelectionMode = false;
677 d->m_blockSelection.clear();
678 viewport()->update();
681 if (!d->m_selectBlockAnchor.isNull() && !textCursor().hasSelection())
682 d->m_selectBlockAnchor = QTextCursor();
684 // Clear any link which might be showing when the selection changes
688 void BaseTextEditorWidget::gotoBlockStart()
690 QTextCursor cursor = textCursor();
691 if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
692 setTextCursor(cursor);
693 _q_matchParentheses();
697 void BaseTextEditorWidget::gotoBlockEnd()
699 QTextCursor cursor = textCursor();
700 if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
701 setTextCursor(cursor);
702 _q_matchParentheses();
706 void BaseTextEditorWidget::gotoBlockStartWithSelection()
708 QTextCursor cursor = textCursor();
709 if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
710 setTextCursor(cursor);
711 _q_matchParentheses();
715 void BaseTextEditorWidget::gotoBlockEndWithSelection()
717 QTextCursor cursor = textCursor();
718 if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
719 setTextCursor(cursor);
720 _q_matchParentheses();
725 void BaseTextEditorWidget::gotoLineStart()
727 handleHomeKey(false);
730 void BaseTextEditorWidget::gotoLineStartWithSelection()
735 void BaseTextEditorWidget::gotoLineEnd()
737 moveCursor(QTextCursor::EndOfLine);
740 void BaseTextEditorWidget::gotoLineEndWithSelection()
742 moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
745 void BaseTextEditorWidget::gotoNextLine()
747 moveCursor(QTextCursor::Down);
750 void BaseTextEditorWidget::gotoNextLineWithSelection()
752 moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor);
755 void BaseTextEditorWidget::gotoPreviousLine()
757 moveCursor(QTextCursor::Up);
760 void BaseTextEditorWidget::gotoPreviousLineWithSelection()
762 moveCursor(QTextCursor::Up, QTextCursor::KeepAnchor);
765 void BaseTextEditorWidget::gotoPreviousCharacter()
767 moveCursor(QTextCursor::PreviousCharacter);
770 void BaseTextEditorWidget::gotoPreviousCharacterWithSelection()
772 moveCursor(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
775 void BaseTextEditorWidget::gotoNextCharacter()
777 moveCursor(QTextCursor::NextCharacter);
780 void BaseTextEditorWidget::gotoNextCharacterWithSelection()
782 moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
785 void BaseTextEditorWidget::gotoPreviousWord()
787 moveCursor(QTextCursor::PreviousWord);
790 void BaseTextEditorWidget::gotoPreviousWordWithSelection()
792 moveCursor(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
795 void BaseTextEditorWidget::gotoNextWord()
797 moveCursor(QTextCursor::NextWord);
800 void BaseTextEditorWidget::gotoNextWordWithSelection()
802 moveCursor(QTextCursor::NextWord, QTextCursor::KeepAnchor);
805 void BaseTextEditorWidget::gotoPreviousWordCamelCase()
807 QTextCursor c = textCursor();
808 camelCaseLeft(c, QTextCursor::MoveAnchor);
812 void BaseTextEditorWidget::gotoPreviousWordCamelCaseWithSelection()
814 QTextCursor c = textCursor();
815 camelCaseLeft(c, QTextCursor::KeepAnchor);
819 void BaseTextEditorWidget::gotoNextWordCamelCase()
821 qDebug() << Q_FUNC_INFO;
822 QTextCursor c = textCursor();
823 camelCaseRight(c, QTextCursor::MoveAnchor);
827 void BaseTextEditorWidget::gotoNextWordCamelCaseWithSelection()
829 QTextCursor c = textCursor();
830 camelCaseRight(c, QTextCursor::KeepAnchor);
836 static QTextCursor flippedCursor(const QTextCursor &cursor)
838 QTextCursor flipped = cursor;
839 flipped.clearSelection();
840 flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
844 void BaseTextEditorWidget::selectBlockUp()
846 QTextCursor cursor = textCursor();
847 if (!cursor.hasSelection())
848 d->m_selectBlockAnchor = cursor;
850 cursor.setPosition(cursor.selectionStart());
853 if (!TextBlockUserData::findPreviousOpenParenthesis(&cursor, false))
855 if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true))
857 setTextCursor(flippedCursor(cursor));
858 _q_matchParentheses();
861 void BaseTextEditorWidget::selectBlockDown()
863 QTextCursor tc = textCursor();
864 QTextCursor cursor = d->m_selectBlockAnchor;
866 if (!tc.hasSelection() || cursor.isNull())
868 tc.setPosition(tc.selectionStart());
871 QTextCursor ahead = cursor;
872 if (!TextBlockUserData::findPreviousOpenParenthesis(&ahead, false))
874 if (ahead.position() <= tc.position())
878 if ( cursor != d->m_selectBlockAnchor)
879 TextBlockUserData::findNextClosingParenthesis(&cursor, true);
881 setTextCursor(flippedCursor(cursor));
882 _q_matchParentheses();
885 void BaseTextEditorWidget::copyLineUp()
887 copyLineUpDown(true);
890 void BaseTextEditorWidget::copyLineDown()
892 copyLineUpDown(false);
895 // @todo: Potential reuse of some code around the following functions...
896 void BaseTextEditorWidget::copyLineUpDown(bool up)
898 QTextCursor cursor = textCursor();
899 QTextCursor move = cursor;
900 move.beginEditBlock();
902 bool hasSelection = cursor.hasSelection();
905 move.setPosition(cursor.selectionStart());
906 move.movePosition(QTextCursor::StartOfBlock);
907 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
908 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
909 QTextCursor::KeepAnchor);
911 move.movePosition(QTextCursor::StartOfBlock);
912 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
915 QString text = move.selectedText();
918 move.setPosition(cursor.selectionStart());
919 move.movePosition(QTextCursor::StartOfBlock);
921 move.movePosition(QTextCursor::Left);
923 move.movePosition(QTextCursor::EndOfBlock);
924 if (move.atBlockStart()) {
925 move.movePosition(QTextCursor::NextBlock);
927 move.movePosition(QTextCursor::Left);
933 int start = move.position();
934 move.clearSelection();
935 move.insertText(text);
936 int end = move.position();
938 move.setPosition(start);
939 move.setPosition(end, QTextCursor::KeepAnchor);
941 indent(document(), move, QChar::Null);
947 void BaseTextEditorWidget::joinLines()
949 QTextCursor cursor = textCursor();
950 QTextCursor start = cursor;
951 QTextCursor end = cursor;
953 start.setPosition(cursor.selectionStart());
954 end.setPosition(cursor.selectionEnd() - 1);
956 int lineCount = qMax(1, end.blockNumber() - start.blockNumber());
958 cursor.beginEditBlock();
959 cursor.setPosition(cursor.selectionStart());
960 while (lineCount--) {
961 cursor.movePosition(QTextCursor::NextBlock);
962 cursor.movePosition(QTextCursor::StartOfBlock);
963 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
964 QString cutLine = cursor.selectedText();
966 // Collapse leading whitespaces to one or insert whitespace
967 cutLine.replace(QRegExp("^\\s*"), " ");
968 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
969 cursor.removeSelectedText();
971 cursor.movePosition(QTextCursor::PreviousBlock);
972 cursor.movePosition(QTextCursor::EndOfBlock);
974 cursor.insertText(cutLine);
976 cursor.endEditBlock();
978 setTextCursor(cursor);
981 void BaseTextEditorWidget::insertLineAbove()
983 QTextCursor cursor = textCursor();
984 cursor.beginEditBlock();
985 cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
986 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
987 cursor.insertBlock();
988 indent(document(), cursor, QChar::Null);
989 cursor.endEditBlock();
990 setTextCursor(cursor);
993 void BaseTextEditorWidget::insertLineBelow()
995 QTextCursor cursor = textCursor();
996 cursor.beginEditBlock();
997 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
998 cursor.insertBlock();
999 indent(document(), cursor, QChar::Null);
1000 cursor.endEditBlock();
1001 setTextCursor(cursor);
1004 void BaseTextEditorWidget::moveLineUp()
1006 moveLineUpDown(true);
1009 void BaseTextEditorWidget::moveLineDown()
1011 moveLineUpDown(false);
1014 void BaseTextEditorWidget::uppercaseSelection()
1016 transformSelection(&QString::toUpper);
1020 void BaseTextEditorWidget::lowercaseSelection()
1022 transformSelection(&QString::toLower);
1025 void BaseTextEditorWidget::moveLineUpDown(bool up)
1027 QTextCursor cursor = textCursor();
1028 QTextCursor move = cursor;
1030 move.setVisualNavigation(false); // this opens folded items instead of destroying them
1032 if (d->m_moveLineUndoHack)
1033 move.joinPreviousEditBlock();
1035 move.beginEditBlock();
1037 bool hasSelection = cursor.hasSelection();
1039 if (cursor.hasSelection()) {
1040 move.setPosition(cursor.selectionStart());
1041 move.movePosition(QTextCursor::StartOfBlock);
1042 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
1043 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
1044 QTextCursor::KeepAnchor);
1046 move.movePosition(QTextCursor::StartOfBlock);
1047 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1049 QString text = move.selectedText();
1051 RefactorMarkers affectedMarkers;
1052 RefactorMarkers nonAffectedMarkers;
1053 QList<int> markerOffsets;
1055 foreach (const RefactorMarker &marker, d->m_refactorOverlay->markers()) {
1056 //test if marker is part of the selection to be moved
1057 if ((move.selectionStart() <= marker.cursor.position())
1058 && (move.selectionEnd() >= marker.cursor.position())) {
1059 affectedMarkers.append(marker);
1060 //remember the offset of markers in text
1061 int offset = marker.cursor.position() - move.selectionStart();
1062 markerOffsets.append(offset);
1064 nonAffectedMarkers.append(marker);
1068 move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
1069 move.removeSelectedText();
1072 move.movePosition(QTextCursor::PreviousBlock);
1074 move.movePosition(QTextCursor::Left);
1076 move.movePosition(QTextCursor::EndOfBlock);
1077 if (move.atBlockStart()) { // empty block
1078 move.movePosition(QTextCursor::NextBlock);
1080 move.movePosition(QTextCursor::Left);
1086 int start = move.position();
1087 move.clearSelection();
1088 move.insertText(text);
1089 int end = move.position();
1092 move.setPosition(start);
1093 move.setPosition(end, QTextCursor::KeepAnchor);
1096 //update positions of affectedMarkers
1097 for (int i=0;i < affectedMarkers.count(); i++) {
1098 int newPosition = start + markerOffsets.at(i);
1099 affectedMarkers[i].cursor.setPosition(newPosition);
1101 d->m_refactorOverlay->setMarkers(nonAffectedMarkers + affectedMarkers);
1103 reindent(document(), move);
1104 move.endEditBlock();
1106 setTextCursor(move);
1107 d->m_moveLineUndoHack = true;
1110 void BaseTextEditorWidget::cleanWhitespace()
1112 d->m_document->cleanWhitespace(textCursor());
1116 // could go into QTextCursor...
1117 static QTextLine currentTextLine(const QTextCursor &cursor)
1119 const QTextBlock block = cursor.block();
1120 if (!block.isValid())
1123 const QTextLayout *layout = block.layout();
1127 const int relativePos = cursor.position() - block.position();
1128 return layout->lineForTextPosition(relativePos);
1131 bool BaseTextEditorWidget::camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode)
1142 if (!cursor.movePosition(QTextCursor::Left, mode))
1146 QChar c = characterAt(cursor.position());
1147 Input input = Input_other;
1150 else if (c.isLower() || c.isDigit())
1152 else if (c == QLatin1Char('_'))
1153 input = Input_underscore;
1154 else if (c.isSpace() && c != QChar::ParagraphSeparator)
1155 input = Input_space;
1157 input = Input_other;
1168 case Input_underscore:
1175 cursor.movePosition(QTextCursor::Right, mode);
1176 return cursor.movePosition(QTextCursor::WordLeft, mode);
1184 cursor.movePosition(QTextCursor::Right, mode);
1196 cursor.movePosition(QTextCursor::Right, mode);
1202 case Input_underscore:
1211 cursor.movePosition(QTextCursor::Right, mode);
1225 case Input_underscore:
1229 cursor.movePosition(QTextCursor::Right, mode);
1230 if (cursor.positionInBlock() == 0)
1232 return cursor.movePosition(QTextCursor::WordLeft, mode);
1236 if (!cursor.movePosition(QTextCursor::Left, mode))
1241 bool BaseTextEditorWidget::camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode)
1253 QChar c = characterAt(cursor.position());
1254 Input input = Input_other;
1257 else if (c.isLower() || c.isDigit())
1259 else if (c == QLatin1Char('_'))
1260 input = Input_underscore;
1261 else if (c.isSpace() && c != QChar::ParagraphSeparator)
1262 input = Input_space;
1264 input = Input_other;
1275 case Input_underscore:
1279 return cursor.movePosition(QTextCursor::WordRight, mode);
1288 case Input_underscore:
1303 cursor.movePosition(QTextCursor::Left, mode);
1305 case Input_underscore:
1323 case Input_underscore:
1335 case Input_underscore:
1353 cursor.movePosition(QTextCursor::Right, mode);
1357 bool BaseTextEditorWidget::cursorMoveKeyEvent(QKeyEvent *e)
1359 QTextCursor cursor = textCursor();
1361 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
1362 QTextCursor::MoveOperation op = QTextCursor::NoMove;
1364 if (e == QKeySequence::MoveToNextChar) {
1365 op = QTextCursor::Right;
1367 else if (e == QKeySequence::MoveToPreviousChar) {
1368 op = QTextCursor::Left;
1370 else if (e == QKeySequence::SelectNextChar) {
1371 op = QTextCursor::Right;
1372 mode = QTextCursor::KeepAnchor;
1374 else if (e == QKeySequence::SelectPreviousChar) {
1375 op = QTextCursor::Left;
1376 mode = QTextCursor::KeepAnchor;
1378 else if (e == QKeySequence::SelectNextWord) {
1379 op = QTextCursor::WordRight;
1380 mode = QTextCursor::KeepAnchor;
1382 else if (e == QKeySequence::SelectPreviousWord) {
1383 op = QTextCursor::WordLeft;
1384 mode = QTextCursor::KeepAnchor;
1386 else if (e == QKeySequence::SelectStartOfLine) {
1387 op = QTextCursor::StartOfLine;
1388 mode = QTextCursor::KeepAnchor;
1390 else if (e == QKeySequence::SelectEndOfLine) {
1391 op = QTextCursor::EndOfLine;
1392 mode = QTextCursor::KeepAnchor;
1394 else if (e == QKeySequence::SelectStartOfBlock) {
1395 op = QTextCursor::StartOfBlock;
1396 mode = QTextCursor::KeepAnchor;
1398 else if (e == QKeySequence::SelectEndOfBlock) {
1399 op = QTextCursor::EndOfBlock;
1400 mode = QTextCursor::KeepAnchor;
1402 else if (e == QKeySequence::SelectStartOfDocument) {
1403 op = QTextCursor::Start;
1404 mode = QTextCursor::KeepAnchor;
1406 else if (e == QKeySequence::SelectEndOfDocument) {
1407 op = QTextCursor::End;
1408 mode = QTextCursor::KeepAnchor;
1410 else if (e == QKeySequence::SelectPreviousLine) {
1411 op = QTextCursor::Up;
1412 mode = QTextCursor::KeepAnchor;
1414 else if (e == QKeySequence::SelectNextLine) {
1415 op = QTextCursor::Down;
1416 mode = QTextCursor::KeepAnchor;
1418 QTextBlock block = cursor.block();
1419 QTextLine line = currentTextLine(cursor);
1420 if (!block.next().isValid()
1422 && line.lineNumber() == block.layout()->lineCount() - 1)
1423 op = QTextCursor::End;
1426 else if (e == QKeySequence::MoveToNextWord) {
1427 op = QTextCursor::WordRight;
1429 else if (e == QKeySequence::MoveToPreviousWord) {
1430 op = QTextCursor::WordLeft;
1432 else if (e == QKeySequence::MoveToEndOfBlock) {
1433 op = QTextCursor::EndOfBlock;
1435 else if (e == QKeySequence::MoveToStartOfBlock) {
1436 op = QTextCursor::StartOfBlock;
1438 else if (e == QKeySequence::MoveToNextLine) {
1439 op = QTextCursor::Down;
1441 else if (e == QKeySequence::MoveToPreviousLine) {
1442 op = QTextCursor::Up;
1444 else if (e == QKeySequence::MoveToPreviousLine) {
1445 op = QTextCursor::Up;
1447 else if (e == QKeySequence::MoveToStartOfLine) {
1448 op = QTextCursor::StartOfLine;
1450 else if (e == QKeySequence::MoveToEndOfLine) {
1451 op = QTextCursor::EndOfLine;
1453 else if (e == QKeySequence::MoveToStartOfDocument) {
1454 op = QTextCursor::Start;
1456 else if (e == QKeySequence::MoveToEndOfDocument) {
1457 op = QTextCursor::End;
1464 // Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but
1465 // here's the breakdown:
1466 // Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command),
1467 // Alt (Option), or Meta (Control).
1468 // Command/Control + Left/Right -- Move to left or right of the line
1469 // + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor)
1470 // Option + Left/Right -- Move one word Left/right.
1471 // + Up/Down -- Begin/End of Paragraph.
1472 // Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
1474 bool visualNavigation = cursor.visualNavigation();
1475 cursor.setVisualNavigation(true);
1477 if (op == QTextCursor::WordRight) {
1478 camelCaseRight(cursor, mode);
1479 } else if (op == QTextCursor::WordLeft) {
1480 camelCaseLeft(cursor, mode);
1482 cursor.movePosition(op, mode);
1484 cursor.setVisualNavigation(visualNavigation);
1486 setTextCursor(cursor);
1487 ensureCursorVisible();
1492 void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e)
1494 viewport()->setCursor(Qt::BlankCursor);
1495 ToolTip::instance()->hide();
1497 d->m_moveLineUndoHack = false;
1498 d->clearVisibleFoldedBlock();
1500 if (e->key() == Qt::Key_Escape) {
1501 if (d->m_snippetOverlay->isVisible()) {
1503 d->m_snippetOverlay->hide();
1504 d->m_snippetOverlay->clear();
1505 QTextCursor cursor = textCursor();
1506 cursor.clearSelection();
1507 setTextCursor(cursor);
1512 bool ro = isReadOnly();
1514 if (d->m_inBlockSelectionMode) {
1515 if (e == QKeySequence::Cut) {
1521 } else if (e == QKeySequence::Delete || e->key() == Qt::Key_Backspace) {
1523 d->removeBlockSelection();
1527 } else if (e == QKeySequence::Paste) {
1529 d->removeBlockSelection();
1537 && (e == QKeySequence::InsertParagraphSeparator
1538 || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))
1541 if (d->m_snippetOverlay->isVisible()) {
1543 d->m_snippetOverlay->hide();
1544 d->m_snippetOverlay->clear();
1545 QTextCursor cursor = textCursor();
1546 cursor.movePosition(QTextCursor::EndOfBlock);
1547 setTextCursor(cursor);
1552 QTextCursor cursor = textCursor();
1553 if (d->m_inBlockSelectionMode)
1554 cursor.clearSelection();
1555 const TabSettings &ts = d->m_document->tabSettings();
1556 cursor.beginEditBlock();
1559 d->m_autoCompleter->paragraphSeparatorAboutToBeInserted(cursor, tabSettings());
1561 QString previousIndentationString;
1562 if (ts.m_autoIndent) {
1563 cursor.insertBlock();
1564 indent(document(), cursor, QChar::Null);
1566 cursor.insertBlock();
1568 // After inserting the block, to avoid duplicating whitespace on the same line
1569 const QString &previousBlockText = cursor.block().previous().text();
1570 previousIndentationString = ts.indentationString(previousBlockText);
1571 if (!previousIndentationString.isEmpty())
1572 cursor.insertText(previousIndentationString);
1574 cursor.endEditBlock();
1577 if (extraBlocks > 0) {
1578 QTextCursor ensureVisible = cursor;
1579 while (extraBlocks > 0) {
1581 ensureVisible.movePosition(QTextCursor::NextBlock);
1582 if (ts.m_autoIndent)
1583 indent(document(), ensureVisible, QChar::Null);
1584 else if (!previousIndentationString.isEmpty())
1585 ensureVisible.insertText(previousIndentationString);
1587 setTextCursor(ensureVisible);
1590 setTextCursor(cursor);
1593 && (e == QKeySequence::MoveToStartOfBlock
1594 || e == QKeySequence::SelectStartOfBlock)){
1595 if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {
1599 handleHomeKey(e == QKeySequence::SelectStartOfBlock);
1603 && (e == QKeySequence::MoveToStartOfLine
1604 || e == QKeySequence::SelectStartOfLine)){
1605 if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {
1609 QTextCursor cursor = textCursor();
1610 if (QTextLayout *layout = cursor.block().layout()) {
1611 if (layout->lineForTextPosition(cursor.position() - cursor.block().position()).lineNumber() == 0) {
1612 handleHomeKey(e == QKeySequence::SelectStartOfLine);
1618 && e == QKeySequence::DeleteStartOfWord
1619 && d->m_document->tabSettings().m_autoIndent
1620 && !textCursor().hasSelection()){
1622 QTextCursor c = textCursor();
1623 int pos = c.position();
1624 camelCaseLeft(c, QTextCursor::MoveAnchor);
1625 int targetpos = c.position();
1627 handleBackspaceKey();
1628 int cpos = textCursor().position();
1629 if (cpos == pos || cpos <= targetpos)
1634 } else if (!ro && e == QKeySequence::DeleteStartOfWord && !textCursor().hasSelection()) {
1636 QTextCursor c = textCursor();
1637 camelCaseLeft(c, QTextCursor::KeepAnchor);
1638 c.removeSelectedText();
1640 } else if (!ro && e == QKeySequence::DeleteEndOfWord && !textCursor().hasSelection()) {
1642 QTextCursor c = textCursor();
1643 camelCaseRight(c, QTextCursor::KeepAnchor);
1644 c.removeSelectedText();
1646 } else switch (e->key()) {
1650 case Qt::Key_Dollar: {
1651 d->m_overlay->setVisible(!d->m_overlay->isVisible());
1652 d->m_overlay->setCursor(textCursor());
1659 case Qt::Key_Backtab: {
1661 if (d->m_snippetOverlay->isVisible() && !d->m_snippetOverlay->isEmpty()) {
1662 d->snippetTabOrBacktab(e->key() == Qt::Key_Tab);
1666 QTextCursor cursor = textCursor();
1668 if (d->m_document->tabSettings().tabShouldIndent(document(), cursor, &newPosition)) {
1669 if (newPosition != cursor.position() && !cursor.hasSelection()) {
1670 cursor.setPosition(newPosition);
1671 setTextCursor(cursor);
1673 indent(document(), cursor, QChar::Null);
1675 indentOrUnindent(e->key() == Qt::Key_Tab);
1680 case Qt::Key_Backspace:
1682 if ((e->modifiers() & (Qt::ControlModifier
1685 | Qt::MetaModifier)) == Qt::NoModifier
1686 && !textCursor().hasSelection()) {
1687 handleBackspaceKey();
1694 if (e->modifiers() & Qt::ControlModifier) {
1695 verticalScrollBar()->triggerAction(
1696 e->key() == Qt::Key_Up ? QAbstractSlider::SliderSingleStepSub :
1697 QAbstractSlider::SliderSingleStepAdd);
1705 if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {
1708 if (e->key() == Qt::Key_Up)
1710 else if (e->key() == Qt::Key_Down)
1712 else if (e->key() == Qt::Key_Left)
1714 else if (e->key() == Qt::Key_Right)
1716 handleBlockSelection(diff_row, diff_col);
1720 // leave block selection mode
1721 if (d->m_inBlockSelectionMode) {
1722 d->m_inBlockSelectionMode = false;
1723 d->m_blockSelection.clear();
1724 viewport()->update();
1729 case Qt::Key_PageUp:
1730 case Qt::Key_PageDown:
1731 if (e->modifiers() == Qt::ControlModifier) {
1732 verticalScrollBar()->triggerAction(
1733 e->key() == Qt::Key_PageUp ? QAbstractSlider::SliderPageStepSub :
1734 QAbstractSlider::SliderPageStepAdd);
1744 if (d->m_inBlockSelectionMode) {
1745 QString text = e->text();
1746 if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) {
1747 d->removeBlockSelection(text);
1752 if (e->key() == Qt::Key_H && e->modifiers() ==
1764 if (ro || e->text().isEmpty() || !e->text().at(0).isPrint()) {
1765 if (cursorMoveKeyEvent(e))
1768 QTextCursor cursor = textCursor();
1769 bool cursorWithinSnippet = false;
1770 if (d->m_snippetOverlay->isVisible()
1771 && (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)) {
1772 cursorWithinSnippet = d->snippetCheckCursor(cursor);
1774 if (cursorWithinSnippet)
1775 cursor.beginEditBlock();
1777 QPlainTextEdit::keyPressEvent(e);
1779 if (cursorWithinSnippet) {
1780 cursor.endEditBlock();
1781 d->m_snippetOverlay->updateEquivalentSelections(textCursor());
1784 } else if ((e->modifiers() & (Qt::ControlModifier|Qt::AltModifier)) != Qt::ControlModifier){
1785 QTextCursor cursor = textCursor();
1786 QString text = e->text();
1787 const QString &autoText = d->m_autoCompleter->autoComplete(cursor, text);
1790 if (d->m_document->tabSettings().m_autoIndent) {
1791 foreach (QChar c, text) {
1792 if (d->m_indenter->isElectricCharacter(c)) {
1799 bool cursorWithinSnippet = false;
1800 if (d->m_snippetOverlay->isVisible())
1801 cursorWithinSnippet = d->snippetCheckCursor(cursor);
1803 bool doEditBlock = !electricChar.isNull() || !autoText.isEmpty() || cursorWithinSnippet;
1805 cursor.beginEditBlock();
1807 cursor.insertText(text);
1809 if (!autoText.isEmpty()) {
1810 int pos = cursor.position();
1811 cursor.insertText(autoText);
1812 //Select the inserted text, to be able to re-indent the inserted text
1813 cursor.setPosition(pos, QTextCursor::KeepAnchor);
1815 if (!electricChar.isNull() && d->m_autoCompleter->contextAllowsElectricCharacters(cursor))
1816 indent(document(), cursor, electricChar);
1817 if (!autoText.isEmpty()) {
1818 if (d->m_document->tabSettings().m_autoIndent)
1819 reindent(document(), cursor);
1820 cursor.setPosition(autoText.length() == 1 ? cursor.position() : cursor.anchor());
1824 cursor.endEditBlock();
1825 if (cursorWithinSnippet)
1826 d->m_snippetOverlay->updateEquivalentSelections(textCursor());
1829 setTextCursor(cursor);
1833 if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled)
1834 d->m_parenthesesMatchingTimer->start(50);
1837 if (!ro && d->m_contentsChanged && !e->text().isEmpty() && e->text().at(0).isPrint()) {
1838 maybeRequestAutoCompletion(e->text().at(0));
1843 void BaseTextEditorWidget::maybeRequestAutoCompletion(const QChar &ch)
1845 if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) {
1846 if (CompletionSupport::instance()->isActive())
1847 d->m_requestAutoCompletionTimer->stop();
1849 d->m_requestAutoCompletionRevision = document()->revision();
1850 d->m_requestAutoCompletionPosition = position();
1851 d->m_requestAutoCompletionTimer->start();
1854 d->m_requestAutoCompletionTimer->stop();
1855 CompletionSupport::instance()->complete(editor(), SemanticCompletion, false);
1859 void BaseTextEditorWidget::_q_requestAutoCompletion()
1861 d->m_requestAutoCompletionTimer->stop();
1863 if (CompletionSupport::instance()->isActive())
1866 if (d->m_requestAutoCompletionRevision == document()->revision()
1867 && d->m_requestAutoCompletionPosition == position())
1868 CompletionSupport::instance()->complete(editor(), SemanticCompletion, false);
1871 void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet)
1873 if ((snippet.count(Snippet::kVariableDelimiter) % 2) != 0) {
1874 qWarning() << "invalid snippet";
1878 QList<QTextEdit::ExtraSelection> selections;
1880 QTextCursor cursor = cursor_arg;
1881 cursor.beginEditBlock();
1882 cursor.removeSelectedText();
1883 const int startCursorPosition = cursor.position();
1886 QMap<int, int> positions;
1888 while (pos < snippet.size()) {
1889 if (snippet.at(pos) != Snippet::kVariableDelimiter) {
1890 const int start = pos;
1892 while (pos < snippet.size() && snippet.at(pos) != Snippet::kVariableDelimiter);
1893 cursor.insertText(snippet.mid(start, pos - start));
1895 // the start of a place holder.
1896 const int start = ++pos;
1897 for (; pos < snippet.size(); ++pos) {
1898 if (snippet.at(pos) == Snippet::kVariableDelimiter)
1902 Q_ASSERT(pos < snippet.size());
1903 Q_ASSERT(snippet.at(pos) == Snippet::kVariableDelimiter);
1905 const QString textToInsert = snippet.mid(start, pos - start);
1907 int cursorPosition = cursor.position();
1908 cursor.insertText(textToInsert);
1910 if (textToInsert.isEmpty()) {
1911 positions.insert(cursorPosition, 0);
1913 positions.insert(cursorPosition, textToInsert.length());
1920 QMapIterator<int,int> it(positions);
1921 while (it.hasNext()) {
1923 int length = it.value();
1924 int position = it.key();
1926 QTextCursor tc(document());
1927 tc.setPosition(position);
1928 tc.setPosition(position + length, QTextCursor::KeepAnchor);
1929 QTextEdit::ExtraSelection selection;
1930 selection.cursor = tc;
1931 selection.format = (length ? d->m_occurrencesFormat : d->m_occurrenceRenameFormat);
1932 selections.append(selection);
1935 cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
1936 indent(cursor.document(), cursor, QChar());
1937 cursor.endEditBlock();
1939 setExtraSelections(BaseTextEditorWidget::SnippetPlaceholderSelection, selections);
1941 if (! selections.isEmpty()) {
1942 const QTextEdit::ExtraSelection &selection = selections.first();
1944 cursor = textCursor();
1945 if (selection.cursor.hasSelection()) {
1946 cursor.setPosition(selection.cursor.selectionStart());
1947 cursor.setPosition(selection.cursor.selectionEnd(), QTextCursor::KeepAnchor);
1949 cursor.setPosition(selection.cursor.position());
1951 setTextCursor(cursor);
1956 void BaseTextEditorWidget::universalHelper()
1958 // Test function for development. Place your new fangled experiment here to
1959 // give it proper scrutiny before pushing it onto others.
1962 void BaseTextEditorWidget::setTextCursor(const QTextCursor &cursor)
1964 // workaround for QTextControl bug
1965 bool selectionChange = cursor.hasSelection() || textCursor().hasSelection();
1966 QTextCursor c = cursor;
1967 c.setVisualNavigation(true);
1968 QPlainTextEdit::setTextCursor(c);
1969 if (selectionChange)
1970 slotSelectionChanged();
1973 void BaseTextEditorWidget::gotoLine(int line, int column)
1975 d->m_lastCursorChangeWasInteresting = false; // avoid adding the previous position to history
1976 const int blockNumber = line - 1;
1977 const QTextBlock &block = document()->findBlockByNumber(blockNumber);
1978 if (block.isValid()) {
1979 QTextCursor cursor(block);
1981 cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
1983 int pos = cursor.position();
1984 while (characterAt(pos).category() == QChar::Separator_Space) {
1987 cursor.setPosition(pos);
1989 setTextCursor(cursor);
1992 saveCurrentCursorPositionForNavigation();
1995 int BaseTextEditorWidget::position(ITextEditor::PositionOperation posOp, int at) const
1997 QTextCursor tc = textCursor();
2002 if (posOp == ITextEditor::Current)
2003 return tc.position();
2006 case ITextEditor::EndOfLine:
2007 tc.movePosition(QTextCursor::EndOfLine);
2008 return tc.position();
2009 case ITextEditor::StartOfLine:
2010 tc.movePosition(QTextCursor::StartOfLine);
2011 return tc.position();
2012 case ITextEditor::Anchor:
2013 if (tc.hasSelection())
2016 case ITextEditor::EndOfDoc:
2017 tc.movePosition(QTextCursor::End);
2018 return tc.position();
2026 void BaseTextEditorWidget::convertPosition(int pos, int *line, int *column) const
2028 QTextBlock block = document()->findBlock(pos);
2029 if (!block.isValid()) {
2033 (*line) = block.blockNumber() + 1;
2034 (*column) = pos - block.position();
2038 QChar BaseTextEditorWidget::characterAt(int pos) const
2040 return document()->characterAt(pos);
2043 bool BaseTextEditorWidget::event(QEvent *e)
2045 d->m_contentsChanged = false;
2046 switch (e->type()) {
2047 case QEvent::ShortcutOverride:
2048 if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && d->m_snippetOverlay->isVisible()) {
2052 e->ignore(); // we are a really nice citizen
2059 return QPlainTextEdit::event(e);
2062 void BaseTextEditorWidget::duplicateFrom(BaseTextEditorWidget *widget)
2066 setDisplayName(widget->displayName());
2067 d->m_revisionsVisible = widget->d->m_revisionsVisible;
2068 if (d->m_document == widget->d->m_document)
2070 d->setupDocumentSignals(widget->d->m_document);
2071 d->m_document = widget->d->m_document;
2074 QString BaseTextEditorWidget::displayName() const
2076 return d->m_displayName;
2079 void BaseTextEditorWidget::setDisplayName(const QString &title)
2081 d->m_displayName = title;
2085 BaseTextDocument *BaseTextEditorWidget::baseTextDocument() const
2087 return d->m_document;
2090 void BaseTextEditorWidget::setBaseTextDocument(BaseTextDocument *doc)
2093 d->setupDocumentSignals(doc);
2094 d->m_document = doc;
2098 void BaseTextEditorWidget::documentAboutToBeReloaded()
2100 //memorize cursor position
2101 d->m_tempState = saveState();
2103 // remove extra selections (loads of QTextCursor objects)
2105 for (int i = 0; i < NExtraSelectionKinds; ++i)
2106 d->m_extraSelections[i].clear();
2107 QPlainTextEdit::setExtraSelections(QList<QTextEdit::ExtraSelection>());
2109 // clear all overlays
2110 d->m_overlay->clear();
2111 d->m_snippetOverlay->clear();
2112 d->m_searchResultOverlay->clear();
2113 d->m_refactorOverlay->clear();
2116 void BaseTextEditorWidget::documentReloaded()
2118 // restore cursor position
2119 restoreState(d->m_tempState);
2122 QByteArray BaseTextEditorWidget::saveState() const
2125 QDataStream stream(&state, QIODevice::WriteOnly);
2126 stream << 1; // version number
2127 stream << verticalScrollBar()->value();
2128 stream << horizontalScrollBar()->value();
2130 convertPosition(textCursor().position(), &line, &column);
2134 // store code folding state
2135 QList<int> foldedBlocks;
2136 QTextBlock block = document()->firstBlock();
2137 while (block.isValid()) {
2138 if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->folded()) {
2139 int number = block.blockNumber();
2140 foldedBlocks += number;
2142 block = block.next();
2144 stream << foldedBlocks;
2149 bool BaseTextEditorWidget::restoreState(const QByteArray &state)
2151 if (state.isEmpty()) {
2152 if (d->m_displaySettings.m_autoFoldFirstComment)
2153 d->foldLicenseHeader();
2161 QDataStream stream(state);
2169 QList<int> collapsedBlocks;
2170 stream >> collapsedBlocks;
2171 QTextDocument *doc = document();
2172 foreach(int blockNumber, collapsedBlocks) {
2173 QTextBlock block = doc->findBlockByNumber(qMax(0, blockNumber));
2174 if (block.isValid())
2175 BaseTextDocumentLayout::doFoldOrUnfold(block, false);
2178 if (d->m_displaySettings.m_autoFoldFirstComment)
2179 d->foldLicenseHeader();
2182 d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history
2183 gotoLine(lval, cval);
2184 verticalScrollBar()->setValue(vval);
2185 horizontalScrollBar()->setValue(hval);
2186 saveCurrentCursorPositionForNavigation();
2190 void BaseTextEditorWidget::setDefaultPath(const QString &defaultPath)
2192 baseTextDocument()->setDefaultPath(defaultPath);
2195 void BaseTextEditorWidget::setSuggestedFileName(const QString &suggestedFileName)
2197 baseTextDocument()->setSuggestedFileName(suggestedFileName);
2200 void BaseTextEditorWidget::setParenthesesMatchingEnabled(bool b)
2202 d->m_parenthesesMatchingEnabled = b;
2205 bool BaseTextEditorWidget::isParenthesesMatchingEnabled() const
2207 return d->m_parenthesesMatchingEnabled;
2210 void BaseTextEditorWidget::setHighlightCurrentLine(bool b)
2212 d->m_highlightCurrentLine = b;
2213 updateCurrentLineHighlight();
2216 bool BaseTextEditorWidget::highlightCurrentLine() const
2218 return d->m_highlightCurrentLine;
2221 void BaseTextEditorWidget::setLineNumbersVisible(bool b)
2223 d->m_lineNumbersVisible = b;
2224 slotUpdateExtraAreaWidth();
2227 bool BaseTextEditorWidget::lineNumbersVisible() const
2229 return d->m_lineNumbersVisible;
2232 void BaseTextEditorWidget::setMarksVisible(bool b)
2234 d->m_marksVisible = b;
2235 slotUpdateExtraAreaWidth();
2238 bool BaseTextEditorWidget::marksVisible() const
2240 return d->m_marksVisible;
2243 void BaseTextEditorWidget::setRequestMarkEnabled(bool b)
2245 d->m_requestMarkEnabled = b;
2248 bool BaseTextEditorWidget::requestMarkEnabled() const
2250 return d->m_requestMarkEnabled;
2253 void BaseTextEditorWidget::setLineSeparatorsAllowed(bool b)
2255 d->m_lineSeparatorsAllowed = b;
2258 bool BaseTextEditorWidget::lineSeparatorsAllowed() const
2260 return d->m_lineSeparatorsAllowed;
2263 void BaseTextEditorWidget::updateCodeFoldingVisible()
2265 const bool visible = d->m_codeFoldingSupported && d->m_displaySettings.m_displayFoldingMarkers;
2266 if (d->m_codeFoldingVisible != visible) {
2267 d->m_codeFoldingVisible = visible;
2268 slotUpdateExtraAreaWidth();
2272 bool BaseTextEditorWidget::codeFoldingVisible() const
2274 return d->m_codeFoldingVisible;
2278 * Sets whether code folding is supported by the syntax highlighter. When not
2279 * supported (the default), this makes sure the code folding is not shown.
2281 * Needs to be called before calling setCodeFoldingVisible.
2283 void BaseTextEditorWidget::setCodeFoldingSupported(bool b)
2285 d->m_codeFoldingSupported = b;
2286 updateCodeFoldingVisible();
2289 bool BaseTextEditorWidget::codeFoldingSupported() const
2291 return d->m_codeFoldingSupported;
2294 void BaseTextEditorWidget::setMouseNavigationEnabled(bool b)
2296 d->m_behaviorSettings.m_mouseNavigation = b;
2299 bool BaseTextEditorWidget::mouseNavigationEnabled() const
2301 return d->m_behaviorSettings.m_mouseNavigation;
2304 void BaseTextEditorWidget::setScrollWheelZoomingEnabled(bool b)
2306 d->m_behaviorSettings.m_scrollWheelZooming = b;
2309 bool BaseTextEditorWidget::scrollWheelZoomingEnabled() const
2311 return d->m_behaviorSettings.m_scrollWheelZooming;
2314 void BaseTextEditorWidget::setRevisionsVisible(bool b)
2316 d->m_revisionsVisible = b;
2317 slotUpdateExtraAreaWidth();
2320 bool BaseTextEditorWidget::revisionsVisible() const
2322 return d->m_revisionsVisible;
2325 void BaseTextEditorWidget::setVisibleWrapColumn(int column)
2327 d->m_visibleWrapColumn = column;
2328 viewport()->update();
2331 int BaseTextEditorWidget::visibleWrapColumn() const
2333 return d->m_visibleWrapColumn;
2336 void BaseTextEditorWidget::setIndenter(Indenter *indenter)
2338 // clear out existing code formatter data
2339 for (QTextBlock it = document()->begin(); it.isValid(); it = it.next()) {
2340 TextEditor::TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(it);
2342 userData->setCodeFormatterData(0);
2344 d->m_indenter.reset(indenter);
2347 Indenter *BaseTextEditorWidget::indenter() const
2349 return d->m_indenter.data();
2352 void BaseTextEditorWidget::setAutoCompleter(AutoCompleter *autoCompleter)
2354 d->m_autoCompleter.reset(autoCompleter);
2357 AutoCompleter *BaseTextEditorWidget::autoCompleter() const
2359 return d->m_autoCompleter.data();
2362 //--------- BaseTextEditorPrivate -----------
2364 BaseTextEditorPrivate::BaseTextEditorPrivate()
2366 m_lastScrollPos(-1),
2369 m_contentsChanged(false),
2370 m_lastCursorChangeWasInteresting(false),
2371 m_document(new BaseTextDocument),
2372 m_parenthesesMatchingEnabled(false),
2374 m_formatRange(false),
2375 m_parenthesesMatchingTimer(0),
2377 extraAreaSelectionAnchorBlockNumber(-1),
2378 extraAreaToggleMarkBlockNumber(-1),
2379 extraAreaHighlightFoldedBlockNumber(-1),
2381 m_snippetOverlay(0),
2382 m_searchResultOverlay(0),
2383 m_refactorOverlay(0),
2384 visibleFoldedBlockNumber(-1),
2385 suggestedVisibleFoldedBlockNumber(-1),
2386 m_mouseOnFoldedMarker(false),
2387 m_marksVisible(false),
2388 m_codeFoldingVisible(false),
2389 m_codeFoldingSupported(false),
2390 m_revisionsVisible(false),
2391 m_lineNumbersVisible(true),
2392 m_highlightCurrentLine(true),
2393 m_requestMarkEnabled(true),
2394 m_lineSeparatorsAllowed(false),
2395 m_visibleWrapColumn(0),
2396 m_linkPressed(false),
2397 m_delayedUpdateTimer(0),
2400 m_inBlockSelectionMode(false),
2401 m_moveLineUndoHack(false),
2402 m_findScopeVerticalBlockSelectionFirstColumn(-1),
2403 m_findScopeVerticalBlockSelectionLastColumn(-1),
2404 m_highlightBlocksTimer(0),
2405 m_requestAutoCompletionRevision(0),
2406 m_requestAutoCompletionPosition(0),
2407 m_requestAutoCompletionTimer(0),
2408 m_cursorBlockNumber(-1),
2409 m_autoCompleter(new AutoCompleter),
2410 m_indenter(new Indenter)
2414 BaseTextEditorPrivate::~BaseTextEditorPrivate()
2418 void BaseTextEditorPrivate::setupDocumentSignals(BaseTextDocument *document)
2420 BaseTextDocument *oldDocument = q->baseTextDocument();
2422 q->disconnect(oldDocument->document(), 0, q, 0);
2423 q->disconnect(oldDocument, 0, q, 0);
2426 QTextDocument *doc = document->document();
2427 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2428 if (!documentLayout) {
2429 QTextOption opt = doc->defaultTextOption();
2430 opt.setTextDirection(Qt::LeftToRight);
2431 opt.setFlags(opt.flags() | QTextOption::IncludeTrailingSpaces
2432 | QTextOption::AddSpaceForLineAndParagraphSeparators
2434 doc->setDefaultTextOption(opt);
2435 documentLayout = new BaseTextDocumentLayout(doc);
2436 doc->setDocumentLayout(documentLayout);
2439 q->setDocument(doc);
2440 q->setCursorWidth(2); // Applies to the document layout
2442 QObject::connect(documentLayout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(slotUpdateBlockNotify(QTextBlock)));
2443 QObject::connect(q, SIGNAL(requestBlockUpdate(QTextBlock)), documentLayout, SIGNAL(updateBlock(QTextBlock)));
2444 QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(changed()));
2445 QObject::connect(doc, SIGNAL(contentsChange(int,int,int)), q,
2446 SLOT(editorContentsChange(int,int,int)), Qt::DirectConnection);
2447 QObject::connect(document, SIGNAL(changed()), q, SIGNAL(changed()));
2448 QObject::connect(document, SIGNAL(titleChanged(QString)), q, SLOT(setDisplayName(const QString &)));
2449 QObject::connect(document, SIGNAL(aboutToReload()), q, SLOT(documentAboutToBeReloaded()));
2450 QObject::connect(document, SIGNAL(reloaded()), q, SLOT(documentReloaded()));
2451 q->slotUpdateExtraAreaWidth();
2455 bool BaseTextEditorPrivate::snippetCheckCursor(const QTextCursor &cursor)
2457 if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
2460 QTextCursor start = cursor;
2461 start.setPosition(cursor.selectionStart());
2462 QTextCursor end = cursor;
2463 end.setPosition(cursor.selectionEnd());
2464 if (!m_snippetOverlay->hasCursorInSelection(start)
2465 || !m_snippetOverlay->hasCursorInSelection(end)
2466 || m_snippetOverlay->hasFirstSelectionBeginMoved()) {
2467 m_snippetOverlay->setVisible(false);
2468 m_snippetOverlay->clear();
2474 void BaseTextEditorPrivate::snippetTabOrBacktab(bool forward)
2476 if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
2478 QTextCursor cursor = q->textCursor();
2479 OverlaySelection final;
2481 for (int i = 0; i < m_snippetOverlay->selections().count(); ++i){
2482 const OverlaySelection &selection = m_snippetOverlay->selections().at(i);
2483 if (selection.m_cursor_begin.position() >= cursor.position()
2484 && selection.m_cursor_end.position() > cursor.position()) {
2490 for (int i = m_snippetOverlay->selections().count()-1; i >= 0; --i){
2491 const OverlaySelection &selection = m_snippetOverlay->selections().at(i);
2492 if (selection.m_cursor_end.position() < cursor.position()) {
2499 if (final.m_cursor_begin.isNull())
2500 final = forward ? m_snippetOverlay->selections().first() : m_snippetOverlay->selections().last();
2502 if (final.m_cursor_begin.position() == final.m_cursor_end.position()) { // empty tab stop
2503 cursor.setPosition(final.m_cursor_end.position());
2505 cursor.setPosition(final.m_cursor_begin.position());
2506 cursor.setPosition(final.m_cursor_end.position(), QTextCursor::KeepAnchor);
2508 q->setTextCursor(cursor);
2511 // Calculate global position for a tooltip considering the left extra area.
2512 QPoint BaseTextEditorWidget::toolTipPosition(const QTextCursor &c) const
2514 const QPoint cursorPos = mapToGlobal(cursorRect(c).bottomRight() + QPoint(1,1));
2515 return cursorPos + QPoint(d->m_extraArea->width(),
2524 bool BaseTextEditorWidget::viewportEvent(QEvent *event)
2526 d->m_contentsChanged = false;
2527 if (event->type() == QEvent::ContextMenu) {
2528 const QContextMenuEvent *ce = static_cast<QContextMenuEvent*>(event);
2529 if (ce->reason() == QContextMenuEvent::Mouse && !textCursor().hasSelection())
2530 setTextCursor(cursorForPosition(ce->pos()));
2531 } else if (event->type() == QEvent::ToolTip) {
2532 const QHelpEvent *he = static_cast<QHelpEvent*>(event);
2533 if (QApplication::keyboardModifiers() & Qt::ControlModifier)
2534 return true; // eat tooltip event when control is pressed
2535 const QPoint &pos = he->pos();
2537 RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(pos);
2538 if (refactorMarker.isValid() && !refactorMarker.tooltip.isEmpty()) {
2539 ToolTip::instance()->show(he->globalPos(),
2540 TextContent(refactorMarker.tooltip),
2542 refactorMarker.rect);
2546 // Allow plugins to show tooltips
2547 const QTextCursor c = cursorForPosition(pos);
2548 const QPoint toolTipPoint = toolTipPosition(c);
2549 bool handled = false;
2550 BaseTextEditor *ed = editor();
2551 emit ed->tooltipOverrideRequested(ed, toolTipPoint, c.position(), &handled);
2553 emit ed->tooltipRequested(ed, toolTipPoint, c.position());
2556 return QPlainTextEdit::viewportEvent(event);
2560 void BaseTextEditorWidget::resizeEvent(QResizeEvent *e)
2562 QPlainTextEdit::resizeEvent(e);
2564 d->m_extraArea->setGeometry(
2565 QStyle::visualRect(layoutDirection(), cr,
2566 QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height())));
2569 QRect BaseTextEditorWidget::foldBox()
2571 if (d->m_highlightBlocksInfo.isEmpty() || d->extraAreaHighlightFoldedBlockNumber < 0)
2574 QTextBlock begin = document()->findBlockByNumber(d->m_highlightBlocksInfo.open.last());
2576 QTextBlock end = document()->findBlockByNumber(d->m_highlightBlocksInfo.close.first());
2577 if (!begin.isValid() || !end.isValid())
2579 QRectF br = blockBoundingGeometry(begin).translated(contentOffset());
2580 QRectF er = blockBoundingGeometry(end).translated(contentOffset());
2582 return QRect(d->m_extraArea->width() - foldBoxWidth(fontMetrics()),
2584 foldBoxWidth(fontMetrics()),
2585 er.bottom() - br.top());
2588 QTextBlock BaseTextEditorWidget::foldedBlockAt(const QPoint &pos, QRect *box) const
2590 QPointF offset(contentOffset());
2591 QTextBlock block = firstVisibleBlock();
2592 qreal top = blockBoundingGeometry(block).translated(offset).top();
2593 qreal bottom = top + blockBoundingRect(block).height();
2595 int viewportHeight = viewport()->height();
2597 while (block.isValid() && top <= viewportHeight) {
2598 QTextBlock nextBlock = block.next();
2599 if (block.isVisible() && bottom >= 0) {
2600 if (nextBlock.isValid() && !nextBlock.isVisible()) {
2601 QTextLayout *layout = block.layout();
2602 QTextLine line = layout->lineAt(layout->lineCount()-1);
2603 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
2604 lineRect.adjust(0, 0, -1, -1);
2606 QRectF collapseRect(lineRect.right() + 12,
2608 fontMetrics().width(QLatin1String(" {...}; ")),
2610 if (collapseRect.contains(pos)) {
2611 QTextBlock result = block;
2613 *box = collapseRect.toAlignedRect();
2617 while (nextBlock.isValid() && !nextBlock.isVisible()) {
2619 nextBlock = block.next();
2627 bottom = top + blockBoundingRect(block).height();
2629 return QTextBlock();
2632 void BaseTextEditorPrivate::highlightSearchResults(const QTextBlock &block,
2633 TextEditorOverlay *overlay)
2635 if (m_searchExpr.isEmpty())
2638 int blockPosition = block.position();
2640 QTextCursor cursor = q->textCursor();
2641 QString text = block.text();
2642 text.replace(QChar::Nbsp, QLatin1Char(' '));
2646 while (idx < text.length()) {
2647 idx = m_searchExpr.indexIn(text, idx + l);
2650 l = m_searchExpr.matchedLength();
2653 if ((m_findFlags & Find::FindWholeWords)
2654 && ((idx && text.at(idx-1).isLetterOrNumber())
2655 || (idx + l < text.length() && text.at(idx + l).isLetterOrNumber())))
2658 if (!q->inFindScope(blockPosition + idx, blockPosition + idx + l))
2661 overlay->addOverlaySelection(blockPosition + idx,
2662 blockPosition + idx + l,
2663 m_searchResultFormat.background().color().darker(120),
2665 (idx == cursor.selectionStart() - blockPosition
2666 && idx + l == cursor.selectionEnd() - blockPosition)?
2667 TextEditorOverlay::DropShadow : 0);
2673 namespace TextEditor {
2674 namespace Internal {
2675 struct BlockSelectionData {
2685 void BaseTextEditorPrivate::clearBlockSelection()
2687 if (m_inBlockSelectionMode) {
2688 m_inBlockSelectionMode = false;
2689 m_blockSelection.clear();
2690 QTextCursor cursor = q->textCursor();
2691 cursor.clearSelection();
2692 q->setTextCursor(cursor);
2696 QString BaseTextEditorPrivate::copyBlockSelection()
2699 QTextCursor cursor = q->textCursor();
2700 if (!m_inBlockSelectionMode)
2702 const TabSettings &ts = q->tabSettings();
2703 QTextBlock block = m_blockSelection.firstBlock.block();
2704 QTextBlock lastBlock = m_blockSelection.lastBlock.block();
2706 QString text = block.text();
2707 int startOffset = 0;
2708 int startPos = ts.positionAtColumn(text, m_blockSelection.firstVisualColumn, &startOffset);
2710 int endPos = ts.positionAtColumn(text, m_blockSelection.lastVisualColumn, &endOffset);
2712 if (startPos == endPos) {
2713 selection += QString(endOffset - startOffset, QLatin1Char(' '));
2715 if (startOffset < 0)
2716 selection += QString(-startOffset, QLatin1Char(' '));
2719 selection += text.mid(startPos, endPos - startPos);
2720 if (endOffset < 0) {
2721 selection += QString(ts.m_tabSize + endOffset, QLatin1Char(' '));
2722 } else if (endOffset > 0) {
2723 selection += QString(endOffset, QLatin1Char(' '));
2726 if (block == lastBlock)
2728 selection += QLatin1Char('\n');
2729 block = block.next();
2734 void BaseTextEditorPrivate::removeBlockSelection(const QString &text)
2736 QTextCursor cursor = q->textCursor();
2737 if (!cursor.hasSelection() || !m_inBlockSelectionMode)
2740 int cursorPosition = cursor.selectionStart();
2741 cursor.clearSelection();
2742 cursor.beginEditBlock();
2744 const TabSettings &ts = q->tabSettings();
2745 QTextBlock block = m_blockSelection.firstBlock.block();
2746 QTextBlock lastBlock = m_blockSelection.lastBlock.block();
2748 QString text = block.text();
2749 int startOffset = 0;
2750 int startPos = ts.positionAtColumn(text, m_blockSelection.firstVisualColumn, &startOffset);
2752 int endPos = ts.positionAtColumn(text, m_blockSelection.lastVisualColumn, &endOffset);
2754 cursor.setPosition(block.position() + startPos);
2755 cursor.setPosition(block.position() + endPos, QTextCursor::KeepAnchor);
2756 cursor.removeSelectedText();
2758 if (startOffset < 0)
2759 cursor.insertText(QString(ts.m_tabSize + startOffset, QLatin1Char(' ')));
2761 cursor.insertText(QString(-endOffset, QLatin1Char(' ')));
2763 if (block == lastBlock)
2765 block = block.next();
2768 cursor.setPosition(cursorPosition);
2769 if (!text.isEmpty())
2770 cursor.insertText(text);
2771 cursor.endEditBlock();
2772 q->setTextCursor(cursor);
2775 void BaseTextEditorPrivate::moveCursorVisible(bool ensureVisible)
2777 QTextCursor cursor = q->textCursor();
2778 if (!cursor.block().isVisible()) {
2779 cursor.setVisualNavigation(true);
2780 cursor.movePosition(QTextCursor::Up);
2781 q->setTextCursor(cursor);
2784 q->ensureCursorVisible();
2787 static QColor blendColors(const QColor &a, const QColor &b, int alpha)
2789 return QColor((a.red() * (256 - alpha) + b.red() * alpha) / 256,
2790 (a.green() * (256 - alpha) + b.green() * alpha) / 256,
2791 (a.blue() * (256 - alpha) + b.blue() * alpha) / 256);
2794 static QColor calcBlendColor(const QColor &baseColor, int level, int count)
2799 if (baseColor.value() > 128) {
2802 color80.setRgb(qMax(0, baseColor.red() - f80),
2803 qMax(0, baseColor.green() - f80),
2804 qMax(0, baseColor.blue() - f80));
2805 color90.setRgb(qMax(0, baseColor.red() - f90),
2806 qMax(0, baseColor.green() - f90),
2807 qMax(0, baseColor.blue() - f90));
2811 color80.setRgb(qMin(255, baseColor.red() + f80),
2812 qMin(255, baseColor.green() + f80),
2813 qMin(255, baseColor.blue() + f80));
2814 color90.setRgb(qMin(255, baseColor.red() + f90),
2815 qMin(255, baseColor.green() + f90),
2816 qMin(255, baseColor.blue() + f90));
2823 if (level == count - 1)
2826 const int blendFactor = level * (256 / (count - 2));
2828 return blendColors(color80, color90, blendFactor);
2831 void BaseTextEditorWidget::paintEvent(QPaintEvent *e)
2834 Here comes an almost verbatim copy of
2835 QPlainTextEdit::paintEvent() so we can adjust the extra
2836 selections dynamically to indicate all search results.
2838 //begin QPlainTextEdit::paintEvent()
2840 QPainter painter(viewport());
2841 QTextDocument *doc = document();
2842 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2843 QTC_ASSERT(documentLayout, return);
2845 QPointF offset(contentOffset());
2846 QTextBlock textCursorBlock = textCursor().block();
2848 bool hasMainSelection = textCursor().hasSelection();
2849 bool suppressSyntaxInIfdefedOutBlock = (d->m_ifdefedOutFormat.foreground()
2850 != palette().foreground());
2852 QRect er = e->rect();
2853 QRect viewportRect = viewport()->rect();
2857 if (d->m_visibleWrapColumn > 0) {
2858 // Don't use QFontMetricsF::averageCharWidth here, due to it returning
2859 // a fractional size even when this is not supported by the platform.
2860 lineX = QFontMetricsF(font()).width(QLatin1Char('x')) * d->m_visibleWrapColumn + offset.x() + 4;
2862 if (lineX < viewportRect.width()) {
2863 const QBrush background = d->m_ifdefedOutFormat.background();
2864 painter.fillRect(QRectF(lineX, er.top(), viewportRect.width() - lineX, er.height()),
2867 const QColor col = (palette().base().color().value() > 128) ? Qt::black : Qt::white;
2868 const QPen pen = painter.pen();
2869 painter.setPen(blendColors(background.isOpaque() ? background.color() : palette().base().color(),
2871 painter.drawLine(QPointF(lineX, er.top()), QPointF(lineX, er.bottom()));
2872 painter.setPen(pen);
2876 // Set a brush origin so that the WaveUnderline knows where the wave started
2877 painter.setBrushOrigin(offset);
2879 // // keep right margin clean from full-width selection
2880 // int maxX = offset.x() + qMax((qreal)viewportRect.width(), documentLayout->documentSize().width())
2881 // - doc->documentMargin();
2882 // er.setRight(qMin(er.right(), maxX));
2883 // painter.setClipRect(er);
2885 bool editable = !isReadOnly();
2886 QTextBlock block = firstVisibleBlock();
2888 QAbstractTextDocumentLayout::PaintContext context = getPaintContext();
2890 if (!d->m_highlightBlocksInfo.isEmpty()) {
2891 const QColor baseColor = palette().base().color();
2893 // extra pass for the block highlight
2895 const int margin = 5;
2896 QTextBlock blockFP = block;
2897 QPointF offsetFP = offset;
2898 while (blockFP.isValid()) {
2899 QRectF r = blockBoundingRect(blockFP).translated(offsetFP);
2901 int n = blockFP.blockNumber();
2903 foreach (int i, d->m_highlightBlocksInfo.open)
2906 foreach (int i, d->m_highlightBlocksInfo.close)
2910 int count = d->m_highlightBlocksInfo.count();
2912 for (int i = 0; i <= depth; ++i) {
2913 const QColor &blendedColor = calcBlendColor(baseColor, i, count);
2914 int vi = i > 0 ? d->m_highlightBlocksInfo.visualIndent.at(i-1) : 0;
2916 oneRect.setWidth(viewport()->width());
2917 oneRect.adjust(vi, 0, -8*i, 0);
2918 if (oneRect.left() >= oneRect.right())
2920 if (lineX > 0 && oneRect.left() < lineX && oneRect.right() > lineX) {
2921 QRectF otherRect = r;
2922 otherRect.setLeft(lineX + 1);
2923 otherRect.setRight(oneRect.right());
2924 oneRect.setRight(lineX - 1);
2925 painter.fillRect(otherRect, blendedColor);
2927 painter.fillRect(oneRect, blendedColor);
2930 offsetFP.ry() += r.height();
2932 if (offsetFP.y() > viewportRect.height() + margin)
2935 blockFP = blockFP.next();
2936 if (!blockFP.isVisible()) {
2937 // invisible blocks do have zero line count
2938 blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
2943 int blockSelectionIndex = -1;
2945 if (d->m_inBlockSelectionMode
2946 && context.selections.count() && context.selections.last().cursor == textCursor()) {
2947 blockSelectionIndex = context.selections.size()-1;
2948 context.selections[blockSelectionIndex].format.clearBackground();
2951 QTextBlock visibleCollapsedBlock;
2952 QPointF visibleCollapsedBlockOffset;
2954 QTextLayout *cursor_layout = 0;
2955 QPointF cursor_offset;
2956 int cursor_cpos = 0;
2959 d->m_searchResultOverlay->clear();
2960 if (!d->m_searchExpr.isEmpty()) { // first pass for the search result overlays
2962 const int margin = 5;
2963 QTextBlock blockFP = block;
2964 QPointF offsetFP = offset;
2965 while (blockFP.isValid()) {
2966 QRectF r = blockBoundingRect(blockFP).translated(offsetFP);
2968 if (r.bottom() >= er.top() - margin && r.top() <= er.bottom() + margin) {
2969 d->highlightSearchResults(blockFP,
2970 d->m_searchResultOverlay);
2972 offsetFP.ry() += r.height();
2974 if (offsetFP.y() > viewportRect.height() + margin)
2977 blockFP = blockFP.next();
2978 if (!blockFP.isVisible()) {
2979 // invisible blocks do have zero line count
2980 blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
2987 { // extra pass for ifdefed out blocks
2988 QTextBlock blockIDO = block;
2989 QPointF offsetIDO = offset;
2990 while (blockIDO.isValid()) {
2992 QRectF r = blockBoundingRect(blockIDO).translated(offsetIDO);
2994 if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
2995 if (BaseTextDocumentLayout::ifdefedOut(blockIDO)) {
2997 rr.setRight(viewportRect.width() - offset.x());
2999 rr.setRight(qMin(lineX, rr.right()));
3000 painter.fillRect(rr, d->m_ifdefedOutFormat.background());
3003 offsetIDO.ry() += r.height();
3005 if (offsetIDO.y() > viewportRect.height())
3008 blockIDO = blockIDO.next();
3009 if (!blockIDO.isVisible()) {
3010 // invisible blocks do have zero line count
3011 blockIDO = doc->findBlockByLineNumber(blockIDO.firstLineNumber());
3017 // possible extra pass for the block selection find scope
3018 if (!d->m_findScopeStart.isNull() && d->m_findScopeVerticalBlockSelectionFirstColumn >= 0) {
3019 QTextBlock blockFS = block;
3020 QPointF offsetFS = offset;
3021 while (blockFS.isValid()) {
3023 QRectF r = blockBoundingRect(blockFS).translated(offsetFS);
3025 if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
3027 if (blockFS.position() >= d->m_findScopeStart.block().position()
3028 && blockFS.position() <= d->m_findScopeEnd.block().position()) {
3029 QTextLayout *layout = blockFS.layout();
3030 QString text = blockFS.text();
3031 const TabSettings &ts = tabSettings();
3032 qreal spacew = QFontMetricsF(font()).width(QLatin1Char(' '));
3035 int relativePos = ts.positionAtColumn(text,
3036 d->m_findScopeVerticalBlockSelectionFirstColumn,
3038 QTextLine line = layout->lineForTextPosition(relativePos);
3039 qreal x = line.cursorToX(relativePos) + offset * spacew;
3042 int erelativePos = ts.positionAtColumn(text,
3043 d->m_findScopeVerticalBlockSelectionLastColumn,
3045 QTextLine eline = layout->lineForTextPosition(erelativePos);
3046 qreal ex = eline.cursorToX(erelativePos) + eoffset * spacew;
3048 QRectF rr = line.naturalTextRect();
3049 rr.moveTop(rr.top() + r.top());
3050 rr.setLeft(r.left() + x);
3051 if (line.lineNumber() == eline.lineNumber()) {
3052 rr.setRight(r.left() + ex);
3054 painter.fillRect(rr, d->m_searchScopeFormat.background());
3056 QColor lineCol = d->m_searchScopeFormat.foreground().color();
3057 QPen pen = painter.pen();
3058 painter.setPen(lineCol);
3059 if (blockFS == d->m_findScopeStart.block())
3060 painter.drawLine(rr.topLeft(), rr.topRight());
3061 if (blockFS == d->m_findScopeEnd.block())
3062 painter.drawLine(rr.bottomLeft(), rr.bottomRight());
3063 painter.drawLine(rr.topLeft(), rr.bottomLeft());
3064 painter.drawLine(rr.topRight(), rr.bottomRight());
3065 painter.setPen(pen);
3068 offsetFS.ry() += r.height();
3070 if (offsetFS.y() > viewportRect.height())
3073 blockFS = blockFS.next();
3074 if (!blockFS.isVisible()) {
3075 // invisible blocks do have zero line count
3076 blockFS = doc->findBlockByLineNumber(blockFS.firstLineNumber());
3082 if (!d->m_findScopeStart.isNull() && d->m_findScopeVerticalBlockSelectionFirstColumn < 0) {
3084 TextEditorOverlay *overlay = new TextEditorOverlay(this);
3085 overlay->addOverlaySelection(d->m_findScopeStart.position(),
3086 d->m_findScopeEnd.position(),
3087 d->m_searchScopeFormat.foreground().color(),
3088 d->m_searchScopeFormat.background().color(),
3089 TextEditorOverlay::ExpandBegin);
3090 overlay->setAlpha(false);
3091 overlay->paint(&painter, e->rect());
3097 d->m_searchResultOverlay->fill(&painter,
3098 d->m_searchResultFormat.background().color(),
3102 while (block.isValid()) {
3104 QRectF r = blockBoundingRect(block).translated(offset);
3106 if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
3108 QTextLayout *layout = block.layout();
3110 QTextOption option = layout->textOption();
3111 if (suppressSyntaxInIfdefedOutBlock && BaseTextDocumentLayout::ifdefedOut(block)) {
3112 option.setFlags(option.flags() | QTextOption::SuppressColors);
3113 painter.setPen(d->m_ifdefedOutFormat.foreground().color());
3115 option.setFlags(option.flags() & ~QTextOption::SuppressColors);
3116 painter.setPen(context.palette.text().color());
3118 layout->setTextOption(option);
3119 layout->setFont(doc->defaultFont()); // this really should be in qplaintextedit when creating the layout!
3121 int blpos = block.position();
3122 int bllen = block.length();
3124 QVector<QTextLayout::FormatRange> selections;
3125 QVector<QTextLayout::FormatRange> prioritySelections;
3127 for (int i = 0; i < context.selections.size(); ++i) {
3128 const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
3129 const int selStart = range.cursor.selectionStart() - blpos;
3130 const int selEnd = range.cursor.selectionEnd() - blpos;
3131 if (selStart < bllen && selEnd >= 0
3132 && selEnd >= selStart) {
3133 QTextLayout::FormatRange o;
3135 o.length = selEnd - selStart;
3136 o.format = range.format;
3137 if (i == blockSelectionIndex) {
3138 QString text = block.text();
3139 const TabSettings &ts = tabSettings();
3140 o.start = ts.positionAtColumn(text, d->m_blockSelection.firstVisualColumn);
3141 o.length = ts.positionAtColumn(text, d->m_blockSelection.lastVisualColumn) - o.start;
3143 if ((hasMainSelection && i == context.selections.size()-1)
3144 || (o.format.foreground().style() == Qt::NoBrush
3145 && o.format.underlineStyle() != QTextCharFormat::NoUnderline
3146 && o.format.background() == Qt::NoBrush))
3147 prioritySelections.append(o);
3149 selections.append(o);
3152 // we disable fullwidth selection. It's only used for m_highlightCurrentLine which we
3153 // do differently now
3154 else if (!range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
3155 && block.contains(range.cursor.position())) {
3156 // for full width selections we don't require an actual selection, just
3157 // a position to specify the line. that's more convenience in usage.
3158 QTextLayout::FormatRange o;
3159 QTextLine l = layout->lineForTextPosition(range.cursor.position() - blpos);
3160 o.start = l.textStart();
3161 o.length = l.textLength();
3162 if (o.start + o.length == bllen - 1)
3163 ++o.length; // include newline
3164 o.format = range.format;
3165 selections.append(o);
3169 selections += prioritySelections;
3171 if (d->m_highlightCurrentLine && block == textCursorBlock) {
3173 QRectF rr = layout->lineForTextPosition(textCursor().positionInBlock()).rect();
3174 rr.moveTop(rr.top() + r.top());
3176 rr.setRight(viewportRect.width() - offset.x());
3177 QColor color = d->m_currentLineFormat.background().color();
3178 // set alpha, otherwise we cannot see block highlighting and find scope underneath
3179 color.setAlpha(128);
3180 painter.fillRect(rr, color);
3184 QRectF blockSelectionCursorRect;
3185 if (d->m_inBlockSelectionMode
3186 && block.position() >= d->m_blockSelection.firstBlock.block().position()
3187 && block.position() <= d->m_blockSelection.lastBlock.block().position()) {
3188 QString text = block.text();
3189 const TabSettings &ts = tabSettings();
3190 qreal spacew = QFontMetricsF(font()).width(QLatin1Char(' '));
3193 int relativePos = ts.positionAtColumn(text, d->m_blockSelection.firstVisualColumn, &offset);
3194 QTextLine line = layout->lineForTextPosition(relativePos);
3195 qreal x = line.cursorToX(relativePos) + offset * spacew;
3198 int erelativePos = ts.positionAtColumn(text, d->m_blockSelection.lastVisualColumn, &eoffset);
3199 QTextLine eline = layout->lineForTextPosition(erelativePos);
3200 qreal ex = eline.cursorToX(erelativePos) + eoffset * spacew;
3202 QRectF rr = line.naturalTextRect();
3203 rr.moveTop(rr.top() + r.top());
3204 rr.setLeft(r.left() + x);
3205 if (line.lineNumber() == eline.lineNumber()) {
3206 rr.setRight(r.left() + ex);
3208 painter.fillRect(rr, palette().highlight());
3209 if ((d->m_blockSelection.anchor == BaseTextBlockSelection::TopLeft
3210 && block == d->m_blockSelection.firstBlock.block())
3211 || (d->m_blockSelection.anchor == BaseTextBlockSelection::BottomLeft
3212 && block == d->m_blockSelection.lastBlock.block())
3214 rr.setRight(rr.left()+2);
3215 blockSelectionCursorRect = rr;
3217 for (int i = line.lineNumber() + 1; i < eline.lineNumber(); ++i) {
3218 rr = layout->lineAt(i).naturalTextRect();
3219 rr.moveTop(rr.top() + r.top());
3220 rr.setLeft(r.left() + x);
3221 painter.fillRect(rr, palette().highlight());
3224 rr = eline.naturalTextRect();
3225 rr.moveTop(rr.top() + r.top());
3226 rr.setRight(r.left() + ex);
3227 if (line.lineNumber() != eline.lineNumber())
3228 painter.fillRect(rr, palette().highlight());
3229 if ((d->m_blockSelection.anchor == BaseTextBlockSelection::TopRight
3230 && block == d->m_blockSelection.firstBlock.block())
3231 || (d->m_blockSelection.anchor == BaseTextBlockSelection::BottomRight
3232 && block == d->m_blockSelection.lastBlock.block())) {
3233 rr.setLeft(rr.right()-2);
3234 blockSelectionCursorRect = rr;
3239 bool drawCursor = ((editable || true) // we want the cursor in read-only mode
3240 && context.cursorPosition >= blpos
3241 && context.cursorPosition < blpos + bllen);
3243 bool drawCursorAsBlock = drawCursor && overwriteMode() ;
3245 if (drawCursorAsBlock) {
3246 int relativePos = context.cursorPosition - blpos;
3247 bool doSelection = true;
3248 QTextLine line = layout->lineForTextPosition(relativePos);
3249 qreal x = line.cursorToX(relativePos);
3251 if (relativePos < line.textLength() - line.textStart()) {
3252 w = line.cursorToX(relativePos + 1) - x;
3253 if (doc->characterAt(context.cursorPosition) == QLatin1Char('\t')) {
3254 doSelection = false;
3255 qreal space = QFontMetricsF(layout->font()).width(QLatin1Char(' '));
3262 w = QFontMetrics(layout->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()
3264 QRectF rr = line.rect();
3265 rr.moveTop(rr.top() + r.top());
3266 rr.moveLeft(r.left() + x);
3268 painter.fillRect(rr, palette().text());
3270 QTextLayout::FormatRange o;
3271 o.start = relativePos;
3273 o.format.setForeground(palette().base());
3274 selections.append(o);
3280 layout->draw(&painter, offset, selections, er);
3282 if ((drawCursor && !drawCursorAsBlock)
3283 || (editable && context.cursorPosition < -1
3284 && !layout->preeditAreaText().isEmpty())) {
3285 int cpos = context.cursorPosition;
3287 cpos = layout->preeditAreaPosition() - (cpos + 2);
3290 cursor_layout = layout;
3291 cursor_offset = offset;
3293 cursor_pen = painter.pen();
3296 #ifndef Q_WS_MAC // no visible cursor on mac
3297 if (blockSelectionCursorRect.isValid())
3298 painter.fillRect(blockSelectionCursorRect, palette().text());
3303 offset.ry() += r.height();
3305 if (offset.y() > viewportRect.height())
3308 block = block.next();
3310 if (!block.isVisible()) {
3311 if (block.blockNumber() == d->visibleFoldedBlockNumber) {
3312 visibleCollapsedBlock = block;
3313 visibleCollapsedBlockOffset = offset + QPointF(0,1);
3316 // invisible blocks do have zero line count
3317 block = doc->findBlockByLineNumber(block.firstLineNumber());
3320 painter.setPen(context.palette.text().color());
3322 if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom()
3323 && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) {
3324 painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background());
3327 //end QPlainTextEdit::paintEvent()
3329 offset = contentOffset();
3330 block = firstVisibleBlock();
3332 qreal top = blockBoundingGeometry(block).translated(offset).top();
3333 qreal bottom = top + blockBoundingRect(block).height();
3335 QTextCursor cursor = textCursor();
3336 bool hasSelection = cursor.hasSelection();
3337 int selectionStart = cursor.selectionStart();
3338 int selectionEnd = cursor.selectionEnd();
3341 while (block.isValid() && top <= e->rect().bottom()) {
3342 QTextBlock nextBlock = block.next();
3343 QTextBlock nextVisibleBlock = nextBlock;
3345 if (!nextVisibleBlock.isVisible()) {
3346 // invisible blocks do have zero line count
3347 nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
3348 // paranoia in case our code somewhere did not set the line count
3349 // of the invisible block to 0
3350 while (nextVisibleBlock.isValid() && !nextVisibleBlock.isVisible())
3351 nextVisibleBlock = nextVisibleBlock.next();
3353 if (block.isVisible() && bottom >= e->rect().top()) {
3354 if (d->m_displaySettings.m_visualizeWhitespace) {
3355 QTextLayout *layout = block.layout();
3356 int lineCount = layout->lineCount();
3357 if (lineCount >= 2 || !nextBlock.isValid()) {
3359 painter.setPen(Qt::lightGray);
3360 for (int i = 0; i < lineCount-1; ++i) { // paint line wrap indicator
3361 QTextLine line = layout->lineAt(i);
3362 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
3363 QChar visualArrow((ushort)0x21b5);
3364 painter.drawText(QPointF(lineRect.right(),
3365 lineRect.top() + line.ascent()),
3368 if (!nextBlock.isValid()) { // paint EOF symbol
3369 QTextLine line = layout->lineAt(lineCount-1);
3370 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
3372 lineRect.adjust(0, 0, -1, -1);
3374 QPointF pos(lineRect.topRight() + QPointF(h+4, line.ascent()));
3376 path.lineTo(pos + QPointF(-h, -h));
3377 path.lineTo(pos + QPointF(0, -2*h));
3378 path.lineTo(pos + QPointF(h, -h));
3379 path.closeSubpath();
3380 painter.setBrush(painter.pen().color());
3381 painter.drawPath(path);
3387 if (nextBlock.isValid() && !nextBlock.isVisible()) {
3389 bool selectThis = (hasSelection
3390 && nextBlock.position() >= selectionStart
3391 && nextBlock.position() < selectionEnd);
3394 painter.setBrush(palette().highlight());
3397 QTextLayout *layout = block.layout();
3398 QTextLine line = layout->lineAt(layout->lineCount()-1);
3399 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
3400 lineRect.adjust(0, 0, -1, -1);
3402 QRectF collapseRect(lineRect.right() + 12,
3404 fontMetrics().width(QLatin1String(" {...}; ")),
3406 painter.setRenderHint(QPainter::Antialiasing, true);
3407 painter.translate(.5, .5);
3408 painter.drawRoundedRect(collapseRect.adjusted(0, 0, 0, -1), 3, 3);
3409 painter.setRenderHint(QPainter::Antialiasing, false);
3410 painter.translate(-.5, -.5);
3412 QString replacement = QLatin1String("...");
3414 if (TextBlockUserData *nextBlockUserData = BaseTextDocumentLayout::testUserData(nextBlock)) {
3415 if (nextBlockUserData->foldingStartIncluded())
3416 replacement.prepend(nextBlock.text().trimmed().left(1));
3419 block = nextVisibleBlock.previous();
3420 if (!block.isValid())
3421 block = doc->lastBlock();
3423 if (TextBlockUserData *blockUserData = BaseTextDocumentLayout::testUserData(block)) {
3424 if (blockUserData->foldingEndIncluded()) {
3425 QString right = block.text().trimmed();
3426 if (right.endsWith(QLatin1Char(';'))) {
3428 right = right.trimmed();
3429 replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
3430 replacement.append(QLatin1Char(';'));
3432 replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
3438 painter.setPen(palette().highlightedText().color());
3439 painter.drawText(collapseRect, Qt::AlignCenter, replacement);
3445 block = nextVisibleBlock;
3447 bottom = top + blockBoundingRect(block).height();
3450 if (d->m_animator && d->m_animator->isRunning()) {
3451 QTextCursor cursor = textCursor();
3452 cursor.setPosition(d->m_animator->position());
3453 d->m_animator->draw(&painter, cursorRect(cursor).topLeft());
3456 // draw the overlays, but only if we do not have a find scope, otherwise the
3457 // view becomes too noisy.
3458 if (d->m_findScopeStart.isNull()) {
3459 if (d->m_overlay->isVisible())
3460 d->m_overlay->paint(&painter, e->rect());
3462 if (d->m_snippetOverlay->isVisible())
3463 d->m_snippetOverlay->paint(&painter, e->rect());
3465 if (!d->m_refactorOverlay->isEmpty())
3466 d->m_refactorOverlay->paint(&painter, e->rect());
3469 if (!d->m_searchResultOverlay->isEmpty()) {
3470 d->m_searchResultOverlay->paint(&painter, e->rect());
3471 d->m_searchResultOverlay->clear();
3475 // draw the cursor last, on top of everything
3476 if (cursor_layout && !d->m_inBlockSelectionMode) {
3477 painter.setPen(cursor_pen);
3478 cursor_layout->drawCursor(&painter, cursor_offset, cursor_cpos, cursorWidth());
3481 if (visibleCollapsedBlock.isValid()) {
3482 drawCollapsedBlockPopup(painter,
3483 visibleCollapsedBlock,
3484 visibleCollapsedBlockOffset,
3489 void BaseTextEditorWidget::drawCollapsedBlockPopup(QPainter &painter,
3490 const QTextBlock &block,
3494 int margin = block.document()->documentMargin();
3496 qreal blockHeight = 0;
3497 QTextBlock b = block;
3499 while (!b.isVisible()) {
3500 b.setVisible(true); // make sure block bounding rect works
3501 QRectF r = blockBoundingRect(b).translated(offset);
3503 QTextLayout *layout = b.layout();
3504 for (int i = layout->lineCount()-1; i >= 0; --i)
3505 maxWidth = qMax(maxWidth, layout->lineAt(i).naturalTextWidth() + 2*margin);
3507 blockHeight += r.height();
3509 b.setVisible(false); // restore previous state
3510 b.setLineCount(0); // restore 0 line count for invisible block
3515 painter.setRenderHint(QPainter::Antialiasing, true);
3516 painter.translate(.5, .5);
3517 QBrush brush = palette().base();
3518 if (d->m_ifdefedOutFormat.hasProperty(QTextFormat::BackgroundBrush))
3519 brush = d->m_ifdefedOutFormat.background();
3520 painter.setBrush(brush);
3521 painter.drawRoundedRect(QRectF(offset.x(),
3523 maxWidth, blockHeight).adjusted(0, 0, 0, 0), 3, 3);
3529 b.setVisible(true); // make sure block bounding rect works
3530 QRectF r = blockBoundingRect(b).translated(offset);
3531 QTextLayout *layout = b.layout();
3532 QVector<QTextLayout::FormatRange> selections;
3533 layout->draw(&painter, offset, selections, clip);
3535 b.setVisible(false); // restore previous state
3536 b.setLineCount(0); // restore 0 line count for invisible block
3537 offset.ry() += r.height();
3542 QWidget *BaseTextEditorWidget::extraArea() const
3544 return d->m_extraArea;
3547 int BaseTextEditorWidget::extraAreaWidth(int *markWidthPtr) const
3549 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());
3550 if (!documentLayout)
3553 if (!d->m_marksVisible && documentLayout->hasMarks)
3554 d->m_marksVisible = true;
3557 const QFontMetrics fm(d->m_extraArea->fontMetrics());
3559 if (d->m_lineNumbersVisible) {
3560 QFont fnt = d->m_extraArea->font();
3561 // this works under the assumption that bold or italic can only make a font wider
3562 fnt.setBold(d->m_currentLineNumberFormat.font().bold());
3563 fnt.setItalic(d->m_currentLineNumberFormat.font().italic());
3564 const QFontMetrics linefm(fnt);
3567 int max = qMax(1, blockCount());
3568 while (max >= 100) {
3572 space += linefm.width(QLatin1Char('9')) * digits;
3576 if (d->m_marksVisible) {
3577 markWidth += fm.lineSpacing();
3578 // if (documentLayout->doubleMarkCount)
3579 // markWidth += fm.lineSpacing() / 3;
3586 *markWidthPtr = markWidth;
3590 if (d->m_codeFoldingVisible)
3591 space += foldBoxWidth(fm);
3595 void BaseTextEditorWidget::slotUpdateExtraAreaWidth()
3597 if (isLeftToRight())
3598 setViewportMargins(extraAreaWidth(), 0, 0, 0);
3600 setViewportMargins(0, 0, extraAreaWidth(), 0);
3603 static void drawRectBox(QPainter *painter, const QRect &rect, bool start, bool end,
3604 const QPalette &pal)
3607 painter->setRenderHint(QPainter::Antialiasing, false);
3609 QRgb b = pal.base().color().rgb();
3610 QRgb h = pal.highlight().color().rgb();
3611 QColor c = Utils::StyleHelper::mergedColors(b,h, 50);
3613 QLinearGradient grad(rect.topLeft(), rect.topRight());
3614 grad.setColorAt(0, c.lighter(110));
3615 grad.setColorAt(1, c.lighter(130));
3618 painter->fillRect(rect, grad);
3619 painter->setPen(outline);
3621 painter->drawLine(rect.topLeft() + QPoint(1, 0), rect.topRight() - QPoint(1, 0));
3623 painter->drawLine(rect.bottomLeft() + QPoint(1, 0), rect.bottomRight() - QPoint(1, 0));
3625 painter->drawLine(rect.topRight() + QPoint(0, start ? 1 : 0), rect.bottomRight() - QPoint(0, end ? 1 : 0));
3626 painter->drawLine(rect.topLeft() + QPoint(0, start ? 1 : 0), rect.bottomLeft() - QPoint(0, end ? 1 : 0));
3631 void BaseTextEditorWidget::extraAreaPaintEvent(QPaintEvent *e)
3633 QTextDocument *doc = document();
3634 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
3635 QTC_ASSERT(documentLayout, return);
3637 int selStart = textCursor().selectionStart();
3638 int selEnd = textCursor().selectionEnd();
3640 QPalette pal = d->m_extraArea->palette();
3641 pal.setCurrentColorGroup(QPalette::Active);
3642 QPainter painter(d->m_extraArea);
3643 const QFontMetrics fm(d->m_extraArea->font());
3644 int fmLineSpacing = fm.lineSpacing();
3647 if (d->m_marksVisible)
3648 markWidth += fm.lineSpacing();
3650 const int collapseColumnWidth = d->m_codeFoldingVisible ? foldBoxWidth(fm): 0;
3651 const int extraAreaWidth = d->m_extraArea->width() - collapseColumnWidth;
3653 painter.fillRect(e->rect(), pal.color(QPalette::Base));
3654 painter.fillRect(e->rect().intersected(QRect(0, 0, extraAreaWidth, INT_MAX)),
3655 pal.color(QPalette::Background));
3657 QTextBlock block = firstVisibleBlock();
3658 int blockNumber = block.blockNumber();
3659 qreal top = blockBoundingGeometry(block).translated(contentOffset()).top();
3662 while (block.isValid() && top <= e->rect().bottom()) {
3665 const qreal height = blockBoundingRect(block).height();
3666 bottom = top + height;
3667 QTextBlock nextBlock = block.next();
3669 QTextBlock nextVisibleBlock = nextBlock;
3670 int nextVisibleBlockNumber = blockNumber + 1;
3672 if (!nextVisibleBlock.isVisible()) {
3673 // invisible blocks do have zero line count
3674 nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
3675 nextVisibleBlockNumber = nextVisibleBlock.blockNumber();
3678 if (bottom < e->rect().top()) {
3679 block = nextVisibleBlock;
3680 blockNumber = nextVisibleBlockNumber;
3684 painter.setPen(pal.color(QPalette::Dark));
3686 if (d->m_codeFoldingVisible || d->m_marksVisible) {
3688 painter.setRenderHint(QPainter::Antialiasing, false);
3690 int previousBraceDepth = block.previous().userState();
3691 if (previousBraceDepth >= 0)
3692 previousBraceDepth >>= 8;
3694 previousBraceDepth = 0;
3696 int braceDepth = block.userState();
3697 if (!nextBlock.isVisible()) {
3698 QTextBlock lastInvisibleBlock = nextVisibleBlock.previous();
3699 if (!lastInvisibleBlock.isValid())
3700 lastInvisibleBlock = doc->lastBlock();
3701 braceDepth = lastInvisibleBlock.userState();
3703 if (braceDepth >= 0)
3708 if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) {
3709 if (d->m_marksVisible) {
3711 foreach (ITextMark *mrk, userData->marks()) {
3713 int radius = fmLineSpacing - 1;
3714 QRect r(x + xoffset, top, radius, radius);
3715 mrk->icon().paint(&painter, r, Qt::AlignCenter);
3721 if (d->m_codeFoldingVisible) {
3723 int extraAreaHighlightFoldBlockNumber = -1;
3724 int extraAreaHighlightFoldEndBlockNumber = -1;
3725 bool endIsVisible = false;
3726 if (!d->m_highlightBlocksInfo.isEmpty()) {
3727 extraAreaHighlightFoldBlockNumber = d->m_highlightBlocksInfo.open.last();
3728 extraAreaHighlightFoldEndBlockNumber = d->m_highlightBlocksInfo.close.first();
3729 endIsVisible = doc->findBlockByNumber(extraAreaHighlightFoldEndBlockNumber).isVisible();
3731 // QTextBlock before = doc->findBlockByNumber(extraAreaHighlightCollapseBlockNumber-1);
3732 // if (TextBlockUserData::hasCollapseAfter(before)) {
3733 // extraAreaHighlightCollapseBlockNumber--;
3737 TextBlockUserData *nextBlockUserData = BaseTextDocumentLayout::testUserData(nextBlock);
3739 bool drawBox = nextBlockUserData
3740 && BaseTextDocumentLayout::foldingIndent(block) < nextBlockUserData->foldingIndent();
3744 bool active = blockNumber == extraAreaHighlightFoldBlockNumber;
3746 bool drawStart = active;
3747 bool drawEnd = blockNumber == extraAreaHighlightFoldEndBlockNumber || (drawStart && !endIsVisible);
3748 bool hovered = blockNumber >= extraAreaHighlightFoldBlockNumber
3749 && blockNumber <= extraAreaHighlightFoldEndBlockNumber;
3751 int boxWidth = foldBoxWidth(fm);
3753 int itop = qRound(top);
3754 int ibottom = qRound(bottom);
3755 QRect box = QRect(extraAreaWidth + 1, itop, boxWidth - 2, ibottom - itop);
3756 drawRectBox(&painter, box, drawStart, drawEnd, pal);
3760 bool expanded = nextBlock.isVisible();
3761 int size = boxWidth/4;
3762 QRect box(extraAreaWidth + size, top + size,
3763 2 * (size) + 1, 2 * (size) + 1);
3764 drawFoldingMarker(&painter, pal, box, expanded, active, hovered);
3772 if (d->m_revisionsVisible && block.revision() != documentLayout->lastSaveRevision) {
3774 painter.setRenderHint(QPainter::Antialiasing, false);
3775 if (block.revision() < 0)
3776 painter.setPen(QPen(Qt::darkGreen, 2));
3778 painter.setPen(QPen(Qt::red, 2));
3779 painter.drawLine(extraAreaWidth - 1, top, extraAreaWidth - 1, bottom - 1);
3783 if (d->m_lineNumbersVisible) {
3784 const QString &number = QString::number(blockNumber + 1);
3786 (selStart < block.position() + block.length()
3787 && selEnd > block.position())
3788 || (selStart == selEnd && selStart == block.position())
3792 QFont f = painter.font();
3793 f.setBold(d->m_currentLineNumberFormat.font().bold());
3794 f.setItalic(d->m_currentLineNumberFormat.font().italic());
3796 painter.setPen(d->m_currentLineNumberFormat.foreground().color());
3798 painter.drawText(QRectF(markWidth, top, extraAreaWidth - markWidth - 4, height), Qt::AlignRight, number);
3803 block = nextVisibleBlock;
3804 blockNumber = nextVisibleBlockNumber;
3808 void BaseTextEditorWidget::drawFoldingMarker(QPainter *painter, const QPalette &pal,
3816 QStyle *s = style();
3817 if (ManhattanStyle *ms = qobject_cast<ManhattanStyle*>(s))
3818 s = ms->baseStyle();
3820 if (!qstrcmp(s->metaObject()->className(), "OxygenStyle")) {
3822 painter->setPen(Qt::NoPen);
3823 int size = rect.size().width();
3824 int sqsize = 2*(size/2);
3826 QColor textColor = pal.buttonText().color();
3827 QColor brushColor = textColor;
3829 textColor.setAlpha(100);
3830 brushColor.setAlpha(100);
3835 a.setPoints(3, 0, sqsize/3, sqsize/2, sqsize - sqsize/3, sqsize, sqsize/3);
3838 a.setPoints(3, sqsize - sqsize/3, sqsize/2, sqsize/2 - sqsize/3, 0, sqsize/2 - sqsize/3, sqsize);
3839 painter->setBrush(brushColor);
3841 painter->translate(0.5, 0.5);
3842 painter->setRenderHint(QPainter::Antialiasing);
3843 painter->translate(rect.topLeft());
3844 painter->setPen(textColor);
3845 painter->setBrush(textColor);
3846 painter->drawPolygon(a);
3849 QStyleOptionViewItemV2 opt;
3851 opt.state = QStyle::State_Active | QStyle::State_Item | QStyle::State_Children;
3853 opt.state |= QStyle::State_Open;
3855 opt.state |= QStyle::State_MouseOver | QStyle::State_Enabled | QStyle::State_Selected;
3857 opt.palette.setBrush(QPalette::Window, pal.highlight());
3859 // QGtkStyle needs a small correction to draw the marker in the right place
3860 if (!qstrcmp(s->metaObject()->className(), "QGtkStyle"))
3861 opt.rect.translate(-2, 0);
3862 else if (!qstrcmp(s->metaObject()->className(), "QMacStyle"))
3863 opt.rect.translate(-1, 0);
3865 s->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
3869 void BaseTextEditorWidget::slotModificationChanged(bool m)
3874 QTextDocument *doc = document();
3875 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
3876 QTC_ASSERT(documentLayout, return);
3877 int oldLastSaveRevision = documentLayout->lastSaveRevision;
3878 documentLayout->lastSaveRevision = doc->revision();
3880 if (oldLastSaveRevision != documentLayout->lastSaveRevision) {
3881 QTextBlock block = doc->begin();
3882 while (block.isValid()) {
3883 if (block.revision() < 0 || block.revision() != oldLastSaveRevision) {
3884 block.setRevision(-documentLayout->lastSaveRevision - 1);
3886 block.setRevision(documentLayout->lastSaveRevision);
3888 block = block.next();
3891 d->m_extraArea->update();
3894 void BaseTextEditorWidget::slotUpdateRequest(const QRect &r, int dy)
3897 d->m_extraArea->scroll(0, dy);
3898 else if (r.width() > 4) { // wider than cursor width, not just cursor blinking
3899 d->m_extraArea->update(0, r.y(), d->m_extraArea->width(), r.height());
3900 if (!d->m_searchExpr.isEmpty()) {
3901 const int m = d->m_searchResultOverlay->dropShadowWidth();
3902 viewport()->update(r.adjusted(-m, -m, m, m));
3906 if (r.contains(viewport()->rect()))
3907 slotUpdateExtraAreaWidth();
3910 void BaseTextEditorWidget::saveCurrentCursorPositionForNavigation()
3912 d->m_lastCursorChangeWasInteresting = true;
3913 d->m_tempNavigationState = saveState();
3916 void BaseTextEditorWidget::updateCurrentLineHighlight()
3918 QList<QTextEdit::ExtraSelection> extraSelections;
3920 if (d->m_highlightCurrentLine) {
3921 QTextEdit::ExtraSelection sel;
3922 sel.format.setBackground(d->m_currentLineFormat.background());
3923 sel.format.setProperty(QTextFormat::FullWidthSelection, true);
3924 sel.cursor = textCursor();
3925 sel.cursor.clearSelection();
3926 extraSelections.append(sel);
3929 setExtraSelections(CurrentLineSelection, extraSelections);
3932 // the extra area shows information for the entire current block, not just the currentline.
3933 // This is why we must force a bigger update region.
3934 int cursorBlockNumber = textCursor().blockNumber();
3935 if (cursorBlockNumber != d->m_cursorBlockNumber) {
3936 QPointF offset = contentOffset();
3937 QTextBlock block = document()->findBlockByNumber(d->m_cursorBlockNumber);
3938 if (block.isValid())
3939 d->m_extraArea->update(blockBoundingGeometry(block).translated(offset).toAlignedRect());
3940 block = document()->findBlockByNumber(cursorBlockNumber);
3941 if (block.isValid() && block.isVisible())
3942 d->m_extraArea->update(blockBoundingGeometry(block).translated(offset).toAlignedRect());
3943 d->m_cursorBlockNumber = cursorBlockNumber;
3948 void BaseTextEditorWidget::slotCursorPositionChanged()
3951 qDebug() << "block" << textCursor().blockNumber()+1
3952 << "brace depth:" << BaseTextDocumentLayout::braceDepth(textCursor().block())
3953 << "indent:" << BaseTextDocumentLayout::userData(textCursor().block())->foldingIndent();
3955 if (!d->m_contentsChanged && d->m_lastCursorChangeWasInteresting) {
3956 Core::EditorManager::instance()->addCurrentPositionToNavigationHistory(editor(), d->m_tempNavigationState);
3957 d->m_lastCursorChangeWasInteresting = false;
3958 } else if (d->m_contentsChanged) {
3959 saveCurrentCursorPositionForNavigation();
3964 void BaseTextEditorWidget::updateHighlights()
3966 if (d->m_parenthesesMatchingEnabled && hasFocus()) {
3967 // Delay update when no matching is displayed yet, to avoid flicker
3968 if (extraSelections(ParenthesesMatchingSelection).isEmpty()
3969 && d->m_animator == 0) {
3970 d->m_parenthesesMatchingTimer->start(50);
3972 // use 0-timer, not direct call, to give the syntax highlighter a chance
3973 // to update the parantheses information
3974 d->m_parenthesesMatchingTimer->start(0);
3978 updateCurrentLineHighlight();
3980 if (d->m_displaySettings.m_highlightBlocks) {
3981 QTextCursor cursor = textCursor();
3982 d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
3983 d->m_highlightBlocksTimer->start(100);
3987 void BaseTextEditorWidget::slotUpdateBlockNotify(const QTextBlock &block)
3989 static bool blockRecursion = false;
3992 blockRecursion = true;
3993 if (d->m_overlay->isVisible()) {
3994 /* an overlay might draw outside the block bounderies, force
3995 complete viewport update */
3996 viewport()->update();
3998 if (block.previous().isValid() && block.userState() != block.previous().userState()) {
3999 /* The syntax highlighting state changes. This opens up for
4000 the possibility that the paragraph has braces that support
4001 code folding. In this case, do the save thing and also
4002 update the previous block, which might contain a fold
4003 box which now is invalid.*/
4004 emit requestBlockUpdate(block.previous());
4006 if (!d->m_findScopeStart.isNull()) {
4007 if (block.position() < d->m_findScopeEnd.position()
4008 && block.position()+block.length() >= d->m_findScopeStart.position()) {
4009 QTextBlock b = block.document()->findBlock(d->m_findScopeStart.position());
4011 emit requestBlockUpdate(b);
4013 } while (b.isValid() && b.position() < d->m_findScopeEnd.position());
4017 blockRecursion = false;
4020 void BaseTextEditorWidget::timerEvent(QTimerEvent *e)
4022 if (e->timerId() == d->autoScrollTimer.timerId()) {
4023 const QPoint globalPos = QCursor::pos();
4024 const QPoint pos = d->m_extraArea->mapFromGlobal(globalPos);
4025 QRect visible = d->m_extraArea->rect();
4026 verticalScrollBar()->triggerAction( pos.y() < visible.center().y() ?
4027 QAbstractSlider::SliderSingleStepSub
4028 : QAbstractSlider::SliderSingleStepAdd);
4029 QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
4030 extraAreaMouseEvent(&ev);
4031 int delta = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height();
4034 int timeout = 4900 / (delta * delta);
4035 d->autoScrollTimer.start(timeout, this);
4037 } else if (e->timerId() == d->foldedBlockTimer.timerId()) {
4038 d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber;
4039 d->suggestedVisibleFoldedBlockNumber = -1;
4040 d->foldedBlockTimer.stop();
4041 viewport()->update();
4043 QPlainTextEdit::timerEvent(e);
4047 void BaseTextEditorPrivate::clearVisibleFoldedBlock()
4049 if (suggestedVisibleFoldedBlockNumber) {
4050 suggestedVisibleFoldedBlockNumber = -1;
4051 foldedBlockTimer.stop();
4053 if (visibleFoldedBlockNumber >= 0) {
4054 visibleFoldedBlockNumber = -1;
4055 q->viewport()->update();
4059 void BaseTextEditorWidget::mouseMoveEvent(QMouseEvent *e)
4063 if (e->buttons() == Qt::NoButton) {
4064 const QTextBlock collapsedBlock = foldedBlockAt(e->pos());
4065 const int blockNumber = collapsedBlock.next().blockNumber();
4066 if (blockNumber < 0) {
4067 d->clearVisibleFoldedBlock();
4068 } else if (blockNumber != d->visibleFoldedBlockNumber) {
4069 d->suggestedVisibleFoldedBlockNumber = blockNumber;
4070 d->foldedBlockTimer.start(40, this);
4073 const RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(e->pos());
4075 // Update the mouse cursor
4076 if ((collapsedBlock.isValid() || refactorMarker.isValid()) && !d->m_mouseOnFoldedMarker) {
4077 d->m_mouseOnFoldedMarker = true;
4078 viewport()->setCursor(Qt::PointingHandCursor);
4079 } else if (!collapsedBlock.isValid() && !refactorMarker.isValid() && d->m_mouseOnFoldedMarker) {
4080 d->m_mouseOnFoldedMarker = false;
4081 viewport()->setCursor(Qt::IBeamCursor);
4084 QPlainTextEdit::mouseMoveEvent(e);
4086 if (e->modifiers() & Qt::AltModifier) {
4087 if (!d->m_inBlockSelectionMode) {
4088 d->m_blockSelection.fromSelection(tabSettings(), textCursor());
4089 d->m_inBlockSelectionMode = true;
4091 QTextCursor cursor = textCursor();
4093 // get visual column
4094 int column = tabSettings().columnAt(cursor.block().text(), cursor.positionInBlock());
4095 if (cursor.positionInBlock() == cursor.block().length()-1) {
4096 column += (e->pos().x() - cursorRect().center().x())/QFontMetricsF(font()).width(QLatin1Char(' '));
4098 d->m_blockSelection.moveAnchor(cursor.blockNumber(), column);
4099 setTextCursor(d->m_blockSelection.selection(tabSettings()));
4100 viewport()->update();
4104 if (viewport()->cursor().shape() == Qt::BlankCursor)
4105 viewport()->setCursor(Qt::IBeamCursor);
4108 static bool handleForwardBackwardMouseButtons(QMouseEvent *e)
4110 if (e->button() == Qt::XButton1) {
4111 Core::EditorManager::instance()->goBackInNavigationHistory();
4114 if (e->button() == Qt::XButton2) {
4115 Core::EditorManager::instance()->goForwardInNavigationHistory();
4122 void BaseTextEditorWidget::mousePressEvent(QMouseEvent *e)
4124 if (e->button() == Qt::LeftButton) {
4125 d->clearBlockSelection(); // just in case, otherwise we might get strange drag and drop
4127 QTextBlock foldedBlock = foldedBlockAt(e->pos());
4128 if (foldedBlock.isValid()) {
4129 toggleBlockVisible(foldedBlock);
4130 viewport()->setCursor(Qt::IBeamCursor);
4133 RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(e->pos());
4134 if (refactorMarker.isValid()) {
4135 qDebug() << "refactorMarkerClicked" << refactorMarker.cursor.position();
4136 emit refactorMarkerClicked(refactorMarker);
4140 if (d->m_currentLink.isValid())
4141 d->m_linkPressed = true;
4146 if (handleForwardBackwardMouseButtons(e))
4150 QPlainTextEdit::mousePressEvent(e);
4153 void BaseTextEditorWidget::mouseReleaseEvent(QMouseEvent *e)
4155 if (mouseNavigationEnabled()
4157 && e->modifiers() & Qt::ControlModifier
4158 && !(e->modifiers() & Qt::ShiftModifier)
4159 && e->button() == Qt::LeftButton
4161 const QTextCursor cursor = cursorForPosition(e->pos());
4162 if (openLink(findLinkAt(cursor))) {
4169 if (handleForwardBackwardMouseButtons(e))
4173 QPlainTextEdit::mouseReleaseEvent(e);
4176 void BaseTextEditorWidget::leaveEvent(QEvent *e)
4178 // Clear link emulation when the mouse leaves the editor
4180 QPlainTextEdit::leaveEvent(e);
4183 void BaseTextEditorWidget::keyReleaseEvent(QKeyEvent *e)
4185 // Clear link emulation when Ctrl is released
4186 if (e->key() == Qt::Key_Control)
4189 QPlainTextEdit::keyReleaseEvent(e);
4192 void BaseTextEditorWidget::dragEnterEvent(QDragEnterEvent *e)
4194 // If the drag event contains URLs, we don't want to insert them as text
4195 if (e->mimeData()->hasUrls()) {
4200 QPlainTextEdit::dragEnterEvent(e);
4203 void BaseTextEditorWidget::extraAreaLeaveEvent(QEvent *)
4205 // fake missing mouse move event from Qt
4206 QMouseEvent me(QEvent::MouseMove, QPoint(-1, -1), Qt::NoButton, 0, 0);
4207 extraAreaMouseEvent(&me);
4210 void BaseTextEditorWidget::extraAreaMouseEvent(QMouseEvent *e)
4212 QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
4215 extraAreaWidth(&markWidth);
4217 if (d->m_codeFoldingVisible
4218 && e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking
4219 // Update which folder marker is highlighted
4220 const int highlightBlockNumber = d->extraAreaHighlightFoldedBlockNumber;
4221 d->extraAreaHighlightFoldedBlockNumber = -1;
4223 if (e->pos().x() > extraArea()->width() - foldBoxWidth(fontMetrics())) {
4224 d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
4225 } else if (d->m_displaySettings.m_highlightBlocks) {
4226 QTextCursor cursor = textCursor();
4227 d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
4230 if (highlightBlockNumber != d->extraAreaHighlightFoldedBlockNumber)
4231 d->m_highlightBlocksTimer->start(d->m_highlightBlocksInfo.isEmpty() ? 120 : 0);
4234 // Set whether the mouse cursor is a hand or normal arrow
4235 if (e->type() == QEvent::MouseMove) {
4236 bool hand = (e->pos().x() <= markWidth);
4237 if (hand != (d->m_extraArea->cursor().shape() == Qt::PointingHandCursor))
4238 d->m_extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor);
4241 if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
4242 if (e->button() == Qt::LeftButton) {
4243 int boxWidth = foldBoxWidth(fontMetrics());
4244 if (d->m_codeFoldingVisible && e->pos().x() > extraArea()->width() - boxWidth) {
4245 if (!cursor.block().next().isVisible()) {
4246 toggleBlockVisible(cursor.block());
4247 d->moveCursorVisible(false);
4248 } else if (foldBox().contains(e->pos())) {
4250 document()->findBlockByNumber(d->m_highlightBlocksInfo.open.last()).position()
4252 QTextBlock c = cursor.block();
4253 toggleBlockVisible(c);
4254 d->moveCursorVisible(false);
4256 } else if (d->m_lineNumbersVisible && e->pos().x() > markWidth) {
4257 QTextCursor selection = cursor;
4258 selection.setVisualNavigation(true);
4259 d->extraAreaSelectionAnchorBlockNumber = selection.blockNumber();
4260 selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
4261 selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
4262 setTextCursor(selection);
4264 d->extraAreaToggleMarkBlockNumber = cursor.blockNumber();
4266 } else if (d->m_marksVisible && e->button() == Qt::RightButton) {
4267 QMenu * contextMenu = new QMenu(this);
4268 emit editor()->markContextMenuRequested(editor(), cursor.blockNumber() + 1, contextMenu);
4269 if (!contextMenu->isEmpty())
4270 contextMenu->exec(e->globalPos());
4273 } else if (d->extraAreaSelectionAnchorBlockNumber >= 0) {
4274 QTextCursor selection = cursor;
4275 selection.setVisualNavigation(true);
4276 if (e->type() == QEvent::MouseMove) {
4277 QTextBlock anchorBlock = document()->findBlockByNumber(d->extraAreaSelectionAnchorBlockNumber);
4278 selection.setPosition(anchorBlock.position());
4279 if (cursor.blockNumber() < d->extraAreaSelectionAnchorBlockNumber) {
4280 selection.movePosition(QTextCursor::EndOfBlock);
4281 selection.movePosition(QTextCursor::Right);
4283 selection.setPosition(cursor.block().position(), QTextCursor::KeepAnchor);
4284 if (cursor.blockNumber() >= d->extraAreaSelectionAnchorBlockNumber) {
4285 selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
4286 selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
4289 if (e->pos().y() >= 0 && e->pos().y() <= d->m_extraArea->height())
4290 d->autoScrollTimer.stop();
4291 else if (!d->autoScrollTimer.isActive())
4292 d->autoScrollTimer.start(100, this);
4295 d->autoScrollTimer.stop();
4296 d->extraAreaSelectionAnchorBlockNumber = -1;
4299 setTextCursor(selection);
4300 } else if (d->extraAreaToggleMarkBlockNumber >= 0 && d->m_marksVisible && d->m_requestMarkEnabled) {
4301 if (e->type() == QEvent::MouseButtonRelease && e->button() == Qt::LeftButton) {
4302 int n = d->extraAreaToggleMarkBlockNumber;
4303 d->extraAreaToggleMarkBlockNumber = -1;
4304 if (cursor.blockNumber() == n) {
4306 emit editor()->markRequested(editor(), line);
4312 void BaseTextEditorWidget::ensureCursorVisible()
4314 QTextBlock block = textCursor().block();
4315 if (!block.isVisible()) {
4316 while (!block.isVisible() && block.previous().isValid())
4317 block = block.previous();
4318 toggleBlockVisible(block);
4320 QPlainTextEdit::ensureCursorVisible();
4323 void BaseTextEditorWidget::toggleBlockVisible(const QTextBlock &block)
4325 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());
4326 QTC_ASSERT(documentLayout, return);
4328 bool visible = block.next().isVisible();
4329 BaseTextDocumentLayout::doFoldOrUnfold(block, !visible);
4330 documentLayout->requestUpdate();
4331 documentLayout->emitDocumentSizeChanged();
4335 const TabSettings &BaseTextEditorWidget::tabSettings() const
4337 return d->m_document->tabSettings();
4340 const DisplaySettings &BaseTextEditorWidget::displaySettings() const
4342 return d->m_displaySettings;
4346 void BaseTextEditorWidget::indentOrUnindent(bool doIndent)
4348 const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings();
4350 QTextCursor cursor = textCursor();
4351 maybeClearSomeExtraSelections(cursor);
4352 cursor.beginEditBlock();
4354 if (cursor.hasSelection()) {
4355 // Indent or unindent the selected lines
4356 int pos = cursor.position();
4357 int anchor = cursor.anchor();
4358 int start = qMin(anchor, pos);
4359 int end = qMax(anchor, pos);
4361 QTextDocument *doc = document();
4362 QTextBlock startBlock = doc->findBlock(start);
4363 QTextBlock endBlock = doc->findBlock(end-1).next();
4365 for (QTextBlock block = startBlock; block != endBlock; block = block.next()) {
4366 QString text = block.text();
4367 int indentPosition = tabSettings.lineIndentPosition(text);
4368 if (!doIndent && !indentPosition)
4369 indentPosition = tabSettings.firstNonSpace(text);
4370 int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent);
4371 cursor.setPosition(block.position() + indentPosition);
4372 cursor.insertText(tabSettings.indentationString(0, targetColumn, block));
4373 cursor.setPosition(block.position());
4374 cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor);
4375 cursor.removeSelectedText();
4377 cursor.endEditBlock();
4379 // Indent or unindent at cursor position
4380 QTextBlock block = cursor.block();
4381 QString text = block.text();
4382 int indentPosition = cursor.positionInBlock();
4383 int spaces = tabSettings.spacesLeftFromPosition(text, indentPosition);
4384 int startColumn = tabSettings.columnAt(text, indentPosition - spaces);
4385 int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent);
4386 cursor.setPosition(block.position() + indentPosition);
4387 cursor.setPosition(block.position() + indentPosition - spaces, QTextCursor::KeepAnchor);
4388 cursor.removeSelectedText();
4389 cursor.insertText(tabSettings.indentationString(startColumn, targetColumn, block));
4390 cursor.endEditBlock();
4391 setTextCursor(cursor);
4395 void BaseTextEditorWidget::handleHomeKey(bool anchor)
4397 QTextCursor cursor = textCursor();
4398 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
4401 mode = QTextCursor::KeepAnchor;
4403 const int initpos = cursor.position();
4404 int pos = cursor.block().position();
4405 QChar character = characterAt(pos);
4406 const QLatin1Char tab = QLatin1Char('\t');
4408 while (character == tab || character.category() == QChar::Separator_Space) {
4412 character = characterAt(pos);
4415 // Go to the start of the block when we're already at the start of the text
4417 pos = cursor.block().position();
4419 cursor.setPosition(pos, mode);
4420 setTextCursor(cursor);
4423 void BaseTextEditorWidget::handleBackspaceKey()
4425 QTextCursor cursor = textCursor();
4426 int pos = cursor.position();
4427 QTC_ASSERT(!cursor.hasSelection(), return);
4429 bool cursorWithinSnippet = false;
4430 if (d->m_snippetOverlay->isVisible()) {
4431 QTextCursor snippetCursor = cursor;
4432 snippetCursor.movePosition(QTextCursor::Left);
4433 cursorWithinSnippet = d->snippetCheckCursor(snippetCursor);
4436 const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings();
4438 if (tabSettings.m_autoIndent && d->m_autoCompleter->autoBackspace(cursor))
4441 bool handled = false;
4442 if (!tabSettings.m_smartBackspace) {
4443 if (cursorWithinSnippet)
4444 cursor.beginEditBlock();
4445 cursor.deletePreviousChar();
4448 QTextBlock currentBlock = cursor.block();
4449 int positionInBlock = pos - currentBlock.position();
4450 const QString blockText = currentBlock.text();
4451 if (cursor.atBlockStart() || tabSettings.firstNonSpace(blockText) < positionInBlock) {
4452 if (cursorWithinSnippet)
4453 cursor.beginEditBlock();
4454 cursor.deletePreviousChar();
4457 int previousIndent = 0;
4458 const int indent = tabSettings.columnAt(blockText, positionInBlock);
4460 for (QTextBlock previousNonEmptyBlock = currentBlock.previous();
4461 previousNonEmptyBlock.isValid();
4462 previousNonEmptyBlock = previousNonEmptyBlock.previous()) {
4463 QString previousNonEmptyBlockText = previousNonEmptyBlock.text();
4464 if (previousNonEmptyBlockText.trimmed().isEmpty())
4467 tabSettings.columnAt(previousNonEmptyBlockText,
4468 tabSettings.firstNonSpace(previousNonEmptyBlockText));
4469 if (previousIndent < indent) {
4470 cursor.beginEditBlock();
4471 cursor.setPosition(currentBlock.position(), QTextCursor::KeepAnchor);
4472 cursor.insertText(tabSettings.indentationString(previousNonEmptyBlockText));
4473 cursor.endEditBlock();
4482 if (cursorWithinSnippet)
4483 cursor.beginEditBlock();
4484 cursor.deletePreviousChar();
4487 if (cursorWithinSnippet) {
4488 cursor.endEditBlock();
4489 d->m_snippetOverlay->updateEquivalentSelections(cursor);
4492 setTextCursor(cursor);
4495 void BaseTextEditorWidget::wheelEvent(QWheelEvent *e)
4497 d->clearVisibleFoldedBlock();
4498 if (scrollWheelZoomingEnabled() && e->modifiers() & Qt::ControlModifier) {
4499 const int delta = e->delta();
4506 QPlainTextEdit::wheelEvent(e);
4509 void BaseTextEditorWidget::zoomIn(int range)
4511 d->clearVisibleFoldedBlock();
4512 emit requestFontZoom(range*10);
4515 void BaseTextEditorWidget::zoomOut(int range)
4520 void BaseTextEditorWidget::zoomReset()
4522 emit requestZoomReset();
4525 void BaseTextEditorWidget::indentInsertedText(const QTextCursor &tc)
4527 indent(tc.document(), tc, QChar::Null);
4530 void BaseTextEditorWidget::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar)
4532 maybeClearSomeExtraSelections(cursor);
4533 d->m_indenter->indent(doc, cursor, typedChar, this);
4536 void BaseTextEditorWidget::reindent(QTextDocument *doc, const QTextCursor &cursor)
4538 maybeClearSomeExtraSelections(cursor);
4539 d->m_indenter->reindent(doc, cursor, this);
4542 BaseTextEditorWidget::Link BaseTextEditorWidget::findLinkAt(const QTextCursor &, bool)
4547 bool BaseTextEditorWidget::openLink(const Link &link)
4549 if (link.fileName.isEmpty())
4552 if (baseTextDocument()->fileName() == link.fileName) {
4553 Core::EditorManager *editorManager = Core::EditorManager::instance();
4554 editorManager->addCurrentPositionToNavigationHistory();
4555 gotoLine(link.line, link.column);
4560 return openEditorAt(link.fileName, link.line, link.column, QString(),
4561 Core::EditorManager::IgnoreNavigationHistory
4562 | Core::EditorManager::ModeSwitch);
4565 void BaseTextEditorWidget::updateLink(QMouseEvent *e)
4567 bool linkFound = false;
4569 if (mouseNavigationEnabled() && e->modifiers() & Qt::ControlModifier) {
4570 // Link emulation behaviour for 'go to definition'
4571 const QTextCursor cursor = cursorForPosition(e->pos());
4573 // Check that the mouse was actually on the text somewhere
4574 bool onText = cursorRect(cursor).right() >= e->x();
4576 QTextCursor nextPos = cursor;
4577 nextPos.movePosition(QTextCursor::Right);
4578 onText = cursorRect(nextPos).right() >= e->x();
4581 const Link link = findLinkAt(cursor, false);
4583 if (onText && link.isValid()) {
4593 void BaseTextEditorWidget::showLink(const Link &link)
4595 if (d->m_currentLink == link)
4598 QTextEdit::ExtraSelection sel;
4599 sel.cursor = textCursor();
4600 sel.cursor.setPosition(link.begin);
4601 sel.cursor.setPosition(link.end, QTextCursor::KeepAnchor);
4602 sel.format = d->m_linkFormat;
4603 sel.format.setFontUnderline(true);
4604 setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>() << sel);
4605 viewport()->setCursor(Qt::PointingHandCursor);
4606 d->m_currentLink = link;
4607 d->m_linkPressed = false;
4610 void BaseTextEditorWidget::clearLink()
4612 if (!d->m_currentLink.isValid())
4615 setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
4616 viewport()->setCursor(Qt::IBeamCursor);
4617 d->m_currentLink = Link();
4618 d->m_linkPressed = false;
4621 void BaseTextEditorPrivate::updateMarksBlock(const QTextBlock &block)
4623 if (const TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block))
4624 foreach (ITextMark *mrk, userData->marks())
4625 mrk->updateBlock(block);
4628 void BaseTextEditorPrivate::updateMarksLineNumber()
4630 QTextDocument *doc = q->document();
4631 QTextBlock block = doc->begin();
4632 int blockNumber = 0;
4633 while (block.isValid()) {
4634 if (const TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block))
4635 foreach (ITextMark *mrk, userData->marks()) {
4636 mrk->updateLineNumber(blockNumber + 1);
4638 block = block.next();
4643 void BaseTextEditorWidget::markBlocksAsChanged(QList<int> blockNumbers)
4645 QTextBlock block = document()->begin();
4646 while (block.isValid()) {
4647 if (block.revision() < 0)
4648 block.setRevision(-block.revision() - 1);
4649 block = block.next();
4651 foreach (const int blockNumber, blockNumbers) {
4652 QTextBlock block = document()->findBlockByNumber(blockNumber);
4653 if (block.isValid())
4654 block.setRevision(-block.revision() - 1);
4659 void BaseTextEditorWidget::highlightSearchResults(const QString &txt, Find::FindFlags findFlags)
4661 QString pattern = txt;
4662 if (pattern.size() < 2)
4663 pattern.clear(); // highlighting single characters is a bit pointless
4665 if (d->m_searchExpr.pattern() == pattern)
4667 d->m_searchExpr.setPattern(pattern);
4668 d->m_searchExpr.setPatternSyntax((findFlags & Find::FindRegularExpression) ?
4669 QRegExp::RegExp : QRegExp::FixedString);
4670 d->m_searchExpr.setCaseSensitivity((findFlags & Find::FindCaseSensitively) ?
4671 Qt::CaseSensitive : Qt::CaseInsensitive);
4672 d->m_findFlags = findFlags;
4674 d->m_delayedUpdateTimer->start(10);
4677 int BaseTextEditorWidget::verticalBlockSelectionFirstColumn() const
4679 if (d->m_inBlockSelectionMode)
4680 return d->m_blockSelection.firstVisualColumn;
4684 int BaseTextEditorWidget::verticalBlockSelectionLastColumn() const
4686 if (d->m_inBlockSelectionMode)
4687 return d->m_blockSelection.lastVisualColumn;
4691 QRegion BaseTextEditorWidget::translatedLineRegion(int lineStart, int lineEnd) const
4694 for (int i = lineStart ; i <= lineEnd; i++) {
4695 QTextBlock block = document()->findBlockByNumber(i);
4696 QPoint topLeft = blockBoundingGeometry(block).translated(contentOffset()).topLeft().toPoint();
4698 if (block.isValid()) {
4699 QTextLayout *layout = block.layout();
4701 for (int i = 0; i < layout->lineCount();i++) {
4702 QTextLine line = layout->lineAt(i);
4703 region += line.naturalTextRect().translated(topLeft).toRect();
4710 void BaseTextEditorWidget::setFindScope(const QTextCursor &start, const QTextCursor &end,
4711 int verticalBlockSelectionFirstColumn,
4712 int verticalBlockSelectionLastColumn)
4714 if (start != d->m_findScopeStart
4715 || end != d->m_findScopeEnd
4716 || verticalBlockSelectionFirstColumn != d->m_findScopeVerticalBlockSelectionFirstColumn
4717 || verticalBlockSelectionLastColumn != d->m_findScopeVerticalBlockSelectionLastColumn) {
4718 d->m_findScopeStart = start;
4719 d->m_findScopeEnd = end;
4720 d->m_findScopeVerticalBlockSelectionFirstColumn = verticalBlockSelectionFirstColumn;
4721 d->m_findScopeVerticalBlockSelectionLastColumn = verticalBlockSelectionLastColumn;
4722 viewport()->update();
4726 void BaseTextEditorWidget::_q_animateUpdate(int position, QPointF lastPos, QRectF rect)
4728 QTextCursor cursor(textCursor());
4729 cursor.setPosition(position);
4730 viewport()->update(QRectF(cursorRect(cursor).topLeft() + rect.topLeft(), rect.size()).toAlignedRect());
4731 if (!lastPos.isNull())
4732 viewport()->update(QRectF(lastPos + rect.topLeft(), rect.size()).toAlignedRect());
4736 BaseTextEditorAnimator::BaseTextEditorAnimator(QObject *parent)
4740 m_timeline = new QTimeLine(256, this);
4741 m_timeline->setCurveShape(QTimeLine::SineCurve);
4742 connect(m_timeline, SIGNAL(valueChanged(qreal)), this, SLOT(step(qreal)));
4743 connect(m_timeline, SIGNAL(finished()), this, SLOT(deleteLater()));
4744 m_timeline->start();
4748 void BaseTextEditorAnimator::setData(QFont f, QPalette pal, const QString &text)
4753 QFontMetrics fm(m_font);
4754 m_size = QSizeF(fm.width(m_text), fm.height());
4757 void BaseTextEditorAnimator::draw(QPainter *p, const QPointF &pos)
4759 m_lastDrawPos = pos;
4760 p->setPen(m_palette.text().color());
4762 f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
4764 int width = fm.width(m_text);
4765 QRectF r((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
4767 p->fillRect(r, m_palette.base());
4769 p->drawText(r, m_text);
4772 bool BaseTextEditorAnimator::isRunning() const
4774 return m_timeline->state() == QTimeLine::Running;
4777 QRectF BaseTextEditorAnimator::rect() const
4780 f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
4782 int width = fm.width(m_text);
4783 return QRectF((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
4786 void BaseTextEditorAnimator::step(qreal v)
4788 QRectF before = rect();
4790 QRectF after = rect();
4791 emit updateRequest(m_position, m_lastDrawPos, before.united(after));
4794 void BaseTextEditorAnimator::finish()
4801 void BaseTextEditorWidget::_q_matchParentheses()
4806 QTextCursor backwardMatch = textCursor();
4807 QTextCursor forwardMatch = textCursor();
4808 const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
4809 const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);
4811 QList<QTextEdit::ExtraSelection> extraSelections;
4813 if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch) {
4814 setExtraSelections(ParenthesesMatchingSelection, extraSelections); // clear
4818 int animatePosition = -1;
4819 if (backwardMatch.hasSelection()) {
4820 QTextEdit::ExtraSelection sel;
4821 if (backwardMatchType == TextBlockUserData::Mismatch) {
4822 sel.cursor = backwardMatch;
4823 sel.format = d->m_mismatchFormat;
4826 if (d->m_displaySettings.m_animateMatchingParentheses) {
4827 animatePosition = backwardMatch.selectionStart();
4828 } else if (d->m_formatRange) {
4829 sel.cursor = backwardMatch;
4830 sel.format = d->m_rangeFormat;
4831 extraSelections.append(sel);
4834 sel.cursor = backwardMatch;
4835 sel.format = d->m_matchFormat;
4837 sel.cursor.setPosition(backwardMatch.selectionStart());
4838 sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
4839 extraSelections.append(sel);
4841 sel.cursor.setPosition(backwardMatch.selectionEnd());
4842 sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
4844 extraSelections.append(sel);
4847 if (forwardMatch.hasSelection()) {
4848 QTextEdit::ExtraSelection sel;
4849 if (forwardMatchType == TextBlockUserData::Mismatch) {
4850 sel.cursor = forwardMatch;
4851 sel.format = d->m_mismatchFormat;
4854 if (d->m_displaySettings.m_animateMatchingParentheses) {
4855 animatePosition = forwardMatch.selectionEnd()-1;
4856 } else if (d->m_formatRange) {
4857 sel.cursor = forwardMatch;
4858 sel.format = d->m_rangeFormat;
4859 extraSelections.append(sel);
4862 sel.cursor = forwardMatch;
4863 sel.format = d->m_matchFormat;
4865 sel.cursor.setPosition(forwardMatch.selectionStart());
4866 sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
4867 extraSelections.append(sel);
4869 sel.cursor.setPosition(forwardMatch.selectionEnd());
4870 sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
4872 extraSelections.append(sel);
4876 if (animatePosition >= 0) {
4877 foreach (const QTextEdit::ExtraSelection &sel, BaseTextEditorWidget::extraSelections(ParenthesesMatchingSelection)) {
4878 if (sel.cursor.selectionStart() == animatePosition
4879 || sel.cursor.selectionEnd() - 1 == animatePosition) {
4880 animatePosition = -1;
4886 if (animatePosition >= 0) {
4888 d->m_animator->finish(); // one animation is enough
4889 d->m_animator = new BaseTextEditorAnimator(this);
4890 d->m_animator->setPosition(animatePosition);
4892 pal.setBrush(QPalette::Text, d->m_matchFormat.foreground());
4893 pal.setBrush(QPalette::Base, d->m_rangeFormat.background());
4894 d->m_animator->setData(font(), pal, characterAt(d->m_animator->position()));
4895 connect(d->m_animator, SIGNAL(updateRequest(int,QPointF,QRectF)),
4896 this, SLOT(_q_animateUpdate(int,QPointF,QRectF)));
4899 setExtraSelections(ParenthesesMatchingSelection, extraSelections);
4902 void BaseTextEditorWidget::_q_highlightBlocks()
4904 BaseTextEditorPrivateHighlightBlocks highlightBlocksInfo;
4907 if (d->extraAreaHighlightFoldedBlockNumber >= 0) {
4908 block = document()->findBlockByNumber(d->extraAreaHighlightFoldedBlockNumber);
4910 && block.next().isValid()
4911 && BaseTextDocumentLayout::foldingIndent(block.next())
4912 > BaseTextDocumentLayout::foldingIndent(block))
4913 block = block.next();
4916 QTextBlock closeBlock = block;
4917 while (block.isValid()) {
4918 int foldingIndent = BaseTextDocumentLayout::foldingIndent(block);
4920 while (block.previous().isValid() && BaseTextDocumentLayout::foldingIndent(block) >= foldingIndent)
4921 block = block.previous();
4922 int nextIndent = BaseTextDocumentLayout::foldingIndent(block);
4923 if (nextIndent == foldingIndent)
4925 highlightBlocksInfo.open.prepend(block.blockNumber());
4926 while (closeBlock.next().isValid()
4927 && BaseTextDocumentLayout::foldingIndent(closeBlock.next()) >= foldingIndent )
4928 closeBlock = closeBlock.next();
4929 highlightBlocksInfo.close.append(closeBlock.blockNumber());
4930 int visualIndent = qMin(d->visualIndent(block), d->visualIndent(closeBlock));
4931 highlightBlocksInfo.visualIndent.prepend(visualIndent);
4935 if (block.isValid()) {
4936 QTextCursor cursor(block);
4937 if (d->extraAreaHighlightCollapseColumn >= 0)
4938 cursor.setPosition(cursor.position() + qMin(d->extraAreaHighlightCollapseColumn,
4940 QTextCursor closeCursor;
4941 bool firstRun = true;
4942 while (TextBlockUserData::findPreviousBlockOpenParenthesis(&cursor, firstRun)) {
4944 highlightBlocksInfo.open.prepend(cursor.blockNumber());
4945 int visualIndent = d->visualIndent(cursor.block());
4946 if (closeCursor.isNull())
4947 closeCursor = cursor;
4948 if (TextBlockUserData::findNextBlockClosingParenthesis(&closeCursor)) {
4949 highlightBlocksInfo.close.append(closeCursor.blockNumber());
4950 visualIndent = qMin(visualIndent, d->visualIndent(closeCursor.block()));
4952 highlightBlocksInfo.visualIndent.prepend(visualIndent);
4956 if (d->m_highlightBlocksInfo != highlightBlocksInfo) {
4957 d->m_highlightBlocksInfo = highlightBlocksInfo;
4958 viewport()->update();
4959 d->m_extraArea->update();
4963 void BaseTextEditorWidget::setActionHack(QObject *hack)
4965 d->m_actionHack = hack;
4968 QObject *BaseTextEditorWidget::actionHack() const
4970 return d->m_actionHack;
4973 void BaseTextEditorWidget::changeEvent(QEvent *e)
4975 QPlainTextEdit::changeEvent(e);
4976 if (e->type() == QEvent::ApplicationFontChange
4977 || e->type() == QEvent::FontChange) {
4978 if (d->m_extraArea) {
4979 QFont f = d->m_extraArea->font();
4980 f.setPointSizeF(font().pointSizeF());
4981 d->m_extraArea->setFont(f);
4982 slotUpdateExtraAreaWidth();
4983 d->m_extraArea->update();
4988 void BaseTextEditorWidget::focusInEvent(QFocusEvent *e)
4990 QPlainTextEdit::focusInEvent(e);
4994 void BaseTextEditorWidget::focusOutEvent(QFocusEvent *e)
4996 QPlainTextEdit::focusOutEvent(e);
4997 if (viewport()->cursor().shape() == Qt::BlankCursor)
4998 viewport()->setCursor(Qt::IBeamCursor);
5002 void BaseTextEditorWidget::maybeSelectLine()
5004 QTextCursor cursor = textCursor();
5005 if (!cursor.hasSelection()) {
5006 const QTextBlock &block = cursor.block();
5007 if (block.next().isValid()) {
5008 cursor.setPosition(block.position());
5009 cursor.setPosition(block.next().position(), QTextCursor::KeepAnchor);
5011 cursor.movePosition(QTextCursor::EndOfBlock);
5012 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
5013 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
5015 setTextCursor(cursor);
5020 void BaseTextEditorWidget::cutLine()
5026 void BaseTextEditorWidget::deleteLine()
5029 textCursor().removeSelectedText();
5032 void BaseTextEditorWidget::setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections)
5034 if (selections.isEmpty() && d->m_extraSelections[kind].isEmpty())
5036 d->m_extraSelections[kind] = selections;
5038 if (kind == CodeSemanticsSelection) {
5039 d->m_overlay->clear();
5040 foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
5041 d->m_overlay->addOverlaySelection(selection.cursor,
5042 selection.format.background().color(),
5043 selection.format.background().color(),
5044 TextEditorOverlay::LockSize);
5046 d->m_overlay->setVisible(!d->m_overlay->isEmpty());
5047 } else if (kind == SnippetPlaceholderSelection) {
5048 d->m_snippetOverlay->clear();
5049 foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
5050 d->m_snippetOverlay->addOverlaySelection(selection.cursor,
5051 selection.format.background().color(),
5052 selection.format.background().color(),
5053 TextEditorOverlay::ExpandBegin);
5055 d->m_snippetOverlay->mapEquivalentSelections();
5056 d->m_snippetOverlay->setVisible(!d->m_snippetOverlay->isEmpty());
5058 QList<QTextEdit::ExtraSelection> all;
5059 for (int i = 0; i < NExtraSelectionKinds; ++i) {
5060 if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection)
5062 all += d->m_extraSelections[i];
5064 QPlainTextEdit::setExtraSelections(all);
5068 QList<QTextEdit::ExtraSelection> BaseTextEditorWidget::extraSelections(ExtraSelectionKind kind) const
5070 return d->m_extraSelections[kind];
5073 void BaseTextEditorWidget::maybeClearSomeExtraSelections(const QTextCursor &cursor)
5075 const int smallSelectionSize = 50 * 50;
5076 if (cursor.selectionEnd() - cursor.selectionStart() < smallSelectionSize)
5079 d->m_extraSelections[UndefinedSymbolSelection].clear();
5080 d->m_extraSelections[ObjCSelection].clear();
5081 d->m_extraSelections[CodeWarningsSelection].clear();
5083 QList<QTextEdit::ExtraSelection> all;
5084 for (int i = 0; i < NExtraSelectionKinds; ++i) {
5085 if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection)
5087 all += d->m_extraSelections[i];
5089 QPlainTextEdit::setExtraSelections(all);
5092 QString BaseTextEditorWidget::extraSelectionTooltip(int pos) const
5094 QList<QTextEdit::ExtraSelection> all;
5095 for (int i = 0; i < NExtraSelectionKinds; ++i) {
5096 const QList<QTextEdit::ExtraSelection> &sel = d->m_extraSelections[i];
5097 for (int j = 0; j < sel.size(); ++j) {
5098 const QTextEdit::ExtraSelection &s = sel.at(j);
5099 if (s.cursor.selectionStart() <= pos
5100 && s.cursor.selectionEnd() >= pos
5101 && !s.format.toolTip().isEmpty())
5102 return s.format.toolTip();
5108 // the blocks list must be sorted
5109 void BaseTextEditorWidget::setIfdefedOutBlocks(const QList<BaseTextEditorWidget::BlockRange> &blocks)
5111 QTextDocument *doc = document();
5112 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5113 QTC_ASSERT(documentLayout, return);
5115 bool needUpdate = false;
5117 QTextBlock block = doc->firstBlock();
5119 int rangeNumber = 0;
5120 int braceDepthDelta = 0;
5121 while (block.isValid()) {
5122 bool cleared = false;
5124 if (rangeNumber < blocks.size()) {
5125 const BlockRange &range = blocks.at(rangeNumber);
5126 if (block.position() >= range.first && ((block.position() + block.length() - 1) <= range.last || !range.last)) {
5127 set = BaseTextDocumentLayout::setIfdefedOut(block);
5129 cleared = BaseTextDocumentLayout::clearIfdefedOut(block);
5131 if (block.contains(range.last))
5134 cleared = BaseTextDocumentLayout::clearIfdefedOut(block);
5137 if (cleared || set) {
5139 int delta = BaseTextDocumentLayout::braceDepthDelta(block);
5141 braceDepthDelta += delta;
5143 braceDepthDelta -= delta;
5146 if (braceDepthDelta) {
5147 BaseTextDocumentLayout::changeBraceDepth(block,braceDepthDelta);
5148 BaseTextDocumentLayout::changeFoldingIndent(block, braceDepthDelta); // ### C++ only, refactor!
5151 block = block.next();
5155 documentLayout->requestUpdate();
5158 void BaseTextEditorWidget::format()
5160 QTextCursor cursor = textCursor();
5161 cursor.beginEditBlock();
5162 indent(document(), cursor, QChar::Null);
5163 cursor.endEditBlock();
5166 void BaseTextEditorWidget::rewrapParagraph()
5168 const int paragraphWidth = displaySettings().m_wrapColumn;
5169 const QRegExp anyLettersOrNumbers = QRegExp("\\w");
5170 const int tabSize = tabSettings().m_tabSize;
5172 QTextCursor cursor = textCursor();
5173 cursor.beginEditBlock();
5175 // Find start of paragraph.
5177 while (cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor)) {
5178 QTextBlock block = cursor.block();
5179 QString text = block.text();
5181 // If this block is empty, move marker back to previous and terminate.
5182 if (!text.contains(anyLettersOrNumbers)) {
5183 cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
5188 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
5190 // Find indent level of current block.
5192 int indentLevel = 0;
5193 QString text = cursor.block().text();
5195 for (int i = 0; i < text.length(); i++) {
5196 const QChar ch = text.at(i);
5198 if (ch == QLatin1Char(' '))
5200 else if (ch == QLatin1Char('\t'))
5201 indentLevel += tabSize - (indentLevel % tabSize);
5206 // If there is a common prefix, it should be kept and expanded to all lines.
5207 // this allows nice reflowing of doxygen style comments.
5208 QTextCursor nextBlock = cursor;
5209 QString commonPrefix;
5211 if (nextBlock.movePosition(QTextCursor::NextBlock))
5213 QString nText = nextBlock.block().text();
5214 int maxLength = qMin(text.length(), nText.length());
5216 for (int i = 0; i < maxLength; ++i) {
5217 const QChar ch = text.at(i);
5219 if (ch != nText[i] || ch.isLetterOrNumber())
5221 commonPrefix.append(ch);
5226 // Find end of paragraph.
5227 while (cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor)) {
5228 QString text = cursor.block().text();
5230 if (!text.contains(anyLettersOrNumbers))
5235 QString selectedText = cursor.selectedText();
5237 // Preserve initial indent level.or common prefix.
5240 if (commonPrefix.isEmpty()) {
5241 spacing = tabSettings().indentationString(0, indentLevel, textCursor().block());
5243 spacing = commonPrefix;
5244 indentLevel = commonPrefix.length();
5247 int currentLength = indentLevel;
5249 result.append(spacing);
5251 // Remove existing instances of any common prefix from paragraph to
5253 selectedText.remove(0, commonPrefix.length());
5254 commonPrefix.prepend(QChar::ParagraphSeparator);
5255 selectedText.replace(commonPrefix, QLatin1String("\n"));
5257 // remove any repeated spaces, trim lines to PARAGRAPH_WIDTH width and
5258 // keep the same indentation level as first line in paragraph.
5259 QString currentWord;
5261 for (int i = 0; i < selectedText.length(); ++i) {
5262 QChar ch = selectedText.at(i);
5264 if (!currentWord.isEmpty()) {
5265 currentLength += currentWord.length() + 1;
5267 if (currentLength > paragraphWidth) {
5268 currentLength = currentWord.length() + 1 + indentLevel;
5269 result.chop(1); // remove trailing space
5270 result.append(QChar::ParagraphSeparator);
5271 result.append(spacing);
5274 result.append(currentWord);
5275 result.append(QLatin1Char(' '));
5276 currentWord.clear();
5282 currentWord.append(ch);
5285 result.append(QChar::ParagraphSeparator);
5287 cursor.insertText(result);
5288 cursor.endEditBlock();
5291 void BaseTextEditorWidget::unCommentSelection()
5295 void BaseTextEditorWidget::showEvent(QShowEvent* e)
5297 if (!d->m_fontSettings.isEmpty()) {
5298 setFontSettings(d->m_fontSettings);
5299 d->m_fontSettings.clear();
5301 QPlainTextEdit::showEvent(e);
5305 void BaseTextEditorWidget::setFontSettingsIfVisible(const TextEditor::FontSettings &fs)
5308 d->m_fontSettings = fs;
5311 setFontSettings(fs);
5314 void BaseTextEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
5316 const QTextCharFormat textFormat = fs.toTextCharFormat(QLatin1String(Constants::C_TEXT));
5317 const QTextCharFormat selectionFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SELECTION));
5318 const QTextCharFormat lineNumberFormat = fs.toTextCharFormat(QLatin1String(Constants::C_LINE_NUMBER));
5319 const QTextCharFormat searchResultFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_RESULT));
5320 d->m_searchScopeFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_SCOPE));
5321 const QTextCharFormat parenthesesFormat = fs.toTextCharFormat(QLatin1String(Constants::C_PARENTHESES));
5322 d->m_currentLineFormat = fs.toTextCharFormat(QLatin1String(Constants::C_CURRENT_LINE));
5323 d->m_currentLineNumberFormat = fs.toTextCharFormat(QLatin1String(Constants::C_CURRENT_LINE_NUMBER));
5324 d->m_linkFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_LINK));
5325 d->m_ifdefedOutFormat = fs.toTextCharFormat(QLatin1String(Constants::C_DISABLED_CODE));
5326 QFont font(textFormat.font());
5328 const QColor foreground = textFormat.foreground().color();
5329 const QColor background = textFormat.background().color();
5330 QPalette p = palette();
5331 p.setColor(QPalette::Text, foreground);
5332 p.setColor(QPalette::Foreground, foreground);
5333 p.setColor(QPalette::Base, background);
5334 p.setColor(QPalette::Highlight, (selectionFormat.background().style() != Qt::NoBrush) ?
5335 selectionFormat.background().color() :
5336 QApplication::palette().color(QPalette::Highlight));
5338 p.setBrush(QPalette::HighlightedText, selectionFormat.foreground());
5340 p.setBrush(QPalette::Inactive, QPalette::Highlight, p.highlight());
5341 p.setBrush(QPalette::Inactive, QPalette::HighlightedText, p.highlightedText());
5344 setTabSettings(d->m_document->tabSettings()); // update tabs, they depend on the font
5347 QPalette ep = d->m_extraArea->palette();
5348 ep.setColor(QPalette::Dark, lineNumberFormat.foreground().color());
5349 ep.setColor(QPalette::Background, lineNumberFormat.background().style() != Qt::NoBrush ?
5350 lineNumberFormat.background().color() : background);
5351 d->m_extraArea->setPalette(ep);
5354 d->m_searchResultFormat.setBackground(searchResultFormat.background());
5357 d->m_matchFormat.setForeground(parenthesesFormat.foreground());
5358 d->m_rangeFormat.setBackground(parenthesesFormat.background());
5362 d->m_occurrencesFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES));
5363 d->m_occurrencesFormat.clearForeground();
5364 d->m_occurrenceRenameFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES_RENAME));
5365 d->m_occurrenceRenameFormat.clearForeground();
5367 slotUpdateExtraAreaWidth(); // Adjust to new font width
5368 updateCurrentLineHighlight(); // Make sure it takes the new color
5371 void BaseTextEditorWidget::setTabSettings(const TabSettings &ts)
5373 d->m_document->setTabSettings(ts);
5374 int charWidth = QFontMetrics(font()).width(QChar(' '));
5375 setTabStopWidth(charWidth * ts.m_tabSize);
5378 void BaseTextEditorWidget::setDisplaySettings(const DisplaySettings &ds)
5380 setLineWrapMode(ds.m_textWrapping ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap);
5381 setLineNumbersVisible(ds.m_displayLineNumbers);
5382 setVisibleWrapColumn(ds.m_showWrapColumn ? ds.m_wrapColumn : 0);
5383 setHighlightCurrentLine(ds.m_highlightCurrentLine);
5384 setRevisionsVisible(ds.m_markTextChanges);
5385 setCenterOnScroll(ds.m_centerCursorOnScroll);
5387 if (d->m_displaySettings.m_visualizeWhitespace != ds.m_visualizeWhitespace) {
5388 if (SyntaxHighlighter *highlighter = baseTextDocument()->syntaxHighlighter())
5389 highlighter->rehighlight();
5390 QTextOption option = document()->defaultTextOption();
5391 if (ds.m_visualizeWhitespace)
5392 option.setFlags(option.flags() | QTextOption::ShowTabsAndSpaces);
5394 option.setFlags(option.flags() & ~QTextOption::ShowTabsAndSpaces);
5395 option.setFlags(option.flags() | QTextOption::AddSpaceForLineAndParagraphSeparators);
5396 document()->setDefaultTextOption(option);
5399 d->m_displaySettings = ds;
5400 if (!ds.m_highlightBlocks) {
5401 d->extraAreaHighlightFoldedBlockNumber = -1;
5402 d->m_highlightBlocksInfo = BaseTextEditorPrivateHighlightBlocks();
5405 updateCodeFoldingVisible();
5407 viewport()->update();
5408 extraArea()->update();
5411 void BaseTextEditorWidget::setBehaviorSettings(const TextEditor::BehaviorSettings &bs)
5413 setMouseNavigationEnabled(bs.m_mouseNavigation);
5414 setScrollWheelZoomingEnabled(bs.m_scrollWheelZooming);
5417 void BaseTextEditorWidget::setStorageSettings(const StorageSettings &storageSettings)
5419 d->m_document->setStorageSettings(storageSettings);
5422 void BaseTextEditorWidget::setCompletionSettings(const TextEditor::CompletionSettings &completionSettings)
5424 d->m_autoCompleter->setAutoParenthesesEnabled(completionSettings.m_autoInsertBrackets);
5425 d->m_autoCompleter->setSurroundWithEnabled(completionSettings.m_autoInsertBrackets);
5428 void BaseTextEditorWidget::setExtraEncodingSettings(const ExtraEncodingSettings &extraEncodingSettings)
5430 d->m_document->setExtraEncodingSettings(extraEncodingSettings);
5433 void BaseTextEditorWidget::fold()
5435 QTextDocument *doc = document();
5436 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5437 QTC_ASSERT(documentLayout, return);
5438 QTextBlock block = textCursor().block();
5439 if (!(BaseTextDocumentLayout::canFold(block) && block.next().isVisible())) {
5440 // find the closest previous block which can fold
5441 int indent = BaseTextDocumentLayout::foldingIndent(block);
5442 while (block.isValid() && (BaseTextDocumentLayout::foldingIndent(block) >= indent || !block.isVisible()))
5443 block = block.previous();
5445 if (block.isValid()) {
5446 BaseTextDocumentLayout::doFoldOrUnfold(block, false);
5447 d->moveCursorVisible();
5448 documentLayout->requestUpdate();
5449 documentLayout->emitDocumentSizeChanged();
5453 void BaseTextEditorWidget::unfold()
5455 QTextDocument *doc = document();
5456 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5457 QTC_ASSERT(documentLayout, return);
5458 QTextBlock block = textCursor().block();
5459 while (block.isValid() && !block.isVisible())
5460 block = block.previous();
5461 BaseTextDocumentLayout::doFoldOrUnfold(block, true);
5462 d->moveCursorVisible();
5463 documentLayout->requestUpdate();
5464 documentLayout->emitDocumentSizeChanged();
5467 void BaseTextEditorWidget::unfoldAll()
5469 QTextDocument *doc = document();
5470 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5471 QTC_ASSERT(documentLayout, return);
5473 QTextBlock block = doc->firstBlock();
5474 bool makeVisible = true;
5475 while (block.isValid()) {
5476 if (block.isVisible() && BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
5477 makeVisible = false;
5480 block = block.next();
5483 block = doc->firstBlock();
5485 while (block.isValid()) {
5486 if (BaseTextDocumentLayout::canFold(block))
5487 BaseTextDocumentLayout::doFoldOrUnfold(block, makeVisible);
5488 block = block.next();
5491 d->moveCursorVisible();
5492 documentLayout->requestUpdate();
5493 documentLayout->emitDocumentSizeChanged();
5497 void BaseTextEditorWidget::setTextCodec(QTextCodec *codec)
5499 baseTextDocument()->setCodec(codec);
5502 QTextCodec *BaseTextEditorWidget::textCodec() const
5504 return baseTextDocument()->codec();
5507 void BaseTextEditorWidget::setReadOnly(bool b)
5509 QPlainTextEdit::setReadOnly(b);
5511 setTextInteractionFlags(textInteractionFlags() | Qt::TextSelectableByKeyboard);
5514 void BaseTextEditorWidget::cut()
5516 if (d->m_inBlockSelectionMode) {
5518 d->removeBlockSelection();
5521 QPlainTextEdit::cut();
5524 void BaseTextEditorWidget::paste()
5526 if (d->m_inBlockSelectionMode) {
5527 d->removeBlockSelection();
5529 QPlainTextEdit::paste();
5532 QMimeData *BaseTextEditorWidget::createMimeDataFromSelection() const
5534 if (d->m_inBlockSelectionMode) {
5535 QMimeData *mimeData = new QMimeData;
5536 QString text = d->copyBlockSelection();
5537 mimeData->setData(QLatin1String("application/vnd.nokia.qtcreator.vblocktext"), text.toUtf8());
5538 mimeData->setText(text); // for exchangeability
5540 } else if (textCursor().hasSelection()) {
5541 QTextCursor cursor = textCursor();
5542 QMimeData *mimeData = new QMimeData;
5544 // Copy the selected text as plain text
5545 QString text = cursor.selectedText();
5546 convertToPlainText(text);
5547 mimeData->setText(text);
5549 // Copy the selected text as HTML
5551 // Create a new document from the selected text document fragment
5552 QTextDocument *tempDocument = new QTextDocument;
5553 QTextCursor tempCursor(tempDocument);
5554 tempCursor.insertFragment(cursor.selection());
5556 // Apply the additional formats set by the syntax highlighter
5557 QTextBlock start = document()->findBlock(cursor.selectionStart());
5558 QTextBlock end = document()->findBlock(cursor.selectionEnd());
5561 const int selectionStart = cursor.selectionStart();
5562 const int endOfDocument = tempDocument->characterCount() - 1;
5563 for (QTextBlock current = start; current.isValid() && current != end; current = current.next()) {
5564 const QTextLayout *layout = current.layout();
5565 foreach (const QTextLayout::FormatRange &range, layout->additionalFormats()) {
5566 const int start = current.position() + range.start - selectionStart;
5567 const int end = start + range.length;
5568 if (end <= 0 || start >= endOfDocument)
5570 tempCursor.setPosition(qMax(start, 0));
5571 tempCursor.setPosition(qMin(end, endOfDocument), QTextCursor::KeepAnchor);
5572 tempCursor.setCharFormat(range.format);
5576 // Reset the user states since they are not interesting
5577 for (QTextBlock block = tempDocument->begin(); block.isValid(); block = block.next())
5578 block.setUserState(-1);
5580 // Make sure the text appears pre-formatted
5581 tempCursor.setPosition(0);
5582 tempCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
5583 QTextBlockFormat blockFormat = tempCursor.blockFormat();
5584 blockFormat.setNonBreakableLines(true);
5585 tempCursor.setBlockFormat(blockFormat);
5587 mimeData->setHtml(tempCursor.selection().toHtml());
5588 delete tempDocument;
5592 Try to figure out whether we are copying an entire block, and store the complete block
5593 including indentation in the qtcreator.blocktext mimetype.
5595 QTextCursor selstart = cursor;
5596 selstart.setPosition(cursor.selectionStart());
5597 QTextCursor selend = cursor;
5598 selend.setPosition(cursor.selectionEnd());
5599 const TabSettings &ts = d->m_document->tabSettings();
5601 bool startOk = ts.cursorIsAtBeginningOfLine(selstart);
5602 bool multipleBlocks = (selend.block() != selstart.block());
5604 if (startOk && multipleBlocks) {
5605 selstart.movePosition(QTextCursor::StartOfBlock);
5606 if (ts.cursorIsAtBeginningOfLine(selend))
5607 selend.movePosition(QTextCursor::StartOfBlock);
5608 cursor.setPosition(selstart.position());
5609 cursor.setPosition(selend.position(), QTextCursor::KeepAnchor);
5610 text = cursor.selectedText();
5611 mimeData->setData(QLatin1String("application/vnd.nokia.qtcreator.blocktext"), text.toUtf8());
5618 bool BaseTextEditorWidget::canInsertFromMimeData(const QMimeData *source) const
5620 return QPlainTextEdit::canInsertFromMimeData(source);
5623 void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source)
5628 if (source->hasFormat(QLatin1String("application/vnd.nokia.qtcreator.vblocktext"))) {
5629 QString text = QString::fromUtf8(source->data(QLatin1String("application/vnd.nokia.qtcreator.vblocktext")));
5633 if (CompletionSupport::instance()->isActive())
5636 QStringList lines = text.split(QLatin1Char('\n'));
5637 QTextCursor cursor = textCursor();
5638 cursor.beginEditBlock();
5639 const TabSettings &ts = d->m_document->tabSettings();
5640 int initialCursorPosition = cursor.position();
5641 int column = ts.columnAt(cursor.block().text(), cursor.positionInBlock());
5642 cursor.insertText(lines.first());
5643 for (int i = 1; i < lines.count(); ++i) {
5644 QTextBlock next = cursor.block().next();
5645 if (next.isValid()) {
5646 cursor.setPosition(next.position());
5648 cursor.movePosition(QTextCursor::EndOfBlock);
5649 cursor.insertBlock();
5652 int position = ts.positionAtColumn(cursor.block().text(), column, &offset);
5653 cursor.setPosition(cursor.block().position() + position);
5655 cursor.deleteChar();
5656 cursor.insertText(QString(-offset, QLatin1Char(' ')));
5658 cursor.insertText(QString(offset, QLatin1Char(' ')));
5660 cursor.insertText(lines.at(i));
5662 cursor.setPosition(initialCursorPosition);
5663 cursor.endEditBlock();
5664 setTextCursor(cursor);
5665 ensureCursorVisible();
5667 if (d->m_snippetOverlay->isVisible() && lines.count() > 1) {
5668 d->m_snippetOverlay->hide();
5669 d->m_snippetOverlay->clear();
5675 QString text = source->text();
5679 if (CompletionSupport::instance()->isActive())
5682 if (d->m_snippetOverlay->isVisible() && (text.contains(QLatin1Char('\n'))
5683 || text.contains(QLatin1Char('\t')))) {
5684 d->m_snippetOverlay->hide();
5685 d->m_snippetOverlay->clear();
5688 const TabSettings &ts = d->m_document->tabSettings();
5689 QTextCursor cursor = textCursor();
5690 if (!ts.m_autoIndent) {
5691 cursor.beginEditBlock();
5692 cursor.insertText(text);
5693 cursor.endEditBlock();
5694 setTextCursor(cursor);
5698 cursor.beginEditBlock();
5699 cursor.removeSelectedText();
5701 bool insertAtBeginningOfLine = ts.cursorIsAtBeginningOfLine(cursor);
5703 if (insertAtBeginningOfLine
5704 && source->hasFormat(QLatin1String("application/vnd.nokia.qtcreator.blocktext"))) {
5705 text = QString::fromUtf8(source->data(QLatin1String("application/vnd.nokia.qtcreator.blocktext")));
5710 int reindentBlockStart = cursor.blockNumber() + (insertAtBeginningOfLine?0:1);
5712 bool hasFinalNewline = (text.endsWith(QLatin1Char('\n'))
5713 || text.endsWith(QChar::ParagraphSeparator)
5714 || text.endsWith(QLatin1Char('\r')));
5716 if (insertAtBeginningOfLine
5717 && hasFinalNewline) // since we'll add a final newline, preserve current line's indentation
5718 cursor.setPosition(cursor.block().position());
5720 int cursorPosition = cursor.position();
5721 cursor.insertText(text);
5723 int reindentBlockEnd = cursor.blockNumber() - (hasFinalNewline?1:0);
5725 if (reindentBlockStart < reindentBlockEnd
5726 || (reindentBlockStart == reindentBlockEnd
5727 && (!insertAtBeginningOfLine || hasFinalNewline))) {
5728 if (insertAtBeginningOfLine && !hasFinalNewline) {
5729 QTextCursor unnecessaryWhitespace = cursor;
5730 unnecessaryWhitespace.setPosition(cursorPosition);
5731 unnecessaryWhitespace.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
5732 unnecessaryWhitespace.removeSelectedText();
5734 QTextCursor c = cursor;
5735 c.setPosition(cursor.document()->findBlockByNumber(reindentBlockStart).position());
5736 c.setPosition(cursor.document()->findBlockByNumber(reindentBlockEnd).position(),
5737 QTextCursor::KeepAnchor);
5738 reindent(document(), c);
5741 cursor.endEditBlock();
5742 setTextCursor(cursor);
5745 void BaseTextEditorWidget::appendStandardContextMenuActions(QMenu *menu)
5747 menu->addSeparator();
5748 Core::ActionManager *am = Core::ICore::instance()->actionManager();
5750 QAction *a = am->command(Core::Constants::CUT)->action();
5751 if (a && a->isEnabled())
5753 a = am->command(Core::Constants::COPY)->action();
5754 if (a && a->isEnabled())
5756 a = am->command(Core::Constants::PASTE)->action();
5757 if (a && a->isEnabled())
5762 BaseTextEditor::BaseTextEditor(BaseTextEditorWidget *editor)
5765 using namespace Find;
5766 Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
5767 BaseTextFind *baseTextFind = new BaseTextFind(editor);
5768 connect(baseTextFind, SIGNAL(highlightAll(QString,Find::FindFlags)),
5769 editor, SLOT(highlightSearchResults(QString,Find::FindFlags)));
5770 connect(baseTextFind, SIGNAL(findScopeChanged(QTextCursor,QTextCursor,int,int)),
5771 editor, SLOT(setFindScope(QTextCursor,QTextCursor,int,int)));
5772 aggregate->add(baseTextFind);
5773 aggregate->add(editor);
5775 m_cursorPositionLabel = new Utils::LineColumnLabel;
5777 m_stretchWidget = new QWidget;
5778 m_stretchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
5780 m_toolBar = new QToolBar;
5781 m_toolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
5782 m_toolBar->addWidget(m_stretchWidget);
5783 m_cursorPositionLabelAction = m_toolBar->addWidget(m_cursorPositionLabel);
5785 connect(editor, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition()));
5788 BaseTextEditor::~BaseTextEditor()
5794 QWidget *BaseTextEditor::toolBar()
5799 void BaseTextEditor::insertExtraToolBarWidget(BaseTextEditor::Side side,
5802 if (widget->sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag) {
5803 if (m_stretchWidget)
5804 m_stretchWidget->deleteLater();
5805 m_stretchWidget = 0;
5809 m_toolBar->insertWidget(m_cursorPositionLabelAction, widget);
5811 m_toolBar->insertWidget(m_toolBar->actions().first(), widget);
5814 int BaseTextEditor::find(const QString &) const
5819 int BaseTextEditor::currentLine() const
5821 return e->textCursor().blockNumber() + 1;
5824 int BaseTextEditor::currentColumn() const
5826 QTextCursor cursor = e->textCursor();
5827 return cursor.position() - cursor.block().position() + 1;
5830 int BaseTextEditor::columnCount() const
5832 return e->columnCount();
5835 int BaseTextEditor::rowCount() const
5837 return e->rowCount();
5840 QRect BaseTextEditor::cursorRect(int pos) const
5842 QTextCursor tc = e->textCursor();
5844 tc.setPosition(pos);
5845 QRect result = e->cursorRect(tc);
5846 result.moveTo(e->viewport()->mapToGlobal(result.topLeft()));
5850 QString BaseTextEditor::contents() const
5852 return e->toPlainText();
5855 QString BaseTextEditor::selectedText() const
5857 if (e->textCursor().hasSelection())
5858 return e->textCursor().selectedText();
5862 QString BaseTextEditor::textAt(int pos, int length) const
5864 QTextCursor c = e->textCursor();
5868 c.movePosition(QTextCursor::End);
5869 if (pos + length > c.position())
5870 length = c.position() - pos;
5873 c.setPosition(pos + length, QTextCursor::KeepAnchor);
5875 return c.selectedText();
5878 void BaseTextEditor::remove(int length)
5880 QTextCursor tc = e->textCursor();
5881 tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
5882 tc.removeSelectedText();
5885 void BaseTextEditor::insert(const QString &string)
5887 QTextCursor tc = e->textCursor();
5888 tc.insertText(string);
5891 void BaseTextEditor::replace(int length, const QString &string)
5893 QTextCursor tc = e->textCursor();
5894 tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
5895 tc.insertText(string);
5898 void BaseTextEditor::setCursorPosition(int pos)
5900 QTextCursor tc = e->textCursor();
5901 tc.setPosition(pos);
5902 e->setTextCursor(tc);
5905 void BaseTextEditor::select(int toPos)
5907 QTextCursor tc = e->textCursor();
5908 tc.setPosition(toPos, QTextCursor::KeepAnchor);
5909 e->setTextCursor(tc);
5912 void BaseTextEditor::updateCursorPosition()
5914 const QTextCursor cursor = e->textCursor();
5915 const QTextBlock block = cursor.block();
5916 const int line = block.blockNumber() + 1;
5917 const int column = cursor.position() - block.position();
5918 m_cursorPositionLabel->setText(tr("Line: %1, Col: %2").arg(line).arg(e->tabSettings().columnAt(block.text(), column)+1),
5919 tr("Line: 9999, Col: 999"));
5920 m_contextHelpId.clear();
5922 if (!block.isVisible())
5923 e->ensureCursorVisible();
5927 QString BaseTextEditor::contextHelpId() const
5929 if (m_contextHelpId.isEmpty())
5930 emit const_cast<BaseTextEditor*>(this)->contextHelpIdRequested(e->editor(),
5931 e->textCursor().position());
5932 return m_contextHelpId;
5936 void BaseTextEditorWidget::setRefactorMarkers(const Internal::RefactorMarkers &markers)
5938 foreach (const RefactorMarker &marker, d->m_refactorOverlay->markers())
5939 requestBlockUpdate(marker.cursor.block());
5940 d->m_refactorOverlay->setMarkers(markers);
5941 foreach (const RefactorMarker &marker, markers)
5942 requestBlockUpdate(marker.cursor.block());
5945 void BaseTextEditorWidget::doFoo() {
5947 qDebug() << Q_FUNC_INFO;
5948 RefactorMarkers markers = d->m_refactorOverlay->markers();
5949 RefactorMarker marker;
5950 marker.tooltip = "Hello World";
5951 marker.cursor = textCursor();
5953 setRefactorMarkers(markers);
5957 void Internal::BaseTextBlockSelection::moveAnchor(int blockNumber, int visualColumn)
5959 if (visualColumn >= 0) {
5961 lastVisualColumn = visualColumn;
5962 if (lastVisualColumn < firstVisualColumn) {
5963 qSwap(firstVisualColumn, lastVisualColumn);
5964 anchor = (Anchor) (anchor - 1);
5967 firstVisualColumn = visualColumn;
5968 if (firstVisualColumn > lastVisualColumn) {
5969 qSwap(firstVisualColumn, lastVisualColumn);
5970 anchor = (Anchor) (anchor + 1);
5975 if (blockNumber >= 0 && blockNumber < firstBlock.document()->blockCount()) {
5976 if (anchor <= TopRight) {
5977 firstBlock.setPosition(firstBlock.document()->findBlockByNumber(blockNumber).position());
5978 if (firstBlock.blockNumber() > lastBlock.blockNumber()) {
5979 qSwap(firstBlock, lastBlock);
5980 anchor = (Anchor) (anchor + 2);
5983 lastBlock.setPosition(firstBlock.document()->findBlockByNumber(blockNumber).position());
5984 if (lastBlock.blockNumber() < firstBlock.blockNumber()) {
5985 qSwap(firstBlock, lastBlock);
5986 anchor = (Anchor) (anchor - 2);
5990 firstBlock.movePosition(QTextCursor::StartOfBlock);
5991 lastBlock.movePosition(QTextCursor::EndOfBlock);
5994 QTextCursor Internal::BaseTextBlockSelection::selection(const TabSettings &ts) const
5996 QTextCursor cursor = firstBlock;
5997 if (anchor <= TopRight) {
5998 cursor.setPosition(lastBlock.block().position() + ts.positionAtColumn(lastBlock.block().text(), lastVisualColumn));
5999 cursor.setPosition(firstBlock.block().position() + ts.positionAtColumn(firstBlock.block().text(), firstVisualColumn),
6000 QTextCursor::KeepAnchor);
6002 cursor.setPosition(firstBlock.block().position() + ts.positionAtColumn(firstBlock.block().text(), firstVisualColumn));
6003 cursor.setPosition(lastBlock.block().position() + ts.positionAtColumn(lastBlock.block().text(), lastVisualColumn),
6004 QTextCursor::KeepAnchor);
6009 void Internal::BaseTextBlockSelection::fromSelection(const TabSettings &ts, const QTextCursor &selection)
6011 firstBlock = selection;
6012 firstBlock.setPosition(selection.selectionStart());
6013 firstVisualColumn = ts.columnAt(firstBlock.block().text(), firstBlock.positionInBlock());
6014 lastBlock = selection;
6015 lastBlock.setPosition(selection.selectionEnd());
6016 lastVisualColumn = ts.columnAt(lastBlock.block().text(), lastBlock.positionInBlock());
6017 if (selection.anchor() > selection.position())
6020 anchor = BottomRight;
6022 firstBlock.movePosition(QTextCursor::StartOfBlock);
6023 lastBlock.movePosition(QTextCursor::EndOfBlock);
6025 bool BaseTextEditorWidget::inFindScope(const QTextCursor &cursor)
6027 if (cursor.isNull())
6029 return inFindScope(cursor.selectionStart(), cursor.selectionEnd());
6032 bool BaseTextEditorWidget::inFindScope(int selectionStart, int selectionEnd)
6034 if (d->m_findScopeStart.isNull())
6035 return true; // no scope, everything is included
6036 if (selectionStart < d->m_findScopeStart.position())
6038 if (selectionEnd > d->m_findScopeEnd.position())
6040 if (d->m_findScopeVerticalBlockSelectionFirstColumn < 0)
6042 QTextBlock block = document()->findBlock(selectionStart);
6043 if (block != document()->findBlock(selectionEnd))
6045 QString text = block.text();
6046 const TabSettings &ts = tabSettings();
6047 int startPosition = ts.positionAtColumn(text, d->m_findScopeVerticalBlockSelectionFirstColumn);
6048 int endPosition = ts.positionAtColumn(text, d->m_findScopeVerticalBlockSelectionLastColumn);
6049 if (selectionStart - block.position() < startPosition)
6051 if (selectionEnd - block.position() > endPosition)
6056 void BaseTextEditorWidget::setBlockSelection(bool on)
6058 if (d->m_inBlockSelectionMode != on) {
6059 d->m_inBlockSelectionMode = on;
6061 d->m_blockSelection.fromSelection(tabSettings(), textCursor());
6063 viewport()->update();
6066 bool BaseTextEditorWidget::hasBlockSelection() const
6068 return d->m_inBlockSelectionMode;
6071 void BaseTextEditorWidget::handleBlockSelection(int diff_row, int diff_col)
6074 if (!d->m_inBlockSelectionMode) {
6075 d->m_blockSelection.fromSelection(tabSettings(), textCursor());
6076 d->m_inBlockSelectionMode = true;
6079 d->m_blockSelection.moveAnchor(d->m_blockSelection.anchorBlockNumber() + diff_row,
6080 d->m_blockSelection.anchorColumnNumber() + diff_col);
6081 setTextCursor(d->m_blockSelection.selection(tabSettings()));
6083 viewport()->update();
6085 // ### TODO ensure horizontal visibility
6086 // const bool rtl = q->isRightToLeft();
6087 // if (cr.left() < visible.left() || cr.right() > visible.right()) {
6088 // int x = cr.center().x() + horizontalOffset() - visible.width()/2;
6089 // hbar->setValue(rtl ? hbar->maximum() - x : x);
6094 int BaseTextEditorWidget::columnCount() const
6096 QFontMetricsF fm(font());
6097 return viewport()->rect().width() / fm.width(QLatin1Char('x'));
6100 int BaseTextEditorWidget::rowCount() const
6102 QFontMetricsF fm(font());
6103 return viewport()->rect().height() / fm.lineSpacing();
6107 Helper method to transform a selected text. If nothing is selected at the moment
6108 the word under the cursor is used.
6109 The type of the transformation is determined by the method pointer given.
6111 @param method pointer to the QString method to use for the transformation
6113 @see uppercaseSelection, lowercaseSelection
6115 void BaseTextEditorWidget::transformSelection(Internal::TransformationMethod method)
6117 QTextCursor cursor = textCursor();
6119 int pos = cursor.position();
6120 int anchor = cursor.anchor();
6122 if (!cursor.hasSelection()) {
6123 // if nothing is selected, select the word over the cursor
6124 cursor.select(QTextCursor::WordUnderCursor);
6127 QString text = cursor.selectedText();
6128 QString transformedText = (text.*method)();
6130 if (transformedText == text) {
6131 // if the transformation does not do anything to the selection, do no create an undo step
6135 cursor.insertText(transformedText);
6137 // (re)select the changed text
6138 // Note: this assumes the transformation did not change the length,
6139 cursor.setPosition(anchor);
6140 cursor.setPosition(pos, QTextCursor::KeepAnchor);
6141 setTextCursor(cursor);