1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "texteditor_global.h"
36 #include "basetextdocument.h"
37 #include "basetextdocumentlayout.h"
38 #include "basetexteditor_p.h"
39 #include "behaviorsettings.h"
40 #include "codecselector.h"
41 #include "completionsettings.h"
42 #include "completionsupport.h"
43 #include "tabsettings.h"
44 #include "texteditorconstants.h"
45 #include "texteditorplugin.h"
46 #include "syntaxhighlighter.h"
48 #include "tipcontents.h"
50 #include "autocompleter.h"
53 #include <aggregation/aggregate.h>
54 #include <coreplugin/actionmanager/actionmanager.h>
55 #include <coreplugin/actionmanager/actioncontainer.h>
56 #include <coreplugin/actionmanager/command.h>
57 #include <coreplugin/coreconstants.h>
58 #include <coreplugin/editormanager/editormanager.h>
59 #include <coreplugin/icore.h>
60 #include <coreplugin/manhattanstyle.h>
61 #include <coreplugin/uniqueidmanager.h>
62 #include <extensionsystem/pluginmanager.h>
63 #include <find/basetextfind.h>
64 #include <utils/linecolumnlabel.h>
65 #include <utils/qtcassert.h>
66 #include <utils/stylehelper.h>
68 #include <QtCore/QCoreApplication>
69 #include <QtCore/QTextCodec>
70 #include <QtCore/QFile>
71 #include <QtCore/QDebug>
72 #include <QtCore/QTimer>
73 #include <QtCore/QTimeLine>
74 #include <QtCore/QTime>
75 #include <QtGui/QAbstractTextDocumentLayout>
76 #include <QtGui/QApplication>
77 #include <QtGui/QKeyEvent>
78 #include <QtGui/QLabel>
79 #include <QtGui/QLayout>
80 #include <QtGui/QPainter>
81 #include <QtGui/QPrinter>
82 #include <QtGui/QPrintDialog>
83 #include <QtGui/QScrollBar>
84 #include <QtGui/QShortcut>
85 #include <QtGui/QStyle>
86 #include <QtGui/QSyntaxHighlighter>
87 #include <QtGui/QTextCursor>
88 #include <QtGui/QTextDocumentFragment>
89 #include <QtGui/QTextBlock>
90 #include <QtGui/QTextLayout>
91 #include <QtGui/QToolBar>
92 #include <QtGui/QInputDialog>
93 #include <QtGui/QMenu>
97 using namespace TextEditor;
98 using namespace TextEditor::Internal;
100 namespace TextEditor {
103 class TextEditExtraArea : public QWidget {
104 BaseTextEditorWidget *textEdit;
106 TextEditExtraArea(BaseTextEditorWidget *edit):QWidget(edit) {
108 setAutoFillBackground(true);
112 QSize sizeHint() const {
113 return QSize(textEdit->extraAreaWidth(), 0);
116 void paintEvent(QPaintEvent *event){
117 textEdit->extraAreaPaintEvent(event);
119 void mousePressEvent(QMouseEvent *event){
120 textEdit->extraAreaMouseEvent(event);
122 void mouseMoveEvent(QMouseEvent *event){
123 textEdit->extraAreaMouseEvent(event);
125 void mouseReleaseEvent(QMouseEvent *event){
126 textEdit->extraAreaMouseEvent(event);
128 void leaveEvent(QEvent *event){
129 textEdit->extraAreaLeaveEvent(event);
132 void wheelEvent(QWheelEvent *event) {
133 QCoreApplication::sendEvent(textEdit->viewport(), event);
137 } // namespace Internal
138 } // namespace TextEditor
140 ITextEditor *BaseTextEditorWidget::openEditorAt(const QString &fileName, int line, int column,
141 const QString &editorKind,
142 Core::EditorManager::OpenEditorFlags flags,
145 Core::EditorManager *editorManager = Core::EditorManager::instance();
146 editorManager->cutForwardNavigationHistory();
147 editorManager->addCurrentPositionToNavigationHistory();
148 Core::IEditor *editor = editorManager->openEditor(fileName, editorKind,
150 TextEditor::ITextEditor *texteditor = qobject_cast<TextEditor::ITextEditor *>(editor);
152 texteditor->gotoLine(line, column);
159 static void convertToPlainText(QString &txt)
161 QChar *uc = txt.data();
162 QChar *e = uc + txt.size();
164 for (; uc != e; ++uc) {
165 switch (uc->unicode()) {
166 case 0xfdd0: // QTextBeginningOfFrame
167 case 0xfdd1: // QTextEndOfFrame
168 case QChar::ParagraphSeparator:
169 case QChar::LineSeparator:
170 *uc = QLatin1Char('\n');
173 *uc = QLatin1Char(' ');
181 BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent)
182 : QPlainTextEdit(parent)
184 d = new BaseTextEditorPrivate;
186 d->m_extraArea = new TextEditExtraArea(this);
187 d->m_extraArea->setMouseTracking(true);
188 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
190 d->m_overlay = new TextEditorOverlay(this);
191 d->m_snippetOverlay = new TextEditorOverlay(this);
192 d->m_searchResultOverlay = new TextEditorOverlay(this);
193 d->m_refactorOverlay = new RefactorOverlay(this);
195 d->setupDocumentSignals(d->m_document);
197 d->m_lastScrollPos = -1;
201 setLayoutDirection(Qt::LeftToRight);
202 viewport()->setMouseTracking(true);
203 d->extraAreaSelectionAnchorBlockNumber
204 = d->extraAreaToggleMarkBlockNumber
205 = d->extraAreaHighlightFoldedBlockNumber
208 d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber = -1;
210 connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth()));
211 connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(slotModificationChanged(bool)));
212 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
213 connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(slotUpdateRequest(QRect, int)));
214 connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
216 // (void) new QShortcut(tr("CTRL+L"), this, SLOT(centerCursor()), 0, Qt::WidgetShortcut);
217 // (void) new QShortcut(tr("F9"), this, SLOT(slotToggleMark()), 0, Qt::WidgetShortcut);
218 // (void) new QShortcut(tr("F11"), this, SLOT(slotToggleBlockVisible()));
221 (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo()));
225 // parentheses matcher
226 d->m_formatRange = true;
227 d->m_matchFormat.setForeground(Qt::red);
228 d->m_rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
229 d->m_mismatchFormat.setBackground(Qt::magenta);
230 d->m_parenthesesMatchingTimer = new QTimer(this);
231 d->m_parenthesesMatchingTimer->setSingleShot(true);
232 connect(d->m_parenthesesMatchingTimer, SIGNAL(timeout()), this, SLOT(_q_matchParentheses()));
234 d->m_highlightBlocksTimer = new QTimer(this);
235 d->m_highlightBlocksTimer->setSingleShot(true);
236 connect(d->m_highlightBlocksTimer, SIGNAL(timeout()), this, SLOT(_q_highlightBlocks()));
238 d->m_requestAutoCompletionTimer = new QTimer(this);
239 d->m_requestAutoCompletionTimer->setSingleShot(true);
240 d->m_requestAutoCompletionTimer->setInterval(500);
241 connect(d->m_requestAutoCompletionTimer, SIGNAL(timeout()), this, SLOT(_q_requestAutoCompletion()));
245 d->m_searchResultFormat.setBackground(QColor(0xffef0b));
247 slotUpdateExtraAreaWidth();
249 setFrameStyle(QFrame::NoFrame);
251 d->m_delayedUpdateTimer = new QTimer(this);
252 d->m_delayedUpdateTimer->setSingleShot(true);
253 connect(d->m_delayedUpdateTimer, SIGNAL(timeout()), viewport(), SLOT(update()));
255 connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
256 this, SLOT(currentEditorChanged(Core::IEditor*)));
258 d->m_moveLineUndoHack = false;
261 BaseTextEditorWidget::~BaseTextEditorWidget()
267 QString BaseTextEditorWidget::mimeType() const
269 return d->m_document->mimeType();
272 void BaseTextEditorWidget::setMimeType(const QString &mt)
274 d->m_document->setMimeType(mt);
277 void BaseTextEditorWidget::print(QPrinter *printer)
279 const bool oldFullPage = printer->fullPage();
280 printer->setFullPage(true);
281 QPrintDialog *dlg = new QPrintDialog(printer, this);
282 dlg->setWindowTitle(tr("Print Document"));
283 if (dlg->exec() == QDialog::Accepted) {
286 printer->setFullPage(oldFullPage);
290 static int foldBoxWidth(const QFontMetrics &fm)
292 const int lineSpacing = fm.lineSpacing();
293 return lineSpacing + lineSpacing%2 + 1;
296 static void printPage(int index, QPainter *painter, const QTextDocument *doc,
297 const QRectF &body, const QRectF &titleBox,
298 const QString &title)
302 painter->translate(body.left(), body.top() - (index - 1) * body.height());
303 QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
305 QAbstractTextDocumentLayout *layout = doc->documentLayout();
306 QAbstractTextDocumentLayout::PaintContext ctx;
308 painter->setFont(QFont(doc->defaultFont()));
309 QRectF box = titleBox.translated(0, view.top());
310 int dpix = painter->device()->logicalDpiX();
311 int dpiy = painter->device()->logicalDpiY();
312 int mx = 5 * dpix / 72.0;
313 int my = 2 * dpiy / 72.0;
314 painter->fillRect(box.adjusted(-mx, -my, mx, my), QColor(210, 210, 210));
315 if (!title.isEmpty())
316 painter->drawText(box, Qt::AlignCenter, title);
317 const QString pageString = QString::number(index);
318 painter->drawText(box, Qt::AlignRight, pageString);
320 painter->setClipRect(view);
322 // don't use the system palette text as default text color, on HP/UX
323 // for example that's white, and white text on white paper doesn't
325 ctx.palette.setColor(QPalette::Text, Qt::black);
327 layout->draw(painter, ctx);
332 void BaseTextEditorPrivate::print(QPrinter *printer)
334 QTextDocument *doc = q->document();
336 QString title = q->displayName();
338 printer->setDocName(title);
343 // Check that there is a valid device to print to.
347 doc = doc->clone(doc);
349 QTextOption opt = doc->defaultTextOption();
350 opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
351 doc->setDefaultTextOption(opt);
353 (void)doc->documentLayout(); // make sure that there is a layout
356 QColor background = q->palette().color(QPalette::Base);
357 bool backgroundIsDark = background.value() < 128;
359 for (QTextBlock srcBlock = q->document()->firstBlock(), dstBlock = doc->firstBlock();
360 srcBlock.isValid() && dstBlock.isValid();
361 srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
364 QList<QTextLayout::FormatRange> formatList = srcBlock.layout()->additionalFormats();
365 if (backgroundIsDark) {
366 // adjust syntax highlighting colors for better contrast
367 for (int i = formatList.count() - 1; i >=0; --i) {
368 QTextCharFormat &format = formatList[i].format;
369 if (format.background().color() == background) {
370 QBrush brush = format.foreground();
371 QColor color = brush.color();
373 color.getHsv(&h, &s, &v, &a);
374 color.setHsv(h, s, qMin(128, v), a);
375 brush.setColor(color);
376 format.setForeground(brush);
378 format.setBackground(Qt::white);
382 dstBlock.layout()->setAdditionalFormats(formatList);
385 QAbstractTextDocumentLayout *layout = doc->documentLayout();
386 layout->setPaintDevice(p.device());
388 int dpiy = p.device()->logicalDpiY();
389 int margin = (int) ((2/2.54)*dpiy); // 2 cm margins
391 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
392 fmt.setMargin(margin);
393 doc->rootFrame()->setFrameFormat(fmt);
395 QRectF pageRect(printer->pageRect());
396 QRectF body = QRectF(0, 0, pageRect.width(), pageRect.height());
397 QFontMetrics fontMetrics(doc->defaultFont(), p.device());
399 QRectF titleBox(margin,
401 - fontMetrics.height()
403 body.width() - 2*margin,
404 fontMetrics.height());
405 doc->setPageSize(body.size());
409 if (printer->collateCopies() == true){
411 pageCopies = printer->numCopies();
413 docCopies = printer->numCopies();
417 int fromPage = printer->fromPage();
418 int toPage = printer->toPage();
419 bool ascending = true;
421 if (fromPage == 0 && toPage == 0) {
423 toPage = doc->pageCount();
426 fromPage = qMax(1, fromPage);
427 toPage = qMin(doc->pageCount(), toPage);
429 if (printer->pageOrder() == QPrinter::LastPageFirst) {
436 for (int i = 0; i < docCopies; ++i) {
440 for (int j = 0; j < pageCopies; ++j) {
441 if (printer->printerState() == QPrinter::Aborted
442 || printer->printerState() == QPrinter::Error)
444 printPage(page, &p, doc, body, titleBox, title);
445 if (j < pageCopies - 1)
460 if ( i < docCopies - 1)
469 int BaseTextEditorPrivate::visualIndent(const QTextBlock &block) const
471 if (!block.isValid())
473 const QTextDocument *document = block.document();
475 while (i < block.length()) {
476 if (!document->characterAt(block.position() + i).isSpace()) {
477 QTextCursor cursor(block);
478 cursor.setPosition(block.position() + i);
479 return q->cursorRect(cursor).x();
487 ITextMarkable *BaseTextEditorWidget::markableInterface() const
489 return baseTextDocument()->documentMarker();
492 BaseTextEditor *BaseTextEditorWidget::editor() const
495 d->m_editor = const_cast<BaseTextEditorWidget*>(this)->createEditor();
496 connect(this, SIGNAL(textChanged()),
497 d->m_editor, SIGNAL(contentsChanged()));
498 connect(this, SIGNAL(changed()),
499 d->m_editor, SIGNAL(changed()));
505 void BaseTextEditorWidget::currentEditorChanged(Core::IEditor *ed)
507 if (ed == editor()) {
508 if (d->m_document->hasDecodingError()) {
509 Core::EditorManager::instance()->showEditorInfoBar(QLatin1String(Constants::SELECT_ENCODING),
510 tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.")
511 .arg(displayName()).arg(QString::fromLatin1(d->m_document->codec()->name())),
512 tr("Select Encoding"),
513 this, SLOT(selectEncoding()));
518 void BaseTextEditorWidget::selectEncoding()
520 BaseTextDocument *doc = d->m_document;
521 CodecSelector codecSelector(this, doc);
523 switch (codecSelector.exec()) {
524 case CodecSelector::Reload:
525 doc->reload(codecSelector.selectedCodec());
526 setReadOnly(d->m_document->hasDecodingError());
527 if (doc->hasDecodingError())
528 currentEditorChanged(Core::EditorManager::instance()->currentEditor());
530 Core::EditorManager::instance()->hideEditorInfoBar(QLatin1String(Constants::SELECT_ENCODING));
532 case CodecSelector::Save:
533 doc->setCodec(codecSelector.selectedCodec());
534 Core::EditorManager::instance()->saveEditor(editor());
536 case CodecSelector::Cancel:
541 QString BaseTextEditorWidget::msgTextTooLarge(quint64 size)
543 return tr("The text is too large to be displayed (%1 MB).").
547 bool BaseTextEditorWidget::createNew(const QString &contents)
549 if (contents.size() > Core::EditorManager::maxTextFileSize()) {
550 setPlainText(msgTextTooLarge(contents.size()));
551 document()->setModified(false);
554 setPlainText(contents);
555 document()->setModified(false);
559 bool BaseTextEditorWidget::open(const QString &fileName)
561 if (d->m_document->open(fileName)) {
562 moveCursor(QTextCursor::Start);
563 setReadOnly(d->m_document->hasDecodingError());
570 Collapses the first comment in a file, if there is only whitespace above
572 void BaseTextEditorPrivate::foldLicenseHeader()
574 QTextDocument *doc = q->document();
575 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
576 QTC_ASSERT(documentLayout, return);
577 QTextBlock block = doc->firstBlock();
578 const TabSettings &ts = m_document->tabSettings();
579 while (block.isValid() && block.isVisible()) {
580 QString text = block.text();
581 if (BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
582 if (text.trimmed().startsWith(QLatin1String("/*"))) {
583 BaseTextDocumentLayout::doFoldOrUnfold(block, false);
585 documentLayout->requestUpdate();
586 documentLayout->emitDocumentSizeChanged();
590 if (ts.firstNonSpace(text) < text.size())
592 block = block.next();
596 const Utils::ChangeSet &BaseTextEditorWidget::changeSet() const
598 return d->m_changeSet;
601 void BaseTextEditorWidget::setChangeSet(const Utils::ChangeSet &changeSet)
603 using namespace Utils;
605 d->m_changeSet = changeSet;
607 foreach (const ChangeSet::EditOp &op, changeSet.operationList()) {
608 // ### TODO: process the edit operation
611 case ChangeSet::EditOp::Replace:
614 case ChangeSet::EditOp::Move:
617 case ChangeSet::EditOp::Insert:
620 case ChangeSet::EditOp::Remove:
623 case ChangeSet::EditOp::Flip:
626 case ChangeSet::EditOp::Copy:
635 Core::IFile *BaseTextEditorWidget::file()
637 return d->m_document;
640 void BaseTextEditorWidget::editorContentsChange(int position, int charsRemoved, int charsAdded)
643 d->m_animator->finish();
645 d->m_contentsChanged = true;
646 QTextDocument *doc = document();
648 // Keep the line numbers and the block information for the text marks updated
649 if (charsRemoved != 0) {
650 d->updateMarksLineNumber();
651 d->updateMarksBlock(document()->findBlock(position));
653 const QTextBlock posBlock = doc->findBlock(position);
654 const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
655 if (posBlock != nextBlock) {
656 d->updateMarksLineNumber();
657 d->updateMarksBlock(posBlock);
658 d->updateMarksBlock(nextBlock);
660 d->updateMarksBlock(posBlock);
664 if (d->m_snippetOverlay->isVisible()) {
665 QTextCursor cursor = textCursor();
666 cursor.setPosition(position);
667 d->snippetCheckCursor(cursor);
670 if (doc->isRedoAvailable())
671 emit editor()->contentsChangedBecauseOfUndo();
674 void BaseTextEditorWidget::slotSelectionChanged()
676 if (d->m_inBlockSelectionMode && !textCursor().hasSelection()) {
677 d->m_inBlockSelectionMode = false;
678 d->m_blockSelection.clear();
679 viewport()->update();
682 if (!d->m_selectBlockAnchor.isNull() && !textCursor().hasSelection())
683 d->m_selectBlockAnchor = QTextCursor();
685 // Clear any link which might be showing when the selection changes
689 void BaseTextEditorWidget::gotoBlockStart()
691 QTextCursor cursor = textCursor();
692 if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
693 setTextCursor(cursor);
694 _q_matchParentheses();
698 void BaseTextEditorWidget::gotoBlockEnd()
700 QTextCursor cursor = textCursor();
701 if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
702 setTextCursor(cursor);
703 _q_matchParentheses();
707 void BaseTextEditorWidget::gotoBlockStartWithSelection()
709 QTextCursor cursor = textCursor();
710 if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
711 setTextCursor(cursor);
712 _q_matchParentheses();
716 void BaseTextEditorWidget::gotoBlockEndWithSelection()
718 QTextCursor cursor = textCursor();
719 if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
720 setTextCursor(cursor);
721 _q_matchParentheses();
726 void BaseTextEditorWidget::gotoLineStart()
728 handleHomeKey(false);
731 void BaseTextEditorWidget::gotoLineStartWithSelection()
736 void BaseTextEditorWidget::gotoLineEnd()
738 moveCursor(QTextCursor::EndOfLine);
741 void BaseTextEditorWidget::gotoLineEndWithSelection()
743 moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
746 void BaseTextEditorWidget::gotoNextLine()
748 moveCursor(QTextCursor::Down);
751 void BaseTextEditorWidget::gotoNextLineWithSelection()
753 moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor);
756 void BaseTextEditorWidget::gotoPreviousLine()
758 moveCursor(QTextCursor::Up);
761 void BaseTextEditorWidget::gotoPreviousLineWithSelection()
763 moveCursor(QTextCursor::Up, QTextCursor::KeepAnchor);
766 void BaseTextEditorWidget::gotoPreviousCharacter()
768 moveCursor(QTextCursor::PreviousCharacter);
771 void BaseTextEditorWidget::gotoPreviousCharacterWithSelection()
773 moveCursor(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
776 void BaseTextEditorWidget::gotoNextCharacter()
778 moveCursor(QTextCursor::NextCharacter);
781 void BaseTextEditorWidget::gotoNextCharacterWithSelection()
783 moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
786 void BaseTextEditorWidget::gotoPreviousWord()
788 moveCursor(QTextCursor::PreviousWord);
791 void BaseTextEditorWidget::gotoPreviousWordWithSelection()
793 moveCursor(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
796 void BaseTextEditorWidget::gotoNextWord()
798 moveCursor(QTextCursor::NextWord);
801 void BaseTextEditorWidget::gotoNextWordWithSelection()
803 moveCursor(QTextCursor::NextWord, QTextCursor::KeepAnchor);
806 void BaseTextEditorWidget::gotoPreviousWordCamelCase()
808 QTextCursor c = textCursor();
809 camelCaseLeft(c, QTextCursor::MoveAnchor);
813 void BaseTextEditorWidget::gotoPreviousWordCamelCaseWithSelection()
815 QTextCursor c = textCursor();
816 camelCaseLeft(c, QTextCursor::KeepAnchor);
820 void BaseTextEditorWidget::gotoNextWordCamelCase()
822 qDebug() << Q_FUNC_INFO;
823 QTextCursor c = textCursor();
824 camelCaseRight(c, QTextCursor::MoveAnchor);
828 void BaseTextEditorWidget::gotoNextWordCamelCaseWithSelection()
830 QTextCursor c = textCursor();
831 camelCaseRight(c, QTextCursor::KeepAnchor);
837 static QTextCursor flippedCursor(const QTextCursor &cursor)
839 QTextCursor flipped = cursor;
840 flipped.clearSelection();
841 flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
845 void BaseTextEditorWidget::selectBlockUp()
847 QTextCursor cursor = textCursor();
848 if (!cursor.hasSelection())
849 d->m_selectBlockAnchor = cursor;
851 cursor.setPosition(cursor.selectionStart());
854 if (!TextBlockUserData::findPreviousOpenParenthesis(&cursor, false))
856 if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true))
858 setTextCursor(flippedCursor(cursor));
859 _q_matchParentheses();
862 void BaseTextEditorWidget::selectBlockDown()
864 QTextCursor tc = textCursor();
865 QTextCursor cursor = d->m_selectBlockAnchor;
867 if (!tc.hasSelection() || cursor.isNull())
869 tc.setPosition(tc.selectionStart());
872 QTextCursor ahead = cursor;
873 if (!TextBlockUserData::findPreviousOpenParenthesis(&ahead, false))
875 if (ahead.position() <= tc.position())
879 if ( cursor != d->m_selectBlockAnchor)
880 TextBlockUserData::findNextClosingParenthesis(&cursor, true);
882 setTextCursor(flippedCursor(cursor));
883 _q_matchParentheses();
886 void BaseTextEditorWidget::copyLineUp()
888 copyLineUpDown(true);
891 void BaseTextEditorWidget::copyLineDown()
893 copyLineUpDown(false);
896 // @todo: Potential reuse of some code around the following functions...
897 void BaseTextEditorWidget::copyLineUpDown(bool up)
899 QTextCursor cursor = textCursor();
900 QTextCursor move = cursor;
901 move.beginEditBlock();
903 bool hasSelection = cursor.hasSelection();
906 move.setPosition(cursor.selectionStart());
907 move.movePosition(QTextCursor::StartOfBlock);
908 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
909 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
910 QTextCursor::KeepAnchor);
912 move.movePosition(QTextCursor::StartOfBlock);
913 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
916 QString text = move.selectedText();
919 move.setPosition(cursor.selectionStart());
920 move.movePosition(QTextCursor::StartOfBlock);
922 move.movePosition(QTextCursor::Left);
924 move.movePosition(QTextCursor::EndOfBlock);
925 if (move.atBlockStart()) {
926 move.movePosition(QTextCursor::NextBlock);
928 move.movePosition(QTextCursor::Left);
934 int start = move.position();
935 move.clearSelection();
936 move.insertText(text);
937 int end = move.position();
939 move.setPosition(start);
940 move.setPosition(end, QTextCursor::KeepAnchor);
942 indent(document(), move, QChar::Null);
948 void BaseTextEditorWidget::joinLines()
950 QTextCursor cursor = textCursor();
951 QTextCursor start = cursor;
952 QTextCursor end = cursor;
954 start.setPosition(cursor.selectionStart());
955 end.setPosition(cursor.selectionEnd() - 1);
957 int lineCount = qMax(1, end.blockNumber() - start.blockNumber());
959 cursor.beginEditBlock();
960 cursor.setPosition(cursor.selectionStart());
961 while (lineCount--) {
962 cursor.movePosition(QTextCursor::NextBlock);
963 cursor.movePosition(QTextCursor::StartOfBlock);
964 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
965 QString cutLine = cursor.selectedText();
967 // Collapse leading whitespaces to one or insert whitespace
968 cutLine.replace(QRegExp("^\\s*"), " ");
969 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
970 cursor.removeSelectedText();
972 cursor.movePosition(QTextCursor::PreviousBlock);
973 cursor.movePosition(QTextCursor::EndOfBlock);
975 cursor.insertText(cutLine);
977 cursor.endEditBlock();
979 setTextCursor(cursor);
982 void BaseTextEditorWidget::insertLineAbove()
984 QTextCursor cursor = textCursor();
985 cursor.beginEditBlock();
986 cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
987 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
988 cursor.insertBlock();
989 indent(document(), cursor, QChar::Null);
990 cursor.endEditBlock();
991 setTextCursor(cursor);
994 void BaseTextEditorWidget::insertLineBelow()
996 QTextCursor cursor = textCursor();
997 cursor.beginEditBlock();
998 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
999 cursor.insertBlock();
1000 indent(document(), cursor, QChar::Null);
1001 cursor.endEditBlock();
1002 setTextCursor(cursor);
1005 void BaseTextEditorWidget::moveLineUp()
1007 moveLineUpDown(true);
1010 void BaseTextEditorWidget::moveLineDown()
1012 moveLineUpDown(false);
1015 void BaseTextEditorWidget::uppercaseSelection()
1017 transformSelection(&QString::toUpper);
1021 void BaseTextEditorWidget::lowercaseSelection()
1023 transformSelection(&QString::toLower);
1026 void BaseTextEditorWidget::moveLineUpDown(bool up)
1028 QTextCursor cursor = textCursor();
1029 QTextCursor move = cursor;
1031 move.setVisualNavigation(false); // this opens folded items instead of destroying them
1033 if (d->m_moveLineUndoHack)
1034 move.joinPreviousEditBlock();
1036 move.beginEditBlock();
1038 bool hasSelection = cursor.hasSelection();
1040 if (cursor.hasSelection()) {
1041 move.setPosition(cursor.selectionStart());
1042 move.movePosition(QTextCursor::StartOfBlock);
1043 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
1044 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
1045 QTextCursor::KeepAnchor);
1047 move.movePosition(QTextCursor::StartOfBlock);
1048 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1050 QString text = move.selectedText();
1052 RefactorMarkers affectedMarkers;
1053 RefactorMarkers nonAffectedMarkers;
1054 QList<int> markerOffsets;
1056 foreach (const RefactorMarker &marker, d->m_refactorOverlay->markers()) {
1057 //test if marker is part of the selection to be moved
1058 if ((move.selectionStart() <= marker.cursor.position())
1059 && (move.selectionEnd() >= marker.cursor.position())) {
1060 affectedMarkers.append(marker);
1061 //remember the offset of markers in text
1062 int offset = marker.cursor.position() - move.selectionStart();
1063 markerOffsets.append(offset);
1065 nonAffectedMarkers.append(marker);
1069 move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
1070 move.removeSelectedText();
1073 move.movePosition(QTextCursor::PreviousBlock);
1075 move.movePosition(QTextCursor::Left);
1077 move.movePosition(QTextCursor::EndOfBlock);
1078 if (move.atBlockStart()) { // empty block
1079 move.movePosition(QTextCursor::NextBlock);
1081 move.movePosition(QTextCursor::Left);
1087 int start = move.position();
1088 move.clearSelection();
1089 move.insertText(text);
1090 int end = move.position();
1093 move.setPosition(start);
1094 move.setPosition(end, QTextCursor::KeepAnchor);
1097 //update positions of affectedMarkers
1098 for (int i=0;i < affectedMarkers.count(); i++) {
1099 int newPosition = start + markerOffsets.at(i);
1100 affectedMarkers[i].cursor.setPosition(newPosition);
1102 d->m_refactorOverlay->setMarkers(nonAffectedMarkers + affectedMarkers);
1104 reindent(document(), move);
1105 move.endEditBlock();
1107 setTextCursor(move);
1108 d->m_moveLineUndoHack = true;
1111 void BaseTextEditorWidget::cleanWhitespace()
1113 d->m_document->cleanWhitespace(textCursor());
1117 // could go into QTextCursor...
1118 static QTextLine currentTextLine(const QTextCursor &cursor)
1120 const QTextBlock block = cursor.block();
1121 if (!block.isValid())
1124 const QTextLayout *layout = block.layout();
1128 const int relativePos = cursor.position() - block.position();
1129 return layout->lineForTextPosition(relativePos);
1132 bool BaseTextEditorWidget::camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode)
1143 if (!cursor.movePosition(QTextCursor::Left, mode))
1147 QChar c = characterAt(cursor.position());
1148 Input input = Input_other;
1151 else if (c.isLower() || c.isDigit())
1153 else if (c == QLatin1Char('_'))
1154 input = Input_underscore;
1155 else if (c.isSpace() && c != QChar::ParagraphSeparator)
1156 input = Input_space;
1158 input = Input_other;
1169 case Input_underscore:
1176 cursor.movePosition(QTextCursor::Right, mode);
1177 return cursor.movePosition(QTextCursor::WordLeft, mode);
1185 cursor.movePosition(QTextCursor::Right, mode);
1197 cursor.movePosition(QTextCursor::Right, mode);
1203 case Input_underscore:
1212 cursor.movePosition(QTextCursor::Right, mode);
1226 case Input_underscore:
1230 cursor.movePosition(QTextCursor::Right, mode);
1231 if (cursor.positionInBlock() == 0)
1233 return cursor.movePosition(QTextCursor::WordLeft, mode);
1237 if (!cursor.movePosition(QTextCursor::Left, mode))
1242 bool BaseTextEditorWidget::camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode)
1254 QChar c = characterAt(cursor.position());
1255 Input input = Input_other;
1258 else if (c.isLower() || c.isDigit())
1260 else if (c == QLatin1Char('_'))
1261 input = Input_underscore;
1262 else if (c.isSpace() && c != QChar::ParagraphSeparator)
1263 input = Input_space;
1265 input = Input_other;
1276 case Input_underscore:
1280 return cursor.movePosition(QTextCursor::WordRight, mode);
1289 case Input_underscore:
1304 cursor.movePosition(QTextCursor::Left, mode);
1306 case Input_underscore:
1324 case Input_underscore:
1336 case Input_underscore:
1354 cursor.movePosition(QTextCursor::Right, mode);
1358 bool BaseTextEditorWidget::cursorMoveKeyEvent(QKeyEvent *e)
1360 QTextCursor cursor = textCursor();
1362 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
1363 QTextCursor::MoveOperation op = QTextCursor::NoMove;
1365 if (e == QKeySequence::MoveToNextChar) {
1366 op = QTextCursor::Right;
1368 else if (e == QKeySequence::MoveToPreviousChar) {
1369 op = QTextCursor::Left;
1371 else if (e == QKeySequence::SelectNextChar) {
1372 op = QTextCursor::Right;
1373 mode = QTextCursor::KeepAnchor;
1375 else if (e == QKeySequence::SelectPreviousChar) {
1376 op = QTextCursor::Left;
1377 mode = QTextCursor::KeepAnchor;
1379 else if (e == QKeySequence::SelectNextWord) {
1380 op = QTextCursor::WordRight;
1381 mode = QTextCursor::KeepAnchor;
1383 else if (e == QKeySequence::SelectPreviousWord) {
1384 op = QTextCursor::WordLeft;
1385 mode = QTextCursor::KeepAnchor;
1387 else if (e == QKeySequence::SelectStartOfLine) {
1388 op = QTextCursor::StartOfLine;
1389 mode = QTextCursor::KeepAnchor;
1391 else if (e == QKeySequence::SelectEndOfLine) {
1392 op = QTextCursor::EndOfLine;
1393 mode = QTextCursor::KeepAnchor;
1395 else if (e == QKeySequence::SelectStartOfBlock) {
1396 op = QTextCursor::StartOfBlock;
1397 mode = QTextCursor::KeepAnchor;
1399 else if (e == QKeySequence::SelectEndOfBlock) {
1400 op = QTextCursor::EndOfBlock;
1401 mode = QTextCursor::KeepAnchor;
1403 else if (e == QKeySequence::SelectStartOfDocument) {
1404 op = QTextCursor::Start;
1405 mode = QTextCursor::KeepAnchor;
1407 else if (e == QKeySequence::SelectEndOfDocument) {
1408 op = QTextCursor::End;
1409 mode = QTextCursor::KeepAnchor;
1411 else if (e == QKeySequence::SelectPreviousLine) {
1412 op = QTextCursor::Up;
1413 mode = QTextCursor::KeepAnchor;
1415 else if (e == QKeySequence::SelectNextLine) {
1416 op = QTextCursor::Down;
1417 mode = QTextCursor::KeepAnchor;
1419 QTextBlock block = cursor.block();
1420 QTextLine line = currentTextLine(cursor);
1421 if (!block.next().isValid()
1423 && line.lineNumber() == block.layout()->lineCount() - 1)
1424 op = QTextCursor::End;
1427 else if (e == QKeySequence::MoveToNextWord) {
1428 op = QTextCursor::WordRight;
1430 else if (e == QKeySequence::MoveToPreviousWord) {
1431 op = QTextCursor::WordLeft;
1433 else if (e == QKeySequence::MoveToEndOfBlock) {
1434 op = QTextCursor::EndOfBlock;
1436 else if (e == QKeySequence::MoveToStartOfBlock) {
1437 op = QTextCursor::StartOfBlock;
1439 else if (e == QKeySequence::MoveToNextLine) {
1440 op = QTextCursor::Down;
1442 else if (e == QKeySequence::MoveToPreviousLine) {
1443 op = QTextCursor::Up;
1445 else if (e == QKeySequence::MoveToPreviousLine) {
1446 op = QTextCursor::Up;
1448 else if (e == QKeySequence::MoveToStartOfLine) {
1449 op = QTextCursor::StartOfLine;
1451 else if (e == QKeySequence::MoveToEndOfLine) {
1452 op = QTextCursor::EndOfLine;
1454 else if (e == QKeySequence::MoveToStartOfDocument) {
1455 op = QTextCursor::Start;
1457 else if (e == QKeySequence::MoveToEndOfDocument) {
1458 op = QTextCursor::End;
1465 // Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but
1466 // here's the breakdown:
1467 // Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command),
1468 // Alt (Option), or Meta (Control).
1469 // Command/Control + Left/Right -- Move to left or right of the line
1470 // + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor)
1471 // Option + Left/Right -- Move one word Left/right.
1472 // + Up/Down -- Begin/End of Paragraph.
1473 // Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
1475 bool visualNavigation = cursor.visualNavigation();
1476 cursor.setVisualNavigation(true);
1478 if (op == QTextCursor::WordRight) {
1479 camelCaseRight(cursor, mode);
1480 } else if (op == QTextCursor::WordLeft) {
1481 camelCaseLeft(cursor, mode);
1483 cursor.movePosition(op, mode);
1485 cursor.setVisualNavigation(visualNavigation);
1487 setTextCursor(cursor);
1488 ensureCursorVisible();
1493 void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e)
1495 viewport()->setCursor(Qt::BlankCursor);
1496 ToolTip::instance()->hide();
1498 d->m_moveLineUndoHack = false;
1499 d->clearVisibleFoldedBlock();
1501 if (e->key() == Qt::Key_Escape) {
1502 if (d->m_snippetOverlay->isVisible()) {
1504 d->m_snippetOverlay->hide();
1505 d->m_snippetOverlay->clear();
1506 QTextCursor cursor = textCursor();
1507 cursor.clearSelection();
1508 setTextCursor(cursor);
1513 bool ro = isReadOnly();
1515 if (d->m_inBlockSelectionMode) {
1516 if (e == QKeySequence::Cut) {
1522 } else if (e == QKeySequence::Delete || e->key() == Qt::Key_Backspace) {
1524 d->removeBlockSelection();
1528 } else if (e == QKeySequence::Paste) {
1530 d->removeBlockSelection();
1538 && (e == QKeySequence::InsertParagraphSeparator
1539 || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))
1542 if (d->m_snippetOverlay->isVisible()) {
1544 d->m_snippetOverlay->hide();
1545 d->m_snippetOverlay->clear();
1546 QTextCursor cursor = textCursor();
1547 cursor.movePosition(QTextCursor::EndOfBlock);
1548 setTextCursor(cursor);
1553 QTextCursor cursor = textCursor();
1554 if (d->m_inBlockSelectionMode)
1555 cursor.clearSelection();
1556 const TabSettings &ts = d->m_document->tabSettings();
1557 cursor.beginEditBlock();
1560 d->m_autoCompleter->paragraphSeparatorAboutToBeInserted(cursor, tabSettings());
1562 QString previousIndentationString;
1563 if (ts.m_autoIndent) {
1564 cursor.insertBlock();
1565 indent(document(), cursor, QChar::Null);
1567 cursor.insertBlock();
1569 // After inserting the block, to avoid duplicating whitespace on the same line
1570 const QString &previousBlockText = cursor.block().previous().text();
1571 previousIndentationString = ts.indentationString(previousBlockText);
1572 if (!previousIndentationString.isEmpty())
1573 cursor.insertText(previousIndentationString);
1575 cursor.endEditBlock();
1578 if (extraBlocks > 0) {
1579 QTextCursor ensureVisible = cursor;
1580 while (extraBlocks > 0) {
1582 ensureVisible.movePosition(QTextCursor::NextBlock);
1583 if (ts.m_autoIndent)
1584 indent(document(), ensureVisible, QChar::Null);
1585 else if (!previousIndentationString.isEmpty())
1586 ensureVisible.insertText(previousIndentationString);
1588 setTextCursor(ensureVisible);
1591 setTextCursor(cursor);
1594 && (e == QKeySequence::MoveToStartOfBlock
1595 || e == QKeySequence::SelectStartOfBlock)){
1596 if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {
1600 handleHomeKey(e == QKeySequence::SelectStartOfBlock);
1604 && (e == QKeySequence::MoveToStartOfLine
1605 || e == QKeySequence::SelectStartOfLine)){
1606 if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {
1610 QTextCursor cursor = textCursor();
1611 if (QTextLayout *layout = cursor.block().layout()) {
1612 if (layout->lineForTextPosition(cursor.position() - cursor.block().position()).lineNumber() == 0) {
1613 handleHomeKey(e == QKeySequence::SelectStartOfLine);
1619 && e == QKeySequence::DeleteStartOfWord
1620 && d->m_document->tabSettings().m_autoIndent
1621 && !textCursor().hasSelection()){
1623 QTextCursor c = textCursor();
1624 int pos = c.position();
1625 camelCaseLeft(c, QTextCursor::MoveAnchor);
1626 int targetpos = c.position();
1628 handleBackspaceKey();
1629 int cpos = textCursor().position();
1630 if (cpos == pos || cpos <= targetpos)
1635 } else if (!ro && e == QKeySequence::DeleteStartOfWord && !textCursor().hasSelection()) {
1637 QTextCursor c = textCursor();
1638 camelCaseLeft(c, QTextCursor::KeepAnchor);
1639 c.removeSelectedText();
1641 } else if (!ro && e == QKeySequence::DeleteEndOfWord && !textCursor().hasSelection()) {
1643 QTextCursor c = textCursor();
1644 camelCaseRight(c, QTextCursor::KeepAnchor);
1645 c.removeSelectedText();
1647 } else switch (e->key()) {
1651 case Qt::Key_Dollar: {
1652 d->m_overlay->setVisible(!d->m_overlay->isVisible());
1653 d->m_overlay->setCursor(textCursor());
1660 case Qt::Key_Backtab: {
1662 if (d->m_snippetOverlay->isVisible() && !d->m_snippetOverlay->isEmpty()) {
1663 d->snippetTabOrBacktab(e->key() == Qt::Key_Tab);
1667 QTextCursor cursor = textCursor();
1669 if (d->m_document->tabSettings().tabShouldIndent(document(), cursor, &newPosition)) {
1670 if (newPosition != cursor.position() && !cursor.hasSelection()) {
1671 cursor.setPosition(newPosition);
1672 setTextCursor(cursor);
1674 indent(document(), cursor, QChar::Null);
1676 indentOrUnindent(e->key() == Qt::Key_Tab);
1681 case Qt::Key_Backspace:
1683 if ((e->modifiers() & (Qt::ControlModifier
1686 | Qt::MetaModifier)) == Qt::NoModifier
1687 && !textCursor().hasSelection()) {
1688 handleBackspaceKey();
1695 if (e->modifiers() & Qt::ControlModifier) {
1696 verticalScrollBar()->triggerAction(
1697 e->key() == Qt::Key_Up ? QAbstractSlider::SliderSingleStepSub :
1698 QAbstractSlider::SliderSingleStepAdd);
1706 if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {
1709 if (e->key() == Qt::Key_Up)
1711 else if (e->key() == Qt::Key_Down)
1713 else if (e->key() == Qt::Key_Left)
1715 else if (e->key() == Qt::Key_Right)
1717 handleBlockSelection(diff_row, diff_col);
1721 // leave block selection mode
1722 if (d->m_inBlockSelectionMode) {
1723 d->m_inBlockSelectionMode = false;
1724 d->m_blockSelection.clear();
1725 viewport()->update();
1730 case Qt::Key_PageUp:
1731 case Qt::Key_PageDown:
1732 if (e->modifiers() == Qt::ControlModifier) {
1733 verticalScrollBar()->triggerAction(
1734 e->key() == Qt::Key_PageUp ? QAbstractSlider::SliderPageStepSub :
1735 QAbstractSlider::SliderPageStepAdd);
1745 if (d->m_inBlockSelectionMode) {
1746 QString text = e->text();
1747 if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) {
1748 d->removeBlockSelection(text);
1753 if (e->key() == Qt::Key_H && e->modifiers() ==
1765 if (ro || e->text().isEmpty() || !e->text().at(0).isPrint()) {
1766 if (cursorMoveKeyEvent(e))
1769 QTextCursor cursor = textCursor();
1770 bool cursorWithinSnippet = false;
1771 if (d->m_snippetOverlay->isVisible()
1772 && (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)) {
1773 cursorWithinSnippet = d->snippetCheckCursor(cursor);
1775 if (cursorWithinSnippet)
1776 cursor.beginEditBlock();
1778 QPlainTextEdit::keyPressEvent(e);
1780 if (cursorWithinSnippet) {
1781 cursor.endEditBlock();
1782 d->m_snippetOverlay->updateEquivalentSelections(textCursor());
1785 } else if ((e->modifiers() & (Qt::ControlModifier|Qt::AltModifier)) != Qt::ControlModifier){
1786 QTextCursor cursor = textCursor();
1787 QString text = e->text();
1788 const QString &autoText = d->m_autoCompleter->autoComplete(cursor, text);
1791 if (d->m_document->tabSettings().m_autoIndent) {
1792 foreach (QChar c, text) {
1793 if (d->m_indenter->isElectricCharacter(c)) {
1800 bool cursorWithinSnippet = false;
1801 if (d->m_snippetOverlay->isVisible())
1802 cursorWithinSnippet = d->snippetCheckCursor(cursor);
1804 bool doEditBlock = !electricChar.isNull() || !autoText.isEmpty() || cursorWithinSnippet;
1806 cursor.beginEditBlock();
1808 cursor.insertText(text);
1810 if (!autoText.isEmpty()) {
1811 int pos = cursor.position();
1812 cursor.insertText(autoText);
1813 //Select the inserted text, to be able to re-indent the inserted text
1814 cursor.setPosition(pos, QTextCursor::KeepAnchor);
1816 if (!electricChar.isNull() && d->m_autoCompleter->contextAllowsElectricCharacters(cursor))
1817 indent(document(), cursor, electricChar);
1818 if (!autoText.isEmpty()) {
1819 if (d->m_document->tabSettings().m_autoIndent)
1820 reindent(document(), cursor);
1821 cursor.setPosition(autoText.length() == 1 ? cursor.position() : cursor.anchor());
1825 cursor.endEditBlock();
1826 if (cursorWithinSnippet)
1827 d->m_snippetOverlay->updateEquivalentSelections(textCursor());
1830 setTextCursor(cursor);
1834 if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled)
1835 d->m_parenthesesMatchingTimer->start(50);
1838 if (!ro && d->m_contentsChanged && !e->text().isEmpty() && e->text().at(0).isPrint()) {
1839 maybeRequestAutoCompletion(e->text().at(0));
1844 void BaseTextEditorWidget::maybeRequestAutoCompletion(const QChar &ch)
1846 if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) {
1847 if (CompletionSupport::instance()->isActive())
1848 d->m_requestAutoCompletionTimer->stop();
1850 d->m_requestAutoCompletionRevision = document()->revision();
1851 d->m_requestAutoCompletionPosition = position();
1852 d->m_requestAutoCompletionTimer->start();
1855 d->m_requestAutoCompletionTimer->stop();
1856 CompletionSupport::instance()->complete(editor(), SemanticCompletion, false);
1860 void BaseTextEditorWidget::_q_requestAutoCompletion()
1862 d->m_requestAutoCompletionTimer->stop();
1864 if (CompletionSupport::instance()->isActive())
1867 if (d->m_requestAutoCompletionRevision == document()->revision()
1868 && d->m_requestAutoCompletionPosition == position())
1869 CompletionSupport::instance()->complete(editor(), SemanticCompletion, false);
1872 void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet)
1874 if ((snippet.count(Snippet::kVariableDelimiter) % 2) != 0) {
1875 qWarning() << "invalid snippet";
1879 QList<QTextEdit::ExtraSelection> selections;
1881 QTextCursor cursor = cursor_arg;
1882 cursor.beginEditBlock();
1883 cursor.removeSelectedText();
1884 const int startCursorPosition = cursor.position();
1887 QMap<int, int> positions;
1889 while (pos < snippet.size()) {
1890 if (snippet.at(pos) != Snippet::kVariableDelimiter) {
1891 const int start = pos;
1893 while (pos < snippet.size() && snippet.at(pos) != Snippet::kVariableDelimiter);
1894 cursor.insertText(snippet.mid(start, pos - start));
1896 // the start of a place holder.
1897 const int start = ++pos;
1898 for (; pos < snippet.size(); ++pos) {
1899 if (snippet.at(pos) == Snippet::kVariableDelimiter)
1903 Q_ASSERT(pos < snippet.size());
1904 Q_ASSERT(snippet.at(pos) == Snippet::kVariableDelimiter);
1906 const QString textToInsert = snippet.mid(start, pos - start);
1908 int cursorPosition = cursor.position();
1909 cursor.insertText(textToInsert);
1911 if (textToInsert.isEmpty()) {
1912 positions.insert(cursorPosition, 0);
1914 positions.insert(cursorPosition, textToInsert.length());
1921 QMapIterator<int,int> it(positions);
1922 while (it.hasNext()) {
1924 int length = it.value();
1925 int position = it.key();
1927 QTextCursor tc(document());
1928 tc.setPosition(position);
1929 tc.setPosition(position + length, QTextCursor::KeepAnchor);
1930 QTextEdit::ExtraSelection selection;
1931 selection.cursor = tc;
1932 selection.format = (length ? d->m_occurrencesFormat : d->m_occurrenceRenameFormat);
1933 selections.append(selection);
1936 cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
1937 indent(cursor.document(), cursor, QChar());
1938 cursor.endEditBlock();
1940 setExtraSelections(BaseTextEditorWidget::SnippetPlaceholderSelection, selections);
1942 if (! selections.isEmpty()) {
1943 const QTextEdit::ExtraSelection &selection = selections.first();
1945 cursor = textCursor();
1946 if (selection.cursor.hasSelection()) {
1947 cursor.setPosition(selection.cursor.selectionStart());
1948 cursor.setPosition(selection.cursor.selectionEnd(), QTextCursor::KeepAnchor);
1950 cursor.setPosition(selection.cursor.position());
1952 setTextCursor(cursor);
1957 void BaseTextEditorWidget::universalHelper()
1959 // Test function for development. Place your new fangled experiment here to
1960 // give it proper scrutiny before pushing it onto others.
1963 void BaseTextEditorWidget::setTextCursor(const QTextCursor &cursor)
1965 // workaround for QTextControl bug
1966 bool selectionChange = cursor.hasSelection() || textCursor().hasSelection();
1967 QTextCursor c = cursor;
1968 c.setVisualNavigation(true);
1969 QPlainTextEdit::setTextCursor(c);
1970 if (selectionChange)
1971 slotSelectionChanged();
1974 void BaseTextEditorWidget::gotoLine(int line, int column)
1976 d->m_lastCursorChangeWasInteresting = false; // avoid adding the previous position to history
1977 const int blockNumber = line - 1;
1978 const QTextBlock &block = document()->findBlockByNumber(blockNumber);
1979 if (block.isValid()) {
1980 QTextCursor cursor(block);
1982 cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
1984 int pos = cursor.position();
1985 while (characterAt(pos).category() == QChar::Separator_Space) {
1988 cursor.setPosition(pos);
1990 setTextCursor(cursor);
1993 saveCurrentCursorPositionForNavigation();
1996 int BaseTextEditorWidget::position(ITextEditor::PositionOperation posOp, int at) const
1998 QTextCursor tc = textCursor();
2003 if (posOp == ITextEditor::Current)
2004 return tc.position();
2007 case ITextEditor::EndOfLine:
2008 tc.movePosition(QTextCursor::EndOfLine);
2009 return tc.position();
2010 case ITextEditor::StartOfLine:
2011 tc.movePosition(QTextCursor::StartOfLine);
2012 return tc.position();
2013 case ITextEditor::Anchor:
2014 if (tc.hasSelection())
2017 case ITextEditor::EndOfDoc:
2018 tc.movePosition(QTextCursor::End);
2019 return tc.position();
2027 void BaseTextEditorWidget::convertPosition(int pos, int *line, int *column) const
2029 QTextBlock block = document()->findBlock(pos);
2030 if (!block.isValid()) {
2034 (*line) = block.blockNumber() + 1;
2035 (*column) = pos - block.position();
2039 QChar BaseTextEditorWidget::characterAt(int pos) const
2041 return document()->characterAt(pos);
2044 bool BaseTextEditorWidget::event(QEvent *e)
2046 d->m_contentsChanged = false;
2047 switch (e->type()) {
2048 case QEvent::ShortcutOverride:
2049 if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && d->m_snippetOverlay->isVisible()) {
2053 e->ignore(); // we are a really nice citizen
2060 return QPlainTextEdit::event(e);
2063 void BaseTextEditorWidget::duplicateFrom(BaseTextEditorWidget *widget)
2067 setDisplayName(widget->displayName());
2068 d->m_revisionsVisible = widget->d->m_revisionsVisible;
2069 if (d->m_document == widget->d->m_document)
2071 d->setupDocumentSignals(widget->d->m_document);
2072 d->m_document = widget->d->m_document;
2075 QString BaseTextEditorWidget::displayName() const
2077 return d->m_displayName;
2080 void BaseTextEditorWidget::setDisplayName(const QString &title)
2082 d->m_displayName = title;
2086 BaseTextDocument *BaseTextEditorWidget::baseTextDocument() const
2088 return d->m_document;
2091 void BaseTextEditorWidget::setBaseTextDocument(BaseTextDocument *doc)
2094 d->setupDocumentSignals(doc);
2095 d->m_document = doc;
2099 void BaseTextEditorWidget::documentAboutToBeReloaded()
2101 //memorize cursor position
2102 d->m_tempState = saveState();
2104 // remove extra selections (loads of QTextCursor objects)
2106 for (int i = 0; i < NExtraSelectionKinds; ++i)
2107 d->m_extraSelections[i].clear();
2108 QPlainTextEdit::setExtraSelections(QList<QTextEdit::ExtraSelection>());
2110 // clear all overlays
2111 d->m_overlay->clear();
2112 d->m_snippetOverlay->clear();
2113 d->m_searchResultOverlay->clear();
2114 d->m_refactorOverlay->clear();
2117 void BaseTextEditorWidget::documentReloaded()
2119 // restore cursor position
2120 restoreState(d->m_tempState);
2123 QByteArray BaseTextEditorWidget::saveState() const
2126 QDataStream stream(&state, QIODevice::WriteOnly);
2127 stream << 1; // version number
2128 stream << verticalScrollBar()->value();
2129 stream << horizontalScrollBar()->value();
2131 convertPosition(textCursor().position(), &line, &column);
2135 // store code folding state
2136 QList<int> foldedBlocks;
2137 QTextBlock block = document()->firstBlock();
2138 while (block.isValid()) {
2139 if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->folded()) {
2140 int number = block.blockNumber();
2141 foldedBlocks += number;
2143 block = block.next();
2145 stream << foldedBlocks;
2150 bool BaseTextEditorWidget::restoreState(const QByteArray &state)
2152 if (state.isEmpty()) {
2153 if (d->m_displaySettings.m_autoFoldFirstComment)
2154 d->foldLicenseHeader();
2162 QDataStream stream(state);
2170 QList<int> collapsedBlocks;
2171 stream >> collapsedBlocks;
2172 QTextDocument *doc = document();
2173 foreach(int blockNumber, collapsedBlocks) {
2174 QTextBlock block = doc->findBlockByNumber(qMax(0, blockNumber));
2175 if (block.isValid())
2176 BaseTextDocumentLayout::doFoldOrUnfold(block, false);
2179 if (d->m_displaySettings.m_autoFoldFirstComment)
2180 d->foldLicenseHeader();
2183 d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history
2184 gotoLine(lval, cval);
2185 verticalScrollBar()->setValue(vval);
2186 horizontalScrollBar()->setValue(hval);
2187 saveCurrentCursorPositionForNavigation();
2191 void BaseTextEditorWidget::setDefaultPath(const QString &defaultPath)
2193 baseTextDocument()->setDefaultPath(defaultPath);
2196 void BaseTextEditorWidget::setSuggestedFileName(const QString &suggestedFileName)
2198 baseTextDocument()->setSuggestedFileName(suggestedFileName);
2201 void BaseTextEditorWidget::setParenthesesMatchingEnabled(bool b)
2203 d->m_parenthesesMatchingEnabled = b;
2206 bool BaseTextEditorWidget::isParenthesesMatchingEnabled() const
2208 return d->m_parenthesesMatchingEnabled;
2211 void BaseTextEditorWidget::setHighlightCurrentLine(bool b)
2213 d->m_highlightCurrentLine = b;
2214 updateCurrentLineHighlight();
2217 bool BaseTextEditorWidget::highlightCurrentLine() const
2219 return d->m_highlightCurrentLine;
2222 void BaseTextEditorWidget::setLineNumbersVisible(bool b)
2224 d->m_lineNumbersVisible = b;
2225 slotUpdateExtraAreaWidth();
2228 bool BaseTextEditorWidget::lineNumbersVisible() const
2230 return d->m_lineNumbersVisible;
2233 void BaseTextEditorWidget::setMarksVisible(bool b)
2235 d->m_marksVisible = b;
2236 slotUpdateExtraAreaWidth();
2239 bool BaseTextEditorWidget::marksVisible() const
2241 return d->m_marksVisible;
2244 void BaseTextEditorWidget::setRequestMarkEnabled(bool b)
2246 d->m_requestMarkEnabled = b;
2249 bool BaseTextEditorWidget::requestMarkEnabled() const
2251 return d->m_requestMarkEnabled;
2254 void BaseTextEditorWidget::setLineSeparatorsAllowed(bool b)
2256 d->m_lineSeparatorsAllowed = b;
2259 bool BaseTextEditorWidget::lineSeparatorsAllowed() const
2261 return d->m_lineSeparatorsAllowed;
2264 void BaseTextEditorWidget::updateCodeFoldingVisible()
2266 const bool visible = d->m_codeFoldingSupported && d->m_displaySettings.m_displayFoldingMarkers;
2267 if (d->m_codeFoldingVisible != visible) {
2268 d->m_codeFoldingVisible = visible;
2269 slotUpdateExtraAreaWidth();
2273 bool BaseTextEditorWidget::codeFoldingVisible() const
2275 return d->m_codeFoldingVisible;
2279 * Sets whether code folding is supported by the syntax highlighter. When not
2280 * supported (the default), this makes sure the code folding is not shown.
2282 * Needs to be called before calling setCodeFoldingVisible.
2284 void BaseTextEditorWidget::setCodeFoldingSupported(bool b)
2286 d->m_codeFoldingSupported = b;
2287 updateCodeFoldingVisible();
2290 bool BaseTextEditorWidget::codeFoldingSupported() const
2292 return d->m_codeFoldingSupported;
2295 void BaseTextEditorWidget::setMouseNavigationEnabled(bool b)
2297 d->m_behaviorSettings.m_mouseNavigation = b;
2300 bool BaseTextEditorWidget::mouseNavigationEnabled() const
2302 return d->m_behaviorSettings.m_mouseNavigation;
2305 void BaseTextEditorWidget::setScrollWheelZoomingEnabled(bool b)
2307 d->m_behaviorSettings.m_scrollWheelZooming = b;
2310 bool BaseTextEditorWidget::scrollWheelZoomingEnabled() const
2312 return d->m_behaviorSettings.m_scrollWheelZooming;
2315 void BaseTextEditorWidget::setRevisionsVisible(bool b)
2317 d->m_revisionsVisible = b;
2318 slotUpdateExtraAreaWidth();
2321 bool BaseTextEditorWidget::revisionsVisible() const
2323 return d->m_revisionsVisible;
2326 void BaseTextEditorWidget::setVisibleWrapColumn(int column)
2328 d->m_visibleWrapColumn = column;
2329 viewport()->update();
2332 int BaseTextEditorWidget::visibleWrapColumn() const
2334 return d->m_visibleWrapColumn;
2337 void BaseTextEditorWidget::setIndenter(Indenter *indenter)
2339 // clear out existing code formatter data
2340 for (QTextBlock it = document()->begin(); it.isValid(); it = it.next()) {
2341 TextEditor::TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(it);
2343 userData->setCodeFormatterData(0);
2345 d->m_indenter.reset(indenter);
2348 Indenter *BaseTextEditorWidget::indenter() const
2350 return d->m_indenter.data();
2353 void BaseTextEditorWidget::setAutoCompleter(AutoCompleter *autoCompleter)
2355 d->m_autoCompleter.reset(autoCompleter);
2358 AutoCompleter *BaseTextEditorWidget::autoCompleter() const
2360 return d->m_autoCompleter.data();
2363 //--------- BaseTextEditorPrivate -----------
2365 BaseTextEditorPrivate::BaseTextEditorPrivate()
2367 m_lastScrollPos(-1),
2370 m_contentsChanged(false),
2371 m_lastCursorChangeWasInteresting(false),
2372 m_document(new BaseTextDocument),
2373 m_parenthesesMatchingEnabled(false),
2375 m_formatRange(false),
2376 m_parenthesesMatchingTimer(0),
2378 extraAreaSelectionAnchorBlockNumber(-1),
2379 extraAreaToggleMarkBlockNumber(-1),
2380 extraAreaHighlightFoldedBlockNumber(-1),
2382 m_snippetOverlay(0),
2383 m_searchResultOverlay(0),
2384 m_refactorOverlay(0),
2385 visibleFoldedBlockNumber(-1),
2386 suggestedVisibleFoldedBlockNumber(-1),
2387 m_mouseOnFoldedMarker(false),
2388 m_marksVisible(false),
2389 m_codeFoldingVisible(false),
2390 m_codeFoldingSupported(false),
2391 m_revisionsVisible(false),
2392 m_lineNumbersVisible(true),
2393 m_highlightCurrentLine(true),
2394 m_requestMarkEnabled(true),
2395 m_lineSeparatorsAllowed(false),
2396 m_visibleWrapColumn(0),
2397 m_linkPressed(false),
2398 m_delayedUpdateTimer(0),
2401 m_inBlockSelectionMode(false),
2402 m_moveLineUndoHack(false),
2403 m_findScopeVerticalBlockSelectionFirstColumn(-1),
2404 m_findScopeVerticalBlockSelectionLastColumn(-1),
2405 m_highlightBlocksTimer(0),
2406 m_requestAutoCompletionRevision(0),
2407 m_requestAutoCompletionPosition(0),
2408 m_requestAutoCompletionTimer(0),
2409 m_cursorBlockNumber(-1),
2410 m_autoCompleter(new AutoCompleter),
2411 m_indenter(new Indenter)
2415 BaseTextEditorPrivate::~BaseTextEditorPrivate()
2419 void BaseTextEditorPrivate::setupDocumentSignals(BaseTextDocument *document)
2421 BaseTextDocument *oldDocument = q->baseTextDocument();
2423 q->disconnect(oldDocument->document(), 0, q, 0);
2424 q->disconnect(oldDocument, 0, q, 0);
2427 QTextDocument *doc = document->document();
2428 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2429 if (!documentLayout) {
2430 QTextOption opt = doc->defaultTextOption();
2431 opt.setTextDirection(Qt::LeftToRight);
2432 opt.setFlags(opt.flags() | QTextOption::IncludeTrailingSpaces
2433 | QTextOption::AddSpaceForLineAndParagraphSeparators
2435 doc->setDefaultTextOption(opt);
2436 documentLayout = new BaseTextDocumentLayout(doc);
2437 doc->setDocumentLayout(documentLayout);
2440 q->setDocument(doc);
2441 q->setCursorWidth(2); // Applies to the document layout
2443 QObject::connect(documentLayout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(slotUpdateBlockNotify(QTextBlock)));
2444 QObject::connect(q, SIGNAL(requestBlockUpdate(QTextBlock)), documentLayout, SIGNAL(updateBlock(QTextBlock)));
2445 QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(changed()));
2446 QObject::connect(doc, SIGNAL(contentsChange(int,int,int)), q,
2447 SLOT(editorContentsChange(int,int,int)), Qt::DirectConnection);
2448 QObject::connect(document, SIGNAL(changed()), q, SIGNAL(changed()));
2449 QObject::connect(document, SIGNAL(titleChanged(QString)), q, SLOT(setDisplayName(const QString &)));
2450 QObject::connect(document, SIGNAL(aboutToReload()), q, SLOT(documentAboutToBeReloaded()));
2451 QObject::connect(document, SIGNAL(reloaded()), q, SLOT(documentReloaded()));
2452 q->slotUpdateExtraAreaWidth();
2456 bool BaseTextEditorPrivate::snippetCheckCursor(const QTextCursor &cursor)
2458 if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
2461 QTextCursor start = cursor;
2462 start.setPosition(cursor.selectionStart());
2463 QTextCursor end = cursor;
2464 end.setPosition(cursor.selectionEnd());
2465 if (!m_snippetOverlay->hasCursorInSelection(start)
2466 || !m_snippetOverlay->hasCursorInSelection(end)
2467 || m_snippetOverlay->hasFirstSelectionBeginMoved()) {
2468 m_snippetOverlay->setVisible(false);
2469 m_snippetOverlay->clear();
2475 void BaseTextEditorPrivate::snippetTabOrBacktab(bool forward)
2477 if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
2479 QTextCursor cursor = q->textCursor();
2480 OverlaySelection final;
2482 for (int i = 0; i < m_snippetOverlay->selections().count(); ++i){
2483 const OverlaySelection &selection = m_snippetOverlay->selections().at(i);
2484 if (selection.m_cursor_begin.position() >= cursor.position()
2485 && selection.m_cursor_end.position() > cursor.position()) {
2491 for (int i = m_snippetOverlay->selections().count()-1; i >= 0; --i){
2492 const OverlaySelection &selection = m_snippetOverlay->selections().at(i);
2493 if (selection.m_cursor_end.position() < cursor.position()) {
2500 if (final.m_cursor_begin.isNull())
2501 final = forward ? m_snippetOverlay->selections().first() : m_snippetOverlay->selections().last();
2503 if (final.m_cursor_begin.position() == final.m_cursor_end.position()) { // empty tab stop
2504 cursor.setPosition(final.m_cursor_end.position());
2506 cursor.setPosition(final.m_cursor_begin.position());
2507 cursor.setPosition(final.m_cursor_end.position(), QTextCursor::KeepAnchor);
2509 q->setTextCursor(cursor);
2512 // Calculate global position for a tooltip considering the left extra area.
2513 QPoint BaseTextEditorWidget::toolTipPosition(const QTextCursor &c) const
2515 const QPoint cursorPos = mapToGlobal(cursorRect(c).bottomRight() + QPoint(1,1));
2516 return cursorPos + QPoint(d->m_extraArea->width(),
2525 bool BaseTextEditorWidget::viewportEvent(QEvent *event)
2527 d->m_contentsChanged = false;
2528 if (event->type() == QEvent::ContextMenu) {
2529 const QContextMenuEvent *ce = static_cast<QContextMenuEvent*>(event);
2530 if (ce->reason() == QContextMenuEvent::Mouse && !textCursor().hasSelection())
2531 setTextCursor(cursorForPosition(ce->pos()));
2532 } else if (event->type() == QEvent::ToolTip) {
2533 const QHelpEvent *he = static_cast<QHelpEvent*>(event);
2534 if (QApplication::keyboardModifiers() & Qt::ControlModifier)
2535 return true; // eat tooltip event when control is pressed
2536 const QPoint &pos = he->pos();
2538 RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(pos);
2539 if (refactorMarker.isValid() && !refactorMarker.tooltip.isEmpty()) {
2540 ToolTip::instance()->show(he->globalPos(),
2541 TextContent(refactorMarker.tooltip),
2543 refactorMarker.rect);
2547 // Allow plugins to show tooltips
2548 const QTextCursor c = cursorForPosition(pos);
2549 const QPoint toolTipPoint = toolTipPosition(c);
2550 bool handled = false;
2551 BaseTextEditor *ed = editor();
2552 emit ed->tooltipOverrideRequested(ed, toolTipPoint, c.position(), &handled);
2554 emit ed->tooltipRequested(ed, toolTipPoint, c.position());
2557 return QPlainTextEdit::viewportEvent(event);
2561 void BaseTextEditorWidget::resizeEvent(QResizeEvent *e)
2563 QPlainTextEdit::resizeEvent(e);
2565 d->m_extraArea->setGeometry(
2566 QStyle::visualRect(layoutDirection(), cr,
2567 QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height())));
2570 QRect BaseTextEditorWidget::foldBox()
2572 if (d->m_highlightBlocksInfo.isEmpty() || d->extraAreaHighlightFoldedBlockNumber < 0)
2575 QTextBlock begin = document()->findBlockByNumber(d->m_highlightBlocksInfo.open.last());
2577 QTextBlock end = document()->findBlockByNumber(d->m_highlightBlocksInfo.close.first());
2578 if (!begin.isValid() || !end.isValid())
2580 QRectF br = blockBoundingGeometry(begin).translated(contentOffset());
2581 QRectF er = blockBoundingGeometry(end).translated(contentOffset());
2583 return QRect(d->m_extraArea->width() - foldBoxWidth(fontMetrics()),
2585 foldBoxWidth(fontMetrics()),
2586 er.bottom() - br.top());
2589 QTextBlock BaseTextEditorWidget::foldedBlockAt(const QPoint &pos, QRect *box) const
2591 QPointF offset(contentOffset());
2592 QTextBlock block = firstVisibleBlock();
2593 qreal top = blockBoundingGeometry(block).translated(offset).top();
2594 qreal bottom = top + blockBoundingRect(block).height();
2596 int viewportHeight = viewport()->height();
2598 while (block.isValid() && top <= viewportHeight) {
2599 QTextBlock nextBlock = block.next();
2600 if (block.isVisible() && bottom >= 0) {
2601 if (nextBlock.isValid() && !nextBlock.isVisible()) {
2602 QTextLayout *layout = block.layout();
2603 QTextLine line = layout->lineAt(layout->lineCount()-1);
2604 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
2605 lineRect.adjust(0, 0, -1, -1);
2607 QRectF collapseRect(lineRect.right() + 12,
2609 fontMetrics().width(QLatin1String(" {...}; ")),
2611 if (collapseRect.contains(pos)) {
2612 QTextBlock result = block;
2614 *box = collapseRect.toAlignedRect();
2618 while (nextBlock.isValid() && !nextBlock.isVisible()) {
2620 nextBlock = block.next();
2628 bottom = top + blockBoundingRect(block).height();
2630 return QTextBlock();
2633 void BaseTextEditorPrivate::highlightSearchResults(const QTextBlock &block,
2634 TextEditorOverlay *overlay)
2636 if (m_searchExpr.isEmpty())
2639 int blockPosition = block.position();
2641 QTextCursor cursor = q->textCursor();
2642 QString text = block.text();
2643 text.replace(QChar::Nbsp, QLatin1Char(' '));
2647 while (idx < text.length()) {
2648 idx = m_searchExpr.indexIn(text, idx + l);
2651 l = m_searchExpr.matchedLength();
2654 if ((m_findFlags & Find::FindWholeWords)
2655 && ((idx && text.at(idx-1).isLetterOrNumber())
2656 || (idx + l < text.length() && text.at(idx + l).isLetterOrNumber())))
2659 if (!q->inFindScope(blockPosition + idx, blockPosition + idx + l))
2662 overlay->addOverlaySelection(blockPosition + idx,
2663 blockPosition + idx + l,
2664 m_searchResultFormat.background().color().darker(120),
2666 (idx == cursor.selectionStart() - blockPosition
2667 && idx + l == cursor.selectionEnd() - blockPosition)?
2668 TextEditorOverlay::DropShadow : 0);
2674 namespace TextEditor {
2675 namespace Internal {
2676 struct BlockSelectionData {
2686 void BaseTextEditorPrivate::clearBlockSelection()
2688 if (m_inBlockSelectionMode) {
2689 m_inBlockSelectionMode = false;
2690 m_blockSelection.clear();
2691 QTextCursor cursor = q->textCursor();
2692 cursor.clearSelection();
2693 q->setTextCursor(cursor);
2697 QString BaseTextEditorPrivate::copyBlockSelection()
2700 QTextCursor cursor = q->textCursor();
2701 if (!m_inBlockSelectionMode)
2703 const TabSettings &ts = q->tabSettings();
2704 QTextBlock block = m_blockSelection.firstBlock.block();
2705 QTextBlock lastBlock = m_blockSelection.lastBlock.block();
2707 QString text = block.text();
2708 int startOffset = 0;
2709 int startPos = ts.positionAtColumn(text, m_blockSelection.firstVisualColumn, &startOffset);
2711 int endPos = ts.positionAtColumn(text, m_blockSelection.lastVisualColumn, &endOffset);
2713 if (startPos == endPos) {
2714 selection += QString(endOffset - startOffset, QLatin1Char(' '));
2716 if (startOffset < 0)
2717 selection += QString(-startOffset, QLatin1Char(' '));
2720 selection += text.mid(startPos, endPos - startPos);
2721 if (endOffset < 0) {
2722 selection += QString(ts.m_tabSize + endOffset, QLatin1Char(' '));
2723 } else if (endOffset > 0) {
2724 selection += QString(endOffset, QLatin1Char(' '));
2727 if (block == lastBlock)
2729 selection += QLatin1Char('\n');
2730 block = block.next();
2735 void BaseTextEditorPrivate::removeBlockSelection(const QString &text)
2737 QTextCursor cursor = q->textCursor();
2738 if (!cursor.hasSelection() || !m_inBlockSelectionMode)
2741 int cursorPosition = cursor.selectionStart();
2742 cursor.clearSelection();
2743 cursor.beginEditBlock();
2745 const TabSettings &ts = q->tabSettings();
2746 QTextBlock block = m_blockSelection.firstBlock.block();
2747 QTextBlock lastBlock = m_blockSelection.lastBlock.block();
2749 QString text = block.text();
2750 int startOffset = 0;
2751 int startPos = ts.positionAtColumn(text, m_blockSelection.firstVisualColumn, &startOffset);
2753 int endPos = ts.positionAtColumn(text, m_blockSelection.lastVisualColumn, &endOffset);
2755 cursor.setPosition(block.position() + startPos);
2756 cursor.setPosition(block.position() + endPos, QTextCursor::KeepAnchor);
2757 cursor.removeSelectedText();
2759 if (startOffset < 0)
2760 cursor.insertText(QString(ts.m_tabSize + startOffset, QLatin1Char(' ')));
2762 cursor.insertText(QString(-endOffset, QLatin1Char(' ')));
2764 if (block == lastBlock)
2766 block = block.next();
2769 cursor.setPosition(cursorPosition);
2770 if (!text.isEmpty())
2771 cursor.insertText(text);
2772 cursor.endEditBlock();
2773 q->setTextCursor(cursor);
2776 void BaseTextEditorPrivate::moveCursorVisible(bool ensureVisible)
2778 QTextCursor cursor = q->textCursor();
2779 if (!cursor.block().isVisible()) {
2780 cursor.setVisualNavigation(true);
2781 cursor.movePosition(QTextCursor::Up);
2782 q->setTextCursor(cursor);
2785 q->ensureCursorVisible();
2788 static QColor blendColors(const QColor &a, const QColor &b, int alpha)
2790 return QColor((a.red() * (256 - alpha) + b.red() * alpha) / 256,
2791 (a.green() * (256 - alpha) + b.green() * alpha) / 256,
2792 (a.blue() * (256 - alpha) + b.blue() * alpha) / 256);
2795 static QColor calcBlendColor(const QColor &baseColor, int level, int count)
2800 if (baseColor.value() > 128) {
2803 color80.setRgb(qMax(0, baseColor.red() - f80),
2804 qMax(0, baseColor.green() - f80),
2805 qMax(0, baseColor.blue() - f80));
2806 color90.setRgb(qMax(0, baseColor.red() - f90),
2807 qMax(0, baseColor.green() - f90),
2808 qMax(0, baseColor.blue() - f90));
2812 color80.setRgb(qMin(255, baseColor.red() + f80),
2813 qMin(255, baseColor.green() + f80),
2814 qMin(255, baseColor.blue() + f80));
2815 color90.setRgb(qMin(255, baseColor.red() + f90),
2816 qMin(255, baseColor.green() + f90),
2817 qMin(255, baseColor.blue() + f90));
2824 if (level == count - 1)
2827 const int blendFactor = level * (256 / (count - 2));
2829 return blendColors(color80, color90, blendFactor);
2832 void BaseTextEditorWidget::paintEvent(QPaintEvent *e)
2835 Here comes an almost verbatim copy of
2836 QPlainTextEdit::paintEvent() so we can adjust the extra
2837 selections dynamically to indicate all search results.
2839 //begin QPlainTextEdit::paintEvent()
2841 QPainter painter(viewport());
2842 QTextDocument *doc = document();
2843 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
2844 QTC_ASSERT(documentLayout, return);
2846 QPointF offset(contentOffset());
2847 QTextBlock textCursorBlock = textCursor().block();
2849 bool hasMainSelection = textCursor().hasSelection();
2850 bool suppressSyntaxInIfdefedOutBlock = (d->m_ifdefedOutFormat.foreground()
2851 != palette().foreground());
2853 QRect er = e->rect();
2854 QRect viewportRect = viewport()->rect();
2858 if (d->m_visibleWrapColumn > 0) {
2859 // Don't use QFontMetricsF::averageCharWidth here, due to it returning
2860 // a fractional size even when this is not supported by the platform.
2861 lineX = QFontMetricsF(font()).width(QLatin1Char('x')) * d->m_visibleWrapColumn + offset.x() + 4;
2863 if (lineX < viewportRect.width()) {
2864 const QBrush background = d->m_ifdefedOutFormat.background();
2865 painter.fillRect(QRectF(lineX, er.top(), viewportRect.width() - lineX, er.height()),
2868 const QColor col = (palette().base().color().value() > 128) ? Qt::black : Qt::white;
2869 const QPen pen = painter.pen();
2870 painter.setPen(blendColors(background.isOpaque() ? background.color() : palette().base().color(),
2872 painter.drawLine(QPointF(lineX, er.top()), QPointF(lineX, er.bottom()));
2873 painter.setPen(pen);
2877 // Set a brush origin so that the WaveUnderline knows where the wave started
2878 painter.setBrushOrigin(offset);
2880 // // keep right margin clean from full-width selection
2881 // int maxX = offset.x() + qMax((qreal)viewportRect.width(), documentLayout->documentSize().width())
2882 // - doc->documentMargin();
2883 // er.setRight(qMin(er.right(), maxX));
2884 // painter.setClipRect(er);
2886 bool editable = !isReadOnly();
2887 QTextBlock block = firstVisibleBlock();
2889 QAbstractTextDocumentLayout::PaintContext context = getPaintContext();
2891 if (!d->m_highlightBlocksInfo.isEmpty()) {
2892 const QColor baseColor = palette().base().color();
2894 // extra pass for the block highlight
2896 const int margin = 5;
2897 QTextBlock blockFP = block;
2898 QPointF offsetFP = offset;
2899 while (blockFP.isValid()) {
2900 QRectF r = blockBoundingRect(blockFP).translated(offsetFP);
2902 int n = blockFP.blockNumber();
2904 foreach (int i, d->m_highlightBlocksInfo.open)
2907 foreach (int i, d->m_highlightBlocksInfo.close)
2911 int count = d->m_highlightBlocksInfo.count();
2913 for (int i = 0; i <= depth; ++i) {
2914 const QColor &blendedColor = calcBlendColor(baseColor, i, count);
2915 int vi = i > 0 ? d->m_highlightBlocksInfo.visualIndent.at(i-1) : 0;
2917 oneRect.setWidth(viewport()->width());
2918 oneRect.adjust(vi, 0, -8*i, 0);
2919 if (oneRect.left() >= oneRect.right())
2921 if (lineX > 0 && oneRect.left() < lineX && oneRect.right() > lineX) {
2922 QRectF otherRect = r;
2923 otherRect.setLeft(lineX + 1);
2924 otherRect.setRight(oneRect.right());
2925 oneRect.setRight(lineX - 1);
2926 painter.fillRect(otherRect, blendedColor);
2928 painter.fillRect(oneRect, blendedColor);
2931 offsetFP.ry() += r.height();
2933 if (offsetFP.y() > viewportRect.height() + margin)
2936 blockFP = blockFP.next();
2937 if (!blockFP.isVisible()) {
2938 // invisible blocks do have zero line count
2939 blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
2944 int blockSelectionIndex = -1;
2946 if (d->m_inBlockSelectionMode
2947 && context.selections.count() && context.selections.last().cursor == textCursor()) {
2948 blockSelectionIndex = context.selections.size()-1;
2949 context.selections[blockSelectionIndex].format.clearBackground();
2952 QTextBlock visibleCollapsedBlock;
2953 QPointF visibleCollapsedBlockOffset;
2955 QTextLayout *cursor_layout = 0;
2956 QPointF cursor_offset;
2957 int cursor_cpos = 0;
2960 d->m_searchResultOverlay->clear();
2961 if (!d->m_searchExpr.isEmpty()) { // first pass for the search result overlays
2963 const int margin = 5;
2964 QTextBlock blockFP = block;
2965 QPointF offsetFP = offset;
2966 while (blockFP.isValid()) {
2967 QRectF r = blockBoundingRect(blockFP).translated(offsetFP);
2969 if (r.bottom() >= er.top() - margin && r.top() <= er.bottom() + margin) {
2970 d->highlightSearchResults(blockFP,
2971 d->m_searchResultOverlay);
2973 offsetFP.ry() += r.height();
2975 if (offsetFP.y() > viewportRect.height() + margin)
2978 blockFP = blockFP.next();
2979 if (!blockFP.isVisible()) {
2980 // invisible blocks do have zero line count
2981 blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
2988 { // extra pass for ifdefed out blocks
2989 QTextBlock blockIDO = block;
2990 QPointF offsetIDO = offset;
2991 while (blockIDO.isValid()) {
2993 QRectF r = blockBoundingRect(blockIDO).translated(offsetIDO);
2995 if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
2996 if (BaseTextDocumentLayout::ifdefedOut(blockIDO)) {
2998 rr.setRight(viewportRect.width() - offset.x());
3000 rr.setRight(qMin(lineX, rr.right()));
3001 painter.fillRect(rr, d->m_ifdefedOutFormat.background());
3004 offsetIDO.ry() += r.height();
3006 if (offsetIDO.y() > viewportRect.height())
3009 blockIDO = blockIDO.next();
3010 if (!blockIDO.isVisible()) {
3011 // invisible blocks do have zero line count
3012 blockIDO = doc->findBlockByLineNumber(blockIDO.firstLineNumber());
3018 // possible extra pass for the block selection find scope
3019 if (!d->m_findScopeStart.isNull() && d->m_findScopeVerticalBlockSelectionFirstColumn >= 0) {
3020 QTextBlock blockFS = block;
3021 QPointF offsetFS = offset;
3022 while (blockFS.isValid()) {
3024 QRectF r = blockBoundingRect(blockFS).translated(offsetFS);
3026 if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
3028 if (blockFS.position() >= d->m_findScopeStart.block().position()
3029 && blockFS.position() <= d->m_findScopeEnd.block().position()) {
3030 QTextLayout *layout = blockFS.layout();
3031 QString text = blockFS.text();
3032 const TabSettings &ts = tabSettings();
3033 qreal spacew = QFontMetricsF(font()).width(QLatin1Char(' '));
3036 int relativePos = ts.positionAtColumn(text,
3037 d->m_findScopeVerticalBlockSelectionFirstColumn,
3039 QTextLine line = layout->lineForTextPosition(relativePos);
3040 qreal x = line.cursorToX(relativePos) + offset * spacew;
3043 int erelativePos = ts.positionAtColumn(text,
3044 d->m_findScopeVerticalBlockSelectionLastColumn,
3046 QTextLine eline = layout->lineForTextPosition(erelativePos);
3047 qreal ex = eline.cursorToX(erelativePos) + eoffset * spacew;
3049 QRectF rr = line.naturalTextRect();
3050 rr.moveTop(rr.top() + r.top());
3051 rr.setLeft(r.left() + x);
3052 if (line.lineNumber() == eline.lineNumber()) {
3053 rr.setRight(r.left() + ex);
3055 painter.fillRect(rr, d->m_searchScopeFormat.background());
3057 QColor lineCol = d->m_searchScopeFormat.foreground().color();
3058 QPen pen = painter.pen();
3059 painter.setPen(lineCol);
3060 if (blockFS == d->m_findScopeStart.block())
3061 painter.drawLine(rr.topLeft(), rr.topRight());
3062 if (blockFS == d->m_findScopeEnd.block())
3063 painter.drawLine(rr.bottomLeft(), rr.bottomRight());
3064 painter.drawLine(rr.topLeft(), rr.bottomLeft());
3065 painter.drawLine(rr.topRight(), rr.bottomRight());
3066 painter.setPen(pen);
3069 offsetFS.ry() += r.height();
3071 if (offsetFS.y() > viewportRect.height())
3074 blockFS = blockFS.next();
3075 if (!blockFS.isVisible()) {
3076 // invisible blocks do have zero line count
3077 blockFS = doc->findBlockByLineNumber(blockFS.firstLineNumber());
3083 if (!d->m_findScopeStart.isNull() && d->m_findScopeVerticalBlockSelectionFirstColumn < 0) {
3085 TextEditorOverlay *overlay = new TextEditorOverlay(this);
3086 overlay->addOverlaySelection(d->m_findScopeStart.position(),
3087 d->m_findScopeEnd.position(),
3088 d->m_searchScopeFormat.foreground().color(),
3089 d->m_searchScopeFormat.background().color(),
3090 TextEditorOverlay::ExpandBegin);
3091 overlay->setAlpha(false);
3092 overlay->paint(&painter, e->rect());
3098 d->m_searchResultOverlay->fill(&painter,
3099 d->m_searchResultFormat.background().color(),
3103 while (block.isValid()) {
3105 QRectF r = blockBoundingRect(block).translated(offset);
3107 if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
3109 QTextLayout *layout = block.layout();
3111 QTextOption option = layout->textOption();
3112 if (suppressSyntaxInIfdefedOutBlock && BaseTextDocumentLayout::ifdefedOut(block)) {
3113 option.setFlags(option.flags() | QTextOption::SuppressColors);
3114 painter.setPen(d->m_ifdefedOutFormat.foreground().color());
3116 option.setFlags(option.flags() & ~QTextOption::SuppressColors);
3117 painter.setPen(context.palette.text().color());
3119 layout->setTextOption(option);
3120 layout->setFont(doc->defaultFont()); // this really should be in qplaintextedit when creating the layout!
3122 int blpos = block.position();
3123 int bllen = block.length();
3125 QVector<QTextLayout::FormatRange> selections;
3126 QVector<QTextLayout::FormatRange> prioritySelections;
3128 for (int i = 0; i < context.selections.size(); ++i) {
3129 const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
3130 const int selStart = range.cursor.selectionStart() - blpos;
3131 const int selEnd = range.cursor.selectionEnd() - blpos;
3132 if (selStart < bllen && selEnd >= 0
3133 && selEnd >= selStart) {
3134 QTextLayout::FormatRange o;
3136 o.length = selEnd - selStart;
3137 o.format = range.format;
3138 if (i == blockSelectionIndex) {
3139 QString text = block.text();
3140 const TabSettings &ts = tabSettings();
3141 o.start = ts.positionAtColumn(text, d->m_blockSelection.firstVisualColumn);
3142 o.length = ts.positionAtColumn(text, d->m_blockSelection.lastVisualColumn) - o.start;
3144 if ((hasMainSelection && i == context.selections.size()-1)
3145 || (o.format.foreground().style() == Qt::NoBrush
3146 && o.format.underlineStyle() != QTextCharFormat::NoUnderline
3147 && o.format.background() == Qt::NoBrush))
3148 prioritySelections.append(o);
3150 selections.append(o);
3153 // we disable fullwidth selection. It's only used for m_highlightCurrentLine which we
3154 // do differently now
3155 else if (!range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
3156 && block.contains(range.cursor.position())) {
3157 // for full width selections we don't require an actual selection, just
3158 // a position to specify the line. that's more convenience in usage.
3159 QTextLayout::FormatRange o;
3160 QTextLine l = layout->lineForTextPosition(range.cursor.position() - blpos);
3161 o.start = l.textStart();
3162 o.length = l.textLength();
3163 if (o.start + o.length == bllen - 1)
3164 ++o.length; // include newline
3165 o.format = range.format;
3166 selections.append(o);
3170 selections += prioritySelections;
3172 if (d->m_highlightCurrentLine && block == textCursorBlock) {
3174 QRectF rr = layout->lineForTextPosition(textCursor().positionInBlock()).rect();
3175 rr.moveTop(rr.top() + r.top());
3177 rr.setRight(viewportRect.width() - offset.x());
3178 QColor color = d->m_currentLineFormat.background().color();
3179 // set alpha, otherwise we cannot see block highlighting and find scope underneath
3180 color.setAlpha(128);
3181 painter.fillRect(rr, color);
3185 QRectF blockSelectionCursorRect;
3186 if (d->m_inBlockSelectionMode
3187 && block.position() >= d->m_blockSelection.firstBlock.block().position()
3188 && block.position() <= d->m_blockSelection.lastBlock.block().position()) {
3189 QString text = block.text();
3190 const TabSettings &ts = tabSettings();
3191 qreal spacew = QFontMetricsF(font()).width(QLatin1Char(' '));
3194 int relativePos = ts.positionAtColumn(text, d->m_blockSelection.firstVisualColumn, &offset);
3195 QTextLine line = layout->lineForTextPosition(relativePos);
3196 qreal x = line.cursorToX(relativePos) + offset * spacew;
3199 int erelativePos = ts.positionAtColumn(text, d->m_blockSelection.lastVisualColumn, &eoffset);
3200 QTextLine eline = layout->lineForTextPosition(erelativePos);
3201 qreal ex = eline.cursorToX(erelativePos) + eoffset * spacew;
3203 QRectF rr = line.naturalTextRect();
3204 rr.moveTop(rr.top() + r.top());
3205 rr.setLeft(r.left() + x);
3206 if (line.lineNumber() == eline.lineNumber()) {
3207 rr.setRight(r.left() + ex);
3209 painter.fillRect(rr, palette().highlight());
3210 if ((d->m_blockSelection.anchor == BaseTextBlockSelection::TopLeft
3211 && block == d->m_blockSelection.firstBlock.block())
3212 || (d->m_blockSelection.anchor == BaseTextBlockSelection::BottomLeft
3213 && block == d->m_blockSelection.lastBlock.block())
3215 rr.setRight(rr.left()+2);
3216 blockSelectionCursorRect = rr;
3218 for (int i = line.lineNumber() + 1; i < eline.lineNumber(); ++i) {
3219 rr = layout->lineAt(i).naturalTextRect();
3220 rr.moveTop(rr.top() + r.top());
3221 rr.setLeft(r.left() + x);
3222 painter.fillRect(rr, palette().highlight());
3225 rr = eline.naturalTextRect();
3226 rr.moveTop(rr.top() + r.top());
3227 rr.setRight(r.left() + ex);
3228 if (line.lineNumber() != eline.lineNumber())
3229 painter.fillRect(rr, palette().highlight());
3230 if ((d->m_blockSelection.anchor == BaseTextBlockSelection::TopRight
3231 && block == d->m_blockSelection.firstBlock.block())
3232 || (d->m_blockSelection.anchor == BaseTextBlockSelection::BottomRight
3233 && block == d->m_blockSelection.lastBlock.block())) {
3234 rr.setLeft(rr.right()-2);
3235 blockSelectionCursorRect = rr;
3240 bool drawCursor = ((editable || true) // we want the cursor in read-only mode
3241 && context.cursorPosition >= blpos
3242 && context.cursorPosition < blpos + bllen);
3244 bool drawCursorAsBlock = drawCursor && overwriteMode() ;
3246 if (drawCursorAsBlock) {
3247 int relativePos = context.cursorPosition - blpos;
3248 bool doSelection = true;
3249 QTextLine line = layout->lineForTextPosition(relativePos);
3250 qreal x = line.cursorToX(relativePos);
3252 if (relativePos < line.textLength() - line.textStart()) {
3253 w = line.cursorToX(relativePos + 1) - x;
3254 if (doc->characterAt(context.cursorPosition) == QLatin1Char('\t')) {
3255 doSelection = false;
3256 qreal space = QFontMetricsF(layout->font()).width(QLatin1Char(' '));
3263 w = QFontMetrics(layout->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()
3265 QRectF rr = line.rect();
3266 rr.moveTop(rr.top() + r.top());
3267 rr.moveLeft(r.left() + x);
3269 painter.fillRect(rr, palette().text());
3271 QTextLayout::FormatRange o;
3272 o.start = relativePos;
3274 o.format.setForeground(palette().base());
3275 selections.append(o);
3281 layout->draw(&painter, offset, selections, er);
3283 if ((drawCursor && !drawCursorAsBlock)
3284 || (editable && context.cursorPosition < -1
3285 && !layout->preeditAreaText().isEmpty())) {
3286 int cpos = context.cursorPosition;
3288 cpos = layout->preeditAreaPosition() - (cpos + 2);
3291 cursor_layout = layout;
3292 cursor_offset = offset;
3294 cursor_pen = painter.pen();
3297 #ifndef Q_WS_MAC // no visible cursor on mac
3298 if (blockSelectionCursorRect.isValid())
3299 painter.fillRect(blockSelectionCursorRect, palette().text());
3304 offset.ry() += r.height();
3306 if (offset.y() > viewportRect.height())
3309 block = block.next();
3311 if (!block.isVisible()) {
3312 if (block.blockNumber() == d->visibleFoldedBlockNumber) {
3313 visibleCollapsedBlock = block;
3314 visibleCollapsedBlockOffset = offset + QPointF(0,1);
3317 // invisible blocks do have zero line count
3318 block = doc->findBlockByLineNumber(block.firstLineNumber());
3321 painter.setPen(context.palette.text().color());
3323 if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom()
3324 && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) {
3325 painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background());
3328 //end QPlainTextEdit::paintEvent()
3330 offset = contentOffset();
3331 block = firstVisibleBlock();
3333 qreal top = blockBoundingGeometry(block).translated(offset).top();
3334 qreal bottom = top + blockBoundingRect(block).height();
3336 QTextCursor cursor = textCursor();
3337 bool hasSelection = cursor.hasSelection();
3338 int selectionStart = cursor.selectionStart();
3339 int selectionEnd = cursor.selectionEnd();
3342 while (block.isValid() && top <= e->rect().bottom()) {
3343 QTextBlock nextBlock = block.next();
3344 QTextBlock nextVisibleBlock = nextBlock;
3346 if (!nextVisibleBlock.isVisible()) {
3347 // invisible blocks do have zero line count
3348 nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
3349 // paranoia in case our code somewhere did not set the line count
3350 // of the invisible block to 0
3351 while (nextVisibleBlock.isValid() && !nextVisibleBlock.isVisible())
3352 nextVisibleBlock = nextVisibleBlock.next();
3354 if (block.isVisible() && bottom >= e->rect().top()) {
3355 if (d->m_displaySettings.m_visualizeWhitespace) {
3356 QTextLayout *layout = block.layout();
3357 int lineCount = layout->lineCount();
3358 if (lineCount >= 2 || !nextBlock.isValid()) {
3360 painter.setPen(Qt::lightGray);
3361 for (int i = 0; i < lineCount-1; ++i) { // paint line wrap indicator
3362 QTextLine line = layout->lineAt(i);
3363 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
3364 QChar visualArrow((ushort)0x21b5);
3365 painter.drawText(QPointF(lineRect.right(),
3366 lineRect.top() + line.ascent()),
3369 if (!nextBlock.isValid()) { // paint EOF symbol
3370 QTextLine line = layout->lineAt(lineCount-1);
3371 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
3373 lineRect.adjust(0, 0, -1, -1);
3375 QPointF pos(lineRect.topRight() + QPointF(h+4, line.ascent()));
3377 path.lineTo(pos + QPointF(-h, -h));
3378 path.lineTo(pos + QPointF(0, -2*h));
3379 path.lineTo(pos + QPointF(h, -h));
3380 path.closeSubpath();
3381 painter.setBrush(painter.pen().color());
3382 painter.drawPath(path);
3388 if (nextBlock.isValid() && !nextBlock.isVisible()) {
3390 bool selectThis = (hasSelection
3391 && nextBlock.position() >= selectionStart
3392 && nextBlock.position() < selectionEnd);
3395 painter.setBrush(palette().highlight());
3398 QTextLayout *layout = block.layout();
3399 QTextLine line = layout->lineAt(layout->lineCount()-1);
3400 QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
3401 lineRect.adjust(0, 0, -1, -1);
3403 QRectF collapseRect(lineRect.right() + 12,
3405 fontMetrics().width(QLatin1String(" {...}; ")),
3407 painter.setRenderHint(QPainter::Antialiasing, true);
3408 painter.translate(.5, .5);
3409 painter.drawRoundedRect(collapseRect.adjusted(0, 0, 0, -1), 3, 3);
3410 painter.setRenderHint(QPainter::Antialiasing, false);
3411 painter.translate(-.5, -.5);
3413 QString replacement = QLatin1String("...");
3415 if (TextBlockUserData *nextBlockUserData = BaseTextDocumentLayout::testUserData(nextBlock)) {
3416 if (nextBlockUserData->foldingStartIncluded())
3417 replacement.prepend(nextBlock.text().trimmed().left(1));
3420 block = nextVisibleBlock.previous();
3421 if (!block.isValid())
3422 block = doc->lastBlock();
3424 if (TextBlockUserData *blockUserData = BaseTextDocumentLayout::testUserData(block)) {
3425 if (blockUserData->foldingEndIncluded()) {
3426 QString right = block.text().trimmed();
3427 if (right.endsWith(QLatin1Char(';'))) {
3429 right = right.trimmed();
3430 replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
3431 replacement.append(QLatin1Char(';'));
3433 replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
3439 painter.setPen(palette().highlightedText().color());
3440 painter.drawText(collapseRect, Qt::AlignCenter, replacement);
3446 block = nextVisibleBlock;
3448 bottom = top + blockBoundingRect(block).height();
3451 if (d->m_animator && d->m_animator->isRunning()) {
3452 QTextCursor cursor = textCursor();
3453 cursor.setPosition(d->m_animator->position());
3454 d->m_animator->draw(&painter, cursorRect(cursor).topLeft());
3457 // draw the overlays, but only if we do not have a find scope, otherwise the
3458 // view becomes too noisy.
3459 if (d->m_findScopeStart.isNull()) {
3460 if (d->m_overlay->isVisible())
3461 d->m_overlay->paint(&painter, e->rect());
3463 if (d->m_snippetOverlay->isVisible())
3464 d->m_snippetOverlay->paint(&painter, e->rect());
3466 if (!d->m_refactorOverlay->isEmpty())
3467 d->m_refactorOverlay->paint(&painter, e->rect());
3470 if (!d->m_searchResultOverlay->isEmpty()) {
3471 d->m_searchResultOverlay->paint(&painter, e->rect());
3472 d->m_searchResultOverlay->clear();
3476 // draw the cursor last, on top of everything
3477 if (cursor_layout && !d->m_inBlockSelectionMode) {
3478 painter.setPen(cursor_pen);
3479 cursor_layout->drawCursor(&painter, cursor_offset, cursor_cpos, cursorWidth());
3482 if (visibleCollapsedBlock.isValid()) {
3483 drawCollapsedBlockPopup(painter,
3484 visibleCollapsedBlock,
3485 visibleCollapsedBlockOffset,
3490 void BaseTextEditorWidget::drawCollapsedBlockPopup(QPainter &painter,
3491 const QTextBlock &block,
3495 int margin = block.document()->documentMargin();
3497 qreal blockHeight = 0;
3498 QTextBlock b = block;
3500 while (!b.isVisible()) {
3501 b.setVisible(true); // make sure block bounding rect works
3502 QRectF r = blockBoundingRect(b).translated(offset);
3504 QTextLayout *layout = b.layout();
3505 for (int i = layout->lineCount()-1; i >= 0; --i)
3506 maxWidth = qMax(maxWidth, layout->lineAt(i).naturalTextWidth() + 2*margin);
3508 blockHeight += r.height();
3510 b.setVisible(false); // restore previous state
3511 b.setLineCount(0); // restore 0 line count for invisible block
3516 painter.setRenderHint(QPainter::Antialiasing, true);
3517 painter.translate(.5, .5);
3518 QBrush brush = palette().base();
3519 if (d->m_ifdefedOutFormat.hasProperty(QTextFormat::BackgroundBrush))
3520 brush = d->m_ifdefedOutFormat.background();
3521 painter.setBrush(brush);
3522 painter.drawRoundedRect(QRectF(offset.x(),
3524 maxWidth, blockHeight).adjusted(0, 0, 0, 0), 3, 3);
3530 b.setVisible(true); // make sure block bounding rect works
3531 QRectF r = blockBoundingRect(b).translated(offset);
3532 QTextLayout *layout = b.layout();
3533 QVector<QTextLayout::FormatRange> selections;
3534 layout->draw(&painter, offset, selections, clip);
3536 b.setVisible(false); // restore previous state
3537 b.setLineCount(0); // restore 0 line count for invisible block
3538 offset.ry() += r.height();
3543 QWidget *BaseTextEditorWidget::extraArea() const
3545 return d->m_extraArea;
3548 int BaseTextEditorWidget::extraAreaWidth(int *markWidthPtr) const
3550 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());
3551 if (!documentLayout)
3554 if (!d->m_marksVisible && documentLayout->hasMarks)
3555 d->m_marksVisible = true;
3558 const QFontMetrics fm(d->m_extraArea->fontMetrics());
3560 if (d->m_lineNumbersVisible) {
3561 QFont fnt = d->m_extraArea->font();
3562 // this works under the assumption that bold or italic can only make a font wider
3563 fnt.setBold(d->m_currentLineNumberFormat.font().bold());
3564 fnt.setItalic(d->m_currentLineNumberFormat.font().italic());
3565 const QFontMetrics linefm(fnt);
3568 int max = qMax(1, blockCount());
3569 while (max >= 100) {
3573 space += linefm.width(QLatin1Char('9')) * digits;
3577 if (d->m_marksVisible) {
3578 markWidth += fm.lineSpacing();
3579 // if (documentLayout->doubleMarkCount)
3580 // markWidth += fm.lineSpacing() / 3;
3587 *markWidthPtr = markWidth;
3591 if (d->m_codeFoldingVisible)
3592 space += foldBoxWidth(fm);
3596 void BaseTextEditorWidget::slotUpdateExtraAreaWidth()
3598 if (isLeftToRight())
3599 setViewportMargins(extraAreaWidth(), 0, 0, 0);
3601 setViewportMargins(0, 0, extraAreaWidth(), 0);
3604 static void drawRectBox(QPainter *painter, const QRect &rect, bool start, bool end,
3605 const QPalette &pal)
3608 painter->setRenderHint(QPainter::Antialiasing, false);
3610 QRgb b = pal.base().color().rgb();
3611 QRgb h = pal.highlight().color().rgb();
3612 QColor c = Utils::StyleHelper::mergedColors(b,h, 50);
3614 QLinearGradient grad(rect.topLeft(), rect.topRight());
3615 grad.setColorAt(0, c.lighter(110));
3616 grad.setColorAt(1, c.lighter(130));
3619 painter->fillRect(rect, grad);
3620 painter->setPen(outline);
3622 painter->drawLine(rect.topLeft() + QPoint(1, 0), rect.topRight() - QPoint(1, 0));
3624 painter->drawLine(rect.bottomLeft() + QPoint(1, 0), rect.bottomRight() - QPoint(1, 0));
3626 painter->drawLine(rect.topRight() + QPoint(0, start ? 1 : 0), rect.bottomRight() - QPoint(0, end ? 1 : 0));
3627 painter->drawLine(rect.topLeft() + QPoint(0, start ? 1 : 0), rect.bottomLeft() - QPoint(0, end ? 1 : 0));
3632 void BaseTextEditorWidget::extraAreaPaintEvent(QPaintEvent *e)
3634 QTextDocument *doc = document();
3635 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
3636 QTC_ASSERT(documentLayout, return);
3638 int selStart = textCursor().selectionStart();
3639 int selEnd = textCursor().selectionEnd();
3641 QPalette pal = d->m_extraArea->palette();
3642 pal.setCurrentColorGroup(QPalette::Active);
3643 QPainter painter(d->m_extraArea);
3644 const QFontMetrics fm(d->m_extraArea->font());
3645 int fmLineSpacing = fm.lineSpacing();
3648 if (d->m_marksVisible)
3649 markWidth += fm.lineSpacing();
3651 const int collapseColumnWidth = d->m_codeFoldingVisible ? foldBoxWidth(fm): 0;
3652 const int extraAreaWidth = d->m_extraArea->width() - collapseColumnWidth;
3654 painter.fillRect(e->rect(), pal.color(QPalette::Base));
3655 painter.fillRect(e->rect().intersected(QRect(0, 0, extraAreaWidth, INT_MAX)),
3656 pal.color(QPalette::Background));
3658 QTextBlock block = firstVisibleBlock();
3659 int blockNumber = block.blockNumber();
3660 qreal top = blockBoundingGeometry(block).translated(contentOffset()).top();
3663 while (block.isValid() && top <= e->rect().bottom()) {
3666 const qreal height = blockBoundingRect(block).height();
3667 bottom = top + height;
3668 QTextBlock nextBlock = block.next();
3670 QTextBlock nextVisibleBlock = nextBlock;
3671 int nextVisibleBlockNumber = blockNumber + 1;
3673 if (!nextVisibleBlock.isVisible()) {
3674 // invisible blocks do have zero line count
3675 nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
3676 nextVisibleBlockNumber = nextVisibleBlock.blockNumber();
3679 if (bottom < e->rect().top()) {
3680 block = nextVisibleBlock;
3681 blockNumber = nextVisibleBlockNumber;
3685 painter.setPen(pal.color(QPalette::Dark));
3687 if (d->m_codeFoldingVisible || d->m_marksVisible) {
3689 painter.setRenderHint(QPainter::Antialiasing, false);
3691 int previousBraceDepth = block.previous().userState();
3692 if (previousBraceDepth >= 0)
3693 previousBraceDepth >>= 8;
3695 previousBraceDepth = 0;
3697 int braceDepth = block.userState();
3698 if (!nextBlock.isVisible()) {
3699 QTextBlock lastInvisibleBlock = nextVisibleBlock.previous();
3700 if (!lastInvisibleBlock.isValid())
3701 lastInvisibleBlock = doc->lastBlock();
3702 braceDepth = lastInvisibleBlock.userState();
3704 if (braceDepth >= 0)
3709 if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) {
3710 if (d->m_marksVisible) {
3712 foreach (ITextMark *mrk, userData->marks()) {
3714 int radius = fmLineSpacing - 1;
3715 QRect r(x + xoffset, top, radius, radius);
3716 mrk->icon().paint(&painter, r, Qt::AlignCenter);
3722 if (d->m_codeFoldingVisible) {
3724 int extraAreaHighlightFoldBlockNumber = -1;
3725 int extraAreaHighlightFoldEndBlockNumber = -1;
3726 bool endIsVisible = false;
3727 if (!d->m_highlightBlocksInfo.isEmpty()) {
3728 extraAreaHighlightFoldBlockNumber = d->m_highlightBlocksInfo.open.last();
3729 extraAreaHighlightFoldEndBlockNumber = d->m_highlightBlocksInfo.close.first();
3730 endIsVisible = doc->findBlockByNumber(extraAreaHighlightFoldEndBlockNumber).isVisible();
3732 // QTextBlock before = doc->findBlockByNumber(extraAreaHighlightCollapseBlockNumber-1);
3733 // if (TextBlockUserData::hasCollapseAfter(before)) {
3734 // extraAreaHighlightCollapseBlockNumber--;
3738 TextBlockUserData *nextBlockUserData = BaseTextDocumentLayout::testUserData(nextBlock);
3740 bool drawBox = nextBlockUserData
3741 && BaseTextDocumentLayout::foldingIndent(block) < nextBlockUserData->foldingIndent();
3745 bool active = blockNumber == extraAreaHighlightFoldBlockNumber;
3747 bool drawStart = active;
3748 bool drawEnd = blockNumber == extraAreaHighlightFoldEndBlockNumber || (drawStart && !endIsVisible);
3749 bool hovered = blockNumber >= extraAreaHighlightFoldBlockNumber
3750 && blockNumber <= extraAreaHighlightFoldEndBlockNumber;
3752 int boxWidth = foldBoxWidth(fm);
3754 int itop = qRound(top);
3755 int ibottom = qRound(bottom);
3756 QRect box = QRect(extraAreaWidth + 1, itop, boxWidth - 2, ibottom - itop);
3757 drawRectBox(&painter, box, drawStart, drawEnd, pal);
3761 bool expanded = nextBlock.isVisible();
3762 int size = boxWidth/4;
3763 QRect box(extraAreaWidth + size, top + size,
3764 2 * (size) + 1, 2 * (size) + 1);
3765 drawFoldingMarker(&painter, pal, box, expanded, active, hovered);
3773 if (d->m_revisionsVisible && block.revision() != documentLayout->lastSaveRevision) {
3775 painter.setRenderHint(QPainter::Antialiasing, false);
3776 if (block.revision() < 0)
3777 painter.setPen(QPen(Qt::darkGreen, 2));
3779 painter.setPen(QPen(Qt::red, 2));
3780 painter.drawLine(extraAreaWidth - 1, top, extraAreaWidth - 1, bottom - 1);
3784 if (d->m_lineNumbersVisible) {
3785 const QString &number = QString::number(blockNumber + 1);
3787 (selStart < block.position() + block.length()
3788 && selEnd > block.position())
3789 || (selStart == selEnd && selStart == block.position())
3793 QFont f = painter.font();
3794 f.setBold(d->m_currentLineNumberFormat.font().bold());
3795 f.setItalic(d->m_currentLineNumberFormat.font().italic());
3797 painter.setPen(d->m_currentLineNumberFormat.foreground().color());
3799 painter.drawText(QRectF(markWidth, top, extraAreaWidth - markWidth - 4, height), Qt::AlignRight, number);
3804 block = nextVisibleBlock;
3805 blockNumber = nextVisibleBlockNumber;
3809 void BaseTextEditorWidget::drawFoldingMarker(QPainter *painter, const QPalette &pal,
3817 QStyle *s = style();
3818 if (ManhattanStyle *ms = qobject_cast<ManhattanStyle*>(s))
3819 s = ms->baseStyle();
3821 if (!qstrcmp(s->metaObject()->className(), "OxygenStyle")) {
3823 painter->setPen(Qt::NoPen);
3824 int size = rect.size().width();
3825 int sqsize = 2*(size/2);
3827 QColor textColor = pal.buttonText().color();
3828 QColor brushColor = textColor;
3830 textColor.setAlpha(100);
3831 brushColor.setAlpha(100);
3836 a.setPoints(3, 0, sqsize/3, sqsize/2, sqsize - sqsize/3, sqsize, sqsize/3);
3839 a.setPoints(3, sqsize - sqsize/3, sqsize/2, sqsize/2 - sqsize/3, 0, sqsize/2 - sqsize/3, sqsize);
3840 painter->setBrush(brushColor);
3842 painter->translate(0.5, 0.5);
3843 painter->setRenderHint(QPainter::Antialiasing);
3844 painter->translate(rect.topLeft());
3845 painter->setPen(textColor);
3846 painter->setBrush(textColor);
3847 painter->drawPolygon(a);
3850 QStyleOptionViewItemV2 opt;
3852 opt.state = QStyle::State_Active | QStyle::State_Item | QStyle::State_Children;
3854 opt.state |= QStyle::State_Open;
3856 opt.state |= QStyle::State_MouseOver | QStyle::State_Enabled | QStyle::State_Selected;
3858 opt.palette.setBrush(QPalette::Window, pal.highlight());
3860 // QGtkStyle needs a small correction to draw the marker in the right place
3861 if (!qstrcmp(s->metaObject()->className(), "QGtkStyle"))
3862 opt.rect.translate(-2, 0);
3863 else if (!qstrcmp(s->metaObject()->className(), "QMacStyle"))
3864 opt.rect.translate(-1, 0);
3866 s->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
3870 void BaseTextEditorWidget::slotModificationChanged(bool m)
3875 QTextDocument *doc = document();
3876 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
3877 QTC_ASSERT(documentLayout, return);
3878 int oldLastSaveRevision = documentLayout->lastSaveRevision;
3879 documentLayout->lastSaveRevision = doc->revision();
3881 if (oldLastSaveRevision != documentLayout->lastSaveRevision) {
3882 QTextBlock block = doc->begin();
3883 while (block.isValid()) {
3884 if (block.revision() < 0 || block.revision() != oldLastSaveRevision) {
3885 block.setRevision(-documentLayout->lastSaveRevision - 1);
3887 block.setRevision(documentLayout->lastSaveRevision);
3889 block = block.next();
3892 d->m_extraArea->update();
3895 void BaseTextEditorWidget::slotUpdateRequest(const QRect &r, int dy)
3898 d->m_extraArea->scroll(0, dy);
3899 else if (r.width() > 4) { // wider than cursor width, not just cursor blinking
3900 d->m_extraArea->update(0, r.y(), d->m_extraArea->width(), r.height());
3901 if (!d->m_searchExpr.isEmpty()) {
3902 const int m = d->m_searchResultOverlay->dropShadowWidth();
3903 viewport()->update(r.adjusted(-m, -m, m, m));
3907 if (r.contains(viewport()->rect()))
3908 slotUpdateExtraAreaWidth();
3911 void BaseTextEditorWidget::saveCurrentCursorPositionForNavigation()
3913 d->m_lastCursorChangeWasInteresting = true;
3914 d->m_tempNavigationState = saveState();
3917 void BaseTextEditorWidget::updateCurrentLineHighlight()
3919 QList<QTextEdit::ExtraSelection> extraSelections;
3921 if (d->m_highlightCurrentLine) {
3922 QTextEdit::ExtraSelection sel;
3923 sel.format.setBackground(d->m_currentLineFormat.background());
3924 sel.format.setProperty(QTextFormat::FullWidthSelection, true);
3925 sel.cursor = textCursor();
3926 sel.cursor.clearSelection();
3927 extraSelections.append(sel);
3930 setExtraSelections(CurrentLineSelection, extraSelections);
3933 // the extra area shows information for the entire current block, not just the currentline.
3934 // This is why we must force a bigger update region.
3935 int cursorBlockNumber = textCursor().blockNumber();
3936 if (cursorBlockNumber != d->m_cursorBlockNumber) {
3937 QPointF offset = contentOffset();
3938 QTextBlock block = document()->findBlockByNumber(d->m_cursorBlockNumber);
3939 if (block.isValid())
3940 d->m_extraArea->update(blockBoundingGeometry(block).translated(offset).toAlignedRect());
3941 block = document()->findBlockByNumber(cursorBlockNumber);
3942 if (block.isValid() && block.isVisible())
3943 d->m_extraArea->update(blockBoundingGeometry(block).translated(offset).toAlignedRect());
3944 d->m_cursorBlockNumber = cursorBlockNumber;
3949 void BaseTextEditorWidget::slotCursorPositionChanged()
3952 qDebug() << "block" << textCursor().blockNumber()+1
3953 << "brace depth:" << BaseTextDocumentLayout::braceDepth(textCursor().block())
3954 << "indent:" << BaseTextDocumentLayout::userData(textCursor().block())->foldingIndent();
3956 if (!d->m_contentsChanged && d->m_lastCursorChangeWasInteresting) {
3957 Core::EditorManager::instance()->addCurrentPositionToNavigationHistory(editor(), d->m_tempNavigationState);
3958 d->m_lastCursorChangeWasInteresting = false;
3959 } else if (d->m_contentsChanged) {
3960 saveCurrentCursorPositionForNavigation();
3965 void BaseTextEditorWidget::updateHighlights()
3967 if (d->m_parenthesesMatchingEnabled && hasFocus()) {
3968 // Delay update when no matching is displayed yet, to avoid flicker
3969 if (extraSelections(ParenthesesMatchingSelection).isEmpty()
3970 && d->m_animator == 0) {
3971 d->m_parenthesesMatchingTimer->start(50);
3973 // use 0-timer, not direct call, to give the syntax highlighter a chance
3974 // to update the parantheses information
3975 d->m_parenthesesMatchingTimer->start(0);
3979 updateCurrentLineHighlight();
3981 if (d->m_displaySettings.m_highlightBlocks) {
3982 QTextCursor cursor = textCursor();
3983 d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
3984 d->m_highlightBlocksTimer->start(100);
3988 void BaseTextEditorWidget::slotUpdateBlockNotify(const QTextBlock &block)
3990 static bool blockRecursion = false;
3993 blockRecursion = true;
3994 if (d->m_overlay->isVisible()) {
3995 /* an overlay might draw outside the block bounderies, force
3996 complete viewport update */
3997 viewport()->update();
3999 if (block.previous().isValid() && block.userState() != block.previous().userState()) {
4000 /* The syntax highlighting state changes. This opens up for
4001 the possibility that the paragraph has braces that support
4002 code folding. In this case, do the save thing and also
4003 update the previous block, which might contain a fold
4004 box which now is invalid.*/
4005 emit requestBlockUpdate(block.previous());
4007 if (!d->m_findScopeStart.isNull()) {
4008 if (block.position() < d->m_findScopeEnd.position()
4009 && block.position()+block.length() >= d->m_findScopeStart.position()) {
4010 QTextBlock b = block.document()->findBlock(d->m_findScopeStart.position());
4012 emit requestBlockUpdate(b);
4014 } while (b.isValid() && b.position() < d->m_findScopeEnd.position());
4018 blockRecursion = false;
4021 void BaseTextEditorWidget::timerEvent(QTimerEvent *e)
4023 if (e->timerId() == d->autoScrollTimer.timerId()) {
4024 const QPoint globalPos = QCursor::pos();
4025 const QPoint pos = d->m_extraArea->mapFromGlobal(globalPos);
4026 QRect visible = d->m_extraArea->rect();
4027 verticalScrollBar()->triggerAction( pos.y() < visible.center().y() ?
4028 QAbstractSlider::SliderSingleStepSub
4029 : QAbstractSlider::SliderSingleStepAdd);
4030 QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
4031 extraAreaMouseEvent(&ev);
4032 int delta = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height();
4035 int timeout = 4900 / (delta * delta);
4036 d->autoScrollTimer.start(timeout, this);
4038 } else if (e->timerId() == d->foldedBlockTimer.timerId()) {
4039 d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber;
4040 d->suggestedVisibleFoldedBlockNumber = -1;
4041 d->foldedBlockTimer.stop();
4042 viewport()->update();
4044 QPlainTextEdit::timerEvent(e);
4048 void BaseTextEditorPrivate::clearVisibleFoldedBlock()
4050 if (suggestedVisibleFoldedBlockNumber) {
4051 suggestedVisibleFoldedBlockNumber = -1;
4052 foldedBlockTimer.stop();
4054 if (visibleFoldedBlockNumber >= 0) {
4055 visibleFoldedBlockNumber = -1;
4056 q->viewport()->update();
4060 void BaseTextEditorWidget::mouseMoveEvent(QMouseEvent *e)
4064 if (e->buttons() == Qt::NoButton) {
4065 const QTextBlock collapsedBlock = foldedBlockAt(e->pos());
4066 const int blockNumber = collapsedBlock.next().blockNumber();
4067 if (blockNumber < 0) {
4068 d->clearVisibleFoldedBlock();
4069 } else if (blockNumber != d->visibleFoldedBlockNumber) {
4070 d->suggestedVisibleFoldedBlockNumber = blockNumber;
4071 d->foldedBlockTimer.start(40, this);
4074 const RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(e->pos());
4076 // Update the mouse cursor
4077 if ((collapsedBlock.isValid() || refactorMarker.isValid()) && !d->m_mouseOnFoldedMarker) {
4078 d->m_mouseOnFoldedMarker = true;
4079 viewport()->setCursor(Qt::PointingHandCursor);
4080 } else if (!collapsedBlock.isValid() && !refactorMarker.isValid() && d->m_mouseOnFoldedMarker) {
4081 d->m_mouseOnFoldedMarker = false;
4082 viewport()->setCursor(Qt::IBeamCursor);
4085 QPlainTextEdit::mouseMoveEvent(e);
4087 if (e->modifiers() & Qt::AltModifier) {
4088 if (!d->m_inBlockSelectionMode) {
4089 d->m_blockSelection.fromSelection(tabSettings(), textCursor());
4090 d->m_inBlockSelectionMode = true;
4092 QTextCursor cursor = textCursor();
4094 // get visual column
4095 int column = tabSettings().columnAt(cursor.block().text(), cursor.positionInBlock());
4096 if (cursor.positionInBlock() == cursor.block().length()-1) {
4097 column += (e->pos().x() - cursorRect().center().x())/QFontMetricsF(font()).width(QLatin1Char(' '));
4099 d->m_blockSelection.moveAnchor(cursor.blockNumber(), column);
4100 setTextCursor(d->m_blockSelection.selection(tabSettings()));
4101 viewport()->update();
4105 if (viewport()->cursor().shape() == Qt::BlankCursor)
4106 viewport()->setCursor(Qt::IBeamCursor);
4109 static bool handleForwardBackwardMouseButtons(QMouseEvent *e)
4111 if (e->button() == Qt::XButton1) {
4112 Core::EditorManager::instance()->goBackInNavigationHistory();
4115 if (e->button() == Qt::XButton2) {
4116 Core::EditorManager::instance()->goForwardInNavigationHistory();
4123 void BaseTextEditorWidget::mousePressEvent(QMouseEvent *e)
4125 if (e->button() == Qt::LeftButton) {
4126 d->clearBlockSelection(); // just in case, otherwise we might get strange drag and drop
4128 QTextBlock foldedBlock = foldedBlockAt(e->pos());
4129 if (foldedBlock.isValid()) {
4130 toggleBlockVisible(foldedBlock);
4131 viewport()->setCursor(Qt::IBeamCursor);
4134 RefactorMarker refactorMarker = d->m_refactorOverlay->markerAt(e->pos());
4135 if (refactorMarker.isValid()) {
4136 qDebug() << "refactorMarkerClicked" << refactorMarker.cursor.position();
4137 emit refactorMarkerClicked(refactorMarker);
4141 if (d->m_currentLink.isValid())
4142 d->m_linkPressed = true;
4147 if (handleForwardBackwardMouseButtons(e))
4151 QPlainTextEdit::mousePressEvent(e);
4154 void BaseTextEditorWidget::mouseReleaseEvent(QMouseEvent *e)
4156 if (mouseNavigationEnabled()
4158 && e->modifiers() & Qt::ControlModifier
4159 && !(e->modifiers() & Qt::ShiftModifier)
4160 && e->button() == Qt::LeftButton
4162 const QTextCursor cursor = cursorForPosition(e->pos());
4163 if (openLink(findLinkAt(cursor))) {
4170 if (handleForwardBackwardMouseButtons(e))
4174 QPlainTextEdit::mouseReleaseEvent(e);
4177 void BaseTextEditorWidget::leaveEvent(QEvent *e)
4179 // Clear link emulation when the mouse leaves the editor
4181 QPlainTextEdit::leaveEvent(e);
4184 void BaseTextEditorWidget::keyReleaseEvent(QKeyEvent *e)
4186 // Clear link emulation when Ctrl is released
4187 if (e->key() == Qt::Key_Control)
4190 QPlainTextEdit::keyReleaseEvent(e);
4193 void BaseTextEditorWidget::dragEnterEvent(QDragEnterEvent *e)
4195 // If the drag event contains URLs, we don't want to insert them as text
4196 if (e->mimeData()->hasUrls()) {
4201 QPlainTextEdit::dragEnterEvent(e);
4204 void BaseTextEditorWidget::extraAreaLeaveEvent(QEvent *)
4206 // fake missing mouse move event from Qt
4207 QMouseEvent me(QEvent::MouseMove, QPoint(-1, -1), Qt::NoButton, 0, 0);
4208 extraAreaMouseEvent(&me);
4211 void BaseTextEditorWidget::extraAreaMouseEvent(QMouseEvent *e)
4213 QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
4216 extraAreaWidth(&markWidth);
4218 if (d->m_codeFoldingVisible
4219 && e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking
4220 // Update which folder marker is highlighted
4221 const int highlightBlockNumber = d->extraAreaHighlightFoldedBlockNumber;
4222 d->extraAreaHighlightFoldedBlockNumber = -1;
4224 if (e->pos().x() > extraArea()->width() - foldBoxWidth(fontMetrics())) {
4225 d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
4226 } else if (d->m_displaySettings.m_highlightBlocks) {
4227 QTextCursor cursor = textCursor();
4228 d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
4231 if (highlightBlockNumber != d->extraAreaHighlightFoldedBlockNumber)
4232 d->m_highlightBlocksTimer->start(d->m_highlightBlocksInfo.isEmpty() ? 120 : 0);
4235 // Set whether the mouse cursor is a hand or normal arrow
4236 if (e->type() == QEvent::MouseMove) {
4237 bool hand = (e->pos().x() <= markWidth);
4238 if (hand != (d->m_extraArea->cursor().shape() == Qt::PointingHandCursor))
4239 d->m_extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor);
4242 if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
4243 if (e->button() == Qt::LeftButton) {
4244 int boxWidth = foldBoxWidth(fontMetrics());
4245 if (d->m_codeFoldingVisible && e->pos().x() > extraArea()->width() - boxWidth) {
4246 if (!cursor.block().next().isVisible()) {
4247 toggleBlockVisible(cursor.block());
4248 d->moveCursorVisible(false);
4249 } else if (foldBox().contains(e->pos())) {
4251 document()->findBlockByNumber(d->m_highlightBlocksInfo.open.last()).position()
4253 QTextBlock c = cursor.block();
4254 toggleBlockVisible(c);
4255 d->moveCursorVisible(false);
4257 } else if (d->m_lineNumbersVisible && e->pos().x() > markWidth) {
4258 QTextCursor selection = cursor;
4259 selection.setVisualNavigation(true);
4260 d->extraAreaSelectionAnchorBlockNumber = selection.blockNumber();
4261 selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
4262 selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
4263 setTextCursor(selection);
4265 d->extraAreaToggleMarkBlockNumber = cursor.blockNumber();
4267 } else if (d->m_marksVisible && e->button() == Qt::RightButton) {
4268 QMenu * contextMenu = new QMenu(this);
4269 emit editor()->markContextMenuRequested(editor(), cursor.blockNumber() + 1, contextMenu);
4270 if (!contextMenu->isEmpty())
4271 contextMenu->exec(e->globalPos());
4274 } else if (d->extraAreaSelectionAnchorBlockNumber >= 0) {
4275 QTextCursor selection = cursor;
4276 selection.setVisualNavigation(true);
4277 if (e->type() == QEvent::MouseMove) {
4278 QTextBlock anchorBlock = document()->findBlockByNumber(d->extraAreaSelectionAnchorBlockNumber);
4279 selection.setPosition(anchorBlock.position());
4280 if (cursor.blockNumber() < d->extraAreaSelectionAnchorBlockNumber) {
4281 selection.movePosition(QTextCursor::EndOfBlock);
4282 selection.movePosition(QTextCursor::Right);
4284 selection.setPosition(cursor.block().position(), QTextCursor::KeepAnchor);
4285 if (cursor.blockNumber() >= d->extraAreaSelectionAnchorBlockNumber) {
4286 selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
4287 selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
4290 if (e->pos().y() >= 0 && e->pos().y() <= d->m_extraArea->height())
4291 d->autoScrollTimer.stop();
4292 else if (!d->autoScrollTimer.isActive())
4293 d->autoScrollTimer.start(100, this);
4296 d->autoScrollTimer.stop();
4297 d->extraAreaSelectionAnchorBlockNumber = -1;
4300 setTextCursor(selection);
4301 } else if (d->extraAreaToggleMarkBlockNumber >= 0 && d->m_marksVisible && d->m_requestMarkEnabled) {
4302 if (e->type() == QEvent::MouseButtonRelease && e->button() == Qt::LeftButton) {
4303 int n = d->extraAreaToggleMarkBlockNumber;
4304 d->extraAreaToggleMarkBlockNumber = -1;
4305 if (cursor.blockNumber() == n) {
4307 emit editor()->markRequested(editor(), line);
4313 void BaseTextEditorWidget::ensureCursorVisible()
4315 QTextBlock block = textCursor().block();
4316 if (!block.isVisible()) {
4317 while (!block.isVisible() && block.previous().isValid())
4318 block = block.previous();
4319 toggleBlockVisible(block);
4321 QPlainTextEdit::ensureCursorVisible();
4324 void BaseTextEditorWidget::toggleBlockVisible(const QTextBlock &block)
4326 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(document()->documentLayout());
4327 QTC_ASSERT(documentLayout, return);
4329 bool visible = block.next().isVisible();
4330 BaseTextDocumentLayout::doFoldOrUnfold(block, !visible);
4331 documentLayout->requestUpdate();
4332 documentLayout->emitDocumentSizeChanged();
4336 const TabSettings &BaseTextEditorWidget::tabSettings() const
4338 return d->m_document->tabSettings();
4341 const DisplaySettings &BaseTextEditorWidget::displaySettings() const
4343 return d->m_displaySettings;
4347 void BaseTextEditorWidget::indentOrUnindent(bool doIndent)
4349 const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings();
4351 QTextCursor cursor = textCursor();
4352 maybeClearSomeExtraSelections(cursor);
4353 cursor.beginEditBlock();
4355 if (cursor.hasSelection()) {
4356 // Indent or unindent the selected lines
4357 int pos = cursor.position();
4358 int anchor = cursor.anchor();
4359 int start = qMin(anchor, pos);
4360 int end = qMax(anchor, pos);
4362 QTextDocument *doc = document();
4363 QTextBlock startBlock = doc->findBlock(start);
4364 QTextBlock endBlock = doc->findBlock(end-1).next();
4366 for (QTextBlock block = startBlock; block != endBlock; block = block.next()) {
4367 QString text = block.text();
4368 int indentPosition = tabSettings.lineIndentPosition(text);
4369 if (!doIndent && !indentPosition)
4370 indentPosition = tabSettings.firstNonSpace(text);
4371 int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent);
4372 cursor.setPosition(block.position() + indentPosition);
4373 cursor.insertText(tabSettings.indentationString(0, targetColumn, block));
4374 cursor.setPosition(block.position());
4375 cursor.setPosition(block.position() + indentPosition, QTextCursor::KeepAnchor);
4376 cursor.removeSelectedText();
4378 cursor.endEditBlock();
4380 // Indent or unindent at cursor position
4381 QTextBlock block = cursor.block();
4382 QString text = block.text();
4383 int indentPosition = cursor.positionInBlock();
4384 int spaces = tabSettings.spacesLeftFromPosition(text, indentPosition);
4385 int startColumn = tabSettings.columnAt(text, indentPosition - spaces);
4386 int targetColumn = tabSettings.indentedColumn(tabSettings.columnAt(text, indentPosition), doIndent);
4387 cursor.setPosition(block.position() + indentPosition);
4388 cursor.setPosition(block.position() + indentPosition - spaces, QTextCursor::KeepAnchor);
4389 cursor.removeSelectedText();
4390 cursor.insertText(tabSettings.indentationString(startColumn, targetColumn, block));
4391 cursor.endEditBlock();
4392 setTextCursor(cursor);
4396 void BaseTextEditorWidget::handleHomeKey(bool anchor)
4398 QTextCursor cursor = textCursor();
4399 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
4402 mode = QTextCursor::KeepAnchor;
4404 const int initpos = cursor.position();
4405 int pos = cursor.block().position();
4406 QChar character = characterAt(pos);
4407 const QLatin1Char tab = QLatin1Char('\t');
4409 while (character == tab || character.category() == QChar::Separator_Space) {
4413 character = characterAt(pos);
4416 // Go to the start of the block when we're already at the start of the text
4418 pos = cursor.block().position();
4420 cursor.setPosition(pos, mode);
4421 setTextCursor(cursor);
4424 void BaseTextEditorWidget::handleBackspaceKey()
4426 QTextCursor cursor = textCursor();
4427 int pos = cursor.position();
4428 QTC_ASSERT(!cursor.hasSelection(), return);
4430 bool cursorWithinSnippet = false;
4431 if (d->m_snippetOverlay->isVisible()) {
4432 QTextCursor snippetCursor = cursor;
4433 snippetCursor.movePosition(QTextCursor::Left);
4434 cursorWithinSnippet = d->snippetCheckCursor(snippetCursor);
4437 const TextEditor::TabSettings &tabSettings = d->m_document->tabSettings();
4439 if (tabSettings.m_autoIndent && d->m_autoCompleter->autoBackspace(cursor))
4442 bool handled = false;
4443 if (!tabSettings.m_smartBackspace) {
4444 if (cursorWithinSnippet)
4445 cursor.beginEditBlock();
4446 cursor.deletePreviousChar();
4449 QTextBlock currentBlock = cursor.block();
4450 int positionInBlock = pos - currentBlock.position();
4451 const QString blockText = currentBlock.text();
4452 if (cursor.atBlockStart() || tabSettings.firstNonSpace(blockText) < positionInBlock) {
4453 if (cursorWithinSnippet)
4454 cursor.beginEditBlock();
4455 cursor.deletePreviousChar();
4458 int previousIndent = 0;
4459 const int indent = tabSettings.columnAt(blockText, positionInBlock);
4461 for (QTextBlock previousNonEmptyBlock = currentBlock.previous();
4462 previousNonEmptyBlock.isValid();
4463 previousNonEmptyBlock = previousNonEmptyBlock.previous()) {
4464 QString previousNonEmptyBlockText = previousNonEmptyBlock.text();
4465 if (previousNonEmptyBlockText.trimmed().isEmpty())
4468 tabSettings.columnAt(previousNonEmptyBlockText,
4469 tabSettings.firstNonSpace(previousNonEmptyBlockText));
4470 if (previousIndent < indent) {
4471 cursor.beginEditBlock();
4472 cursor.setPosition(currentBlock.position(), QTextCursor::KeepAnchor);
4473 cursor.insertText(tabSettings.indentationString(previousNonEmptyBlockText));
4474 cursor.endEditBlock();
4483 if (cursorWithinSnippet)
4484 cursor.beginEditBlock();
4485 cursor.deletePreviousChar();
4488 if (cursorWithinSnippet) {
4489 cursor.endEditBlock();
4490 d->m_snippetOverlay->updateEquivalentSelections(cursor);
4493 setTextCursor(cursor);
4496 void BaseTextEditorWidget::wheelEvent(QWheelEvent *e)
4498 d->clearVisibleFoldedBlock();
4499 if (scrollWheelZoomingEnabled() && e->modifiers() & Qt::ControlModifier) {
4500 const int delta = e->delta();
4507 QPlainTextEdit::wheelEvent(e);
4510 void BaseTextEditorWidget::zoomIn(int range)
4512 d->clearVisibleFoldedBlock();
4513 emit requestFontZoom(range*10);
4516 void BaseTextEditorWidget::zoomOut(int range)
4521 void BaseTextEditorWidget::zoomReset()
4523 emit requestZoomReset();
4526 void BaseTextEditorWidget::indentInsertedText(const QTextCursor &tc)
4528 indent(tc.document(), tc, QChar::Null);
4531 void BaseTextEditorWidget::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar)
4533 maybeClearSomeExtraSelections(cursor);
4534 d->m_indenter->indent(doc, cursor, typedChar, this);
4537 void BaseTextEditorWidget::reindent(QTextDocument *doc, const QTextCursor &cursor)
4539 maybeClearSomeExtraSelections(cursor);
4540 d->m_indenter->reindent(doc, cursor, this);
4543 BaseTextEditorWidget::Link BaseTextEditorWidget::findLinkAt(const QTextCursor &, bool)
4548 bool BaseTextEditorWidget::openLink(const Link &link)
4550 if (link.fileName.isEmpty())
4553 if (baseTextDocument()->fileName() == link.fileName) {
4554 Core::EditorManager *editorManager = Core::EditorManager::instance();
4555 editorManager->addCurrentPositionToNavigationHistory();
4556 gotoLine(link.line, link.column);
4561 return openEditorAt(link.fileName, link.line, link.column, QString(),
4562 Core::EditorManager::IgnoreNavigationHistory
4563 | Core::EditorManager::ModeSwitch);
4566 void BaseTextEditorWidget::updateLink(QMouseEvent *e)
4568 bool linkFound = false;
4570 if (mouseNavigationEnabled() && e->modifiers() & Qt::ControlModifier) {
4571 // Link emulation behaviour for 'go to definition'
4572 const QTextCursor cursor = cursorForPosition(e->pos());
4574 // Check that the mouse was actually on the text somewhere
4575 bool onText = cursorRect(cursor).right() >= e->x();
4577 QTextCursor nextPos = cursor;
4578 nextPos.movePosition(QTextCursor::Right);
4579 onText = cursorRect(nextPos).right() >= e->x();
4582 const Link link = findLinkAt(cursor, false);
4584 if (onText && link.isValid()) {
4594 void BaseTextEditorWidget::showLink(const Link &link)
4596 if (d->m_currentLink == link)
4599 QTextEdit::ExtraSelection sel;
4600 sel.cursor = textCursor();
4601 sel.cursor.setPosition(link.begin);
4602 sel.cursor.setPosition(link.end, QTextCursor::KeepAnchor);
4603 sel.format = d->m_linkFormat;
4604 sel.format.setFontUnderline(true);
4605 setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>() << sel);
4606 viewport()->setCursor(Qt::PointingHandCursor);
4607 d->m_currentLink = link;
4608 d->m_linkPressed = false;
4611 void BaseTextEditorWidget::clearLink()
4613 if (!d->m_currentLink.isValid())
4616 setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
4617 viewport()->setCursor(Qt::IBeamCursor);
4618 d->m_currentLink = Link();
4619 d->m_linkPressed = false;
4622 void BaseTextEditorPrivate::updateMarksBlock(const QTextBlock &block)
4624 if (const TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block))
4625 foreach (ITextMark *mrk, userData->marks())
4626 mrk->updateBlock(block);
4629 void BaseTextEditorPrivate::updateMarksLineNumber()
4631 QTextDocument *doc = q->document();
4632 QTextBlock block = doc->begin();
4633 int blockNumber = 0;
4634 while (block.isValid()) {
4635 if (const TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block))
4636 foreach (ITextMark *mrk, userData->marks()) {
4637 mrk->updateLineNumber(blockNumber + 1);
4639 block = block.next();
4644 void BaseTextEditorWidget::markBlocksAsChanged(QList<int> blockNumbers)
4646 QTextBlock block = document()->begin();
4647 while (block.isValid()) {
4648 if (block.revision() < 0)
4649 block.setRevision(-block.revision() - 1);
4650 block = block.next();
4652 foreach (const int blockNumber, blockNumbers) {
4653 QTextBlock block = document()->findBlockByNumber(blockNumber);
4654 if (block.isValid())
4655 block.setRevision(-block.revision() - 1);
4660 void BaseTextEditorWidget::highlightSearchResults(const QString &txt, Find::FindFlags findFlags)
4662 QString pattern = txt;
4663 if (pattern.size() < 2)
4664 pattern.clear(); // highlighting single characters is a bit pointless
4666 if (d->m_searchExpr.pattern() == pattern)
4668 d->m_searchExpr.setPattern(pattern);
4669 d->m_searchExpr.setPatternSyntax((findFlags & Find::FindRegularExpression) ?
4670 QRegExp::RegExp : QRegExp::FixedString);
4671 d->m_searchExpr.setCaseSensitivity((findFlags & Find::FindCaseSensitively) ?
4672 Qt::CaseSensitive : Qt::CaseInsensitive);
4673 d->m_findFlags = findFlags;
4675 d->m_delayedUpdateTimer->start(10);
4678 int BaseTextEditorWidget::verticalBlockSelectionFirstColumn() const
4680 if (d->m_inBlockSelectionMode)
4681 return d->m_blockSelection.firstVisualColumn;
4685 int BaseTextEditorWidget::verticalBlockSelectionLastColumn() const
4687 if (d->m_inBlockSelectionMode)
4688 return d->m_blockSelection.lastVisualColumn;
4692 QRegion BaseTextEditorWidget::translatedLineRegion(int lineStart, int lineEnd) const
4695 for (int i = lineStart ; i <= lineEnd; i++) {
4696 QTextBlock block = document()->findBlockByNumber(i);
4697 QPoint topLeft = blockBoundingGeometry(block).translated(contentOffset()).topLeft().toPoint();
4699 if (block.isValid()) {
4700 QTextLayout *layout = block.layout();
4702 for (int i = 0; i < layout->lineCount();i++) {
4703 QTextLine line = layout->lineAt(i);
4704 region += line.naturalTextRect().translated(topLeft).toRect();
4711 void BaseTextEditorWidget::setFindScope(const QTextCursor &start, const QTextCursor &end,
4712 int verticalBlockSelectionFirstColumn,
4713 int verticalBlockSelectionLastColumn)
4715 if (start != d->m_findScopeStart
4716 || end != d->m_findScopeEnd
4717 || verticalBlockSelectionFirstColumn != d->m_findScopeVerticalBlockSelectionFirstColumn
4718 || verticalBlockSelectionLastColumn != d->m_findScopeVerticalBlockSelectionLastColumn) {
4719 d->m_findScopeStart = start;
4720 d->m_findScopeEnd = end;
4721 d->m_findScopeVerticalBlockSelectionFirstColumn = verticalBlockSelectionFirstColumn;
4722 d->m_findScopeVerticalBlockSelectionLastColumn = verticalBlockSelectionLastColumn;
4723 viewport()->update();
4727 void BaseTextEditorWidget::_q_animateUpdate(int position, QPointF lastPos, QRectF rect)
4729 QTextCursor cursor(textCursor());
4730 cursor.setPosition(position);
4731 viewport()->update(QRectF(cursorRect(cursor).topLeft() + rect.topLeft(), rect.size()).toAlignedRect());
4732 if (!lastPos.isNull())
4733 viewport()->update(QRectF(lastPos + rect.topLeft(), rect.size()).toAlignedRect());
4737 BaseTextEditorAnimator::BaseTextEditorAnimator(QObject *parent)
4741 m_timeline = new QTimeLine(256, this);
4742 m_timeline->setCurveShape(QTimeLine::SineCurve);
4743 connect(m_timeline, SIGNAL(valueChanged(qreal)), this, SLOT(step(qreal)));
4744 connect(m_timeline, SIGNAL(finished()), this, SLOT(deleteLater()));
4745 m_timeline->start();
4749 void BaseTextEditorAnimator::setData(QFont f, QPalette pal, const QString &text)
4754 QFontMetrics fm(m_font);
4755 m_size = QSizeF(fm.width(m_text), fm.height());
4758 void BaseTextEditorAnimator::draw(QPainter *p, const QPointF &pos)
4760 m_lastDrawPos = pos;
4761 p->setPen(m_palette.text().color());
4763 f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
4765 int width = fm.width(m_text);
4766 QRectF r((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
4768 p->fillRect(r, m_palette.base());
4770 p->drawText(r, m_text);
4773 bool BaseTextEditorAnimator::isRunning() const
4775 return m_timeline->state() == QTimeLine::Running;
4778 QRectF BaseTextEditorAnimator::rect() const
4781 f.setPointSizeF(f.pointSizeF() * (1.0 + m_value/2));
4783 int width = fm.width(m_text);
4784 return QRectF((m_size.width()-width)/2, (m_size.height() - fm.height())/2, width, fm.height());
4787 void BaseTextEditorAnimator::step(qreal v)
4789 QRectF before = rect();
4791 QRectF after = rect();
4792 emit updateRequest(m_position, m_lastDrawPos, before.united(after));
4795 void BaseTextEditorAnimator::finish()
4802 void BaseTextEditorWidget::_q_matchParentheses()
4807 QTextCursor backwardMatch = textCursor();
4808 QTextCursor forwardMatch = textCursor();
4809 const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
4810 const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);
4812 QList<QTextEdit::ExtraSelection> extraSelections;
4814 if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch) {
4815 setExtraSelections(ParenthesesMatchingSelection, extraSelections); // clear
4819 int animatePosition = -1;
4820 if (backwardMatch.hasSelection()) {
4821 QTextEdit::ExtraSelection sel;
4822 if (backwardMatchType == TextBlockUserData::Mismatch) {
4823 sel.cursor = backwardMatch;
4824 sel.format = d->m_mismatchFormat;
4827 if (d->m_displaySettings.m_animateMatchingParentheses) {
4828 animatePosition = backwardMatch.selectionStart();
4829 } else if (d->m_formatRange) {
4830 sel.cursor = backwardMatch;
4831 sel.format = d->m_rangeFormat;
4832 extraSelections.append(sel);
4835 sel.cursor = backwardMatch;
4836 sel.format = d->m_matchFormat;
4838 sel.cursor.setPosition(backwardMatch.selectionStart());
4839 sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
4840 extraSelections.append(sel);
4842 sel.cursor.setPosition(backwardMatch.selectionEnd());
4843 sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
4845 extraSelections.append(sel);
4848 if (forwardMatch.hasSelection()) {
4849 QTextEdit::ExtraSelection sel;
4850 if (forwardMatchType == TextBlockUserData::Mismatch) {
4851 sel.cursor = forwardMatch;
4852 sel.format = d->m_mismatchFormat;
4855 if (d->m_displaySettings.m_animateMatchingParentheses) {
4856 animatePosition = forwardMatch.selectionEnd()-1;
4857 } else if (d->m_formatRange) {
4858 sel.cursor = forwardMatch;
4859 sel.format = d->m_rangeFormat;
4860 extraSelections.append(sel);
4863 sel.cursor = forwardMatch;
4864 sel.format = d->m_matchFormat;
4866 sel.cursor.setPosition(forwardMatch.selectionStart());
4867 sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
4868 extraSelections.append(sel);
4870 sel.cursor.setPosition(forwardMatch.selectionEnd());
4871 sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
4873 extraSelections.append(sel);
4877 if (animatePosition >= 0) {
4878 foreach (const QTextEdit::ExtraSelection &sel, BaseTextEditorWidget::extraSelections(ParenthesesMatchingSelection)) {
4879 if (sel.cursor.selectionStart() == animatePosition
4880 || sel.cursor.selectionEnd() - 1 == animatePosition) {
4881 animatePosition = -1;
4887 if (animatePosition >= 0) {
4889 d->m_animator->finish(); // one animation is enough
4890 d->m_animator = new BaseTextEditorAnimator(this);
4891 d->m_animator->setPosition(animatePosition);
4893 pal.setBrush(QPalette::Text, d->m_matchFormat.foreground());
4894 pal.setBrush(QPalette::Base, d->m_rangeFormat.background());
4895 d->m_animator->setData(font(), pal, characterAt(d->m_animator->position()));
4896 connect(d->m_animator, SIGNAL(updateRequest(int,QPointF,QRectF)),
4897 this, SLOT(_q_animateUpdate(int,QPointF,QRectF)));
4900 setExtraSelections(ParenthesesMatchingSelection, extraSelections);
4903 void BaseTextEditorWidget::_q_highlightBlocks()
4905 BaseTextEditorPrivateHighlightBlocks highlightBlocksInfo;
4908 if (d->extraAreaHighlightFoldedBlockNumber >= 0) {
4909 block = document()->findBlockByNumber(d->extraAreaHighlightFoldedBlockNumber);
4911 && block.next().isValid()
4912 && BaseTextDocumentLayout::foldingIndent(block.next())
4913 > BaseTextDocumentLayout::foldingIndent(block))
4914 block = block.next();
4917 QTextBlock closeBlock = block;
4918 while (block.isValid()) {
4919 int foldingIndent = BaseTextDocumentLayout::foldingIndent(block);
4921 while (block.previous().isValid() && BaseTextDocumentLayout::foldingIndent(block) >= foldingIndent)
4922 block = block.previous();
4923 int nextIndent = BaseTextDocumentLayout::foldingIndent(block);
4924 if (nextIndent == foldingIndent)
4926 highlightBlocksInfo.open.prepend(block.blockNumber());
4927 while (closeBlock.next().isValid()
4928 && BaseTextDocumentLayout::foldingIndent(closeBlock.next()) >= foldingIndent )
4929 closeBlock = closeBlock.next();
4930 highlightBlocksInfo.close.append(closeBlock.blockNumber());
4931 int visualIndent = qMin(d->visualIndent(block), d->visualIndent(closeBlock));
4932 highlightBlocksInfo.visualIndent.prepend(visualIndent);
4936 if (block.isValid()) {
4937 QTextCursor cursor(block);
4938 if (d->extraAreaHighlightCollapseColumn >= 0)
4939 cursor.setPosition(cursor.position() + qMin(d->extraAreaHighlightCollapseColumn,
4941 QTextCursor closeCursor;
4942 bool firstRun = true;
4943 while (TextBlockUserData::findPreviousBlockOpenParenthesis(&cursor, firstRun)) {
4945 highlightBlocksInfo.open.prepend(cursor.blockNumber());
4946 int visualIndent = d->visualIndent(cursor.block());
4947 if (closeCursor.isNull())
4948 closeCursor = cursor;
4949 if (TextBlockUserData::findNextBlockClosingParenthesis(&closeCursor)) {
4950 highlightBlocksInfo.close.append(closeCursor.blockNumber());
4951 visualIndent = qMin(visualIndent, d->visualIndent(closeCursor.block()));
4953 highlightBlocksInfo.visualIndent.prepend(visualIndent);
4957 if (d->m_highlightBlocksInfo != highlightBlocksInfo) {
4958 d->m_highlightBlocksInfo = highlightBlocksInfo;
4959 viewport()->update();
4960 d->m_extraArea->update();
4964 void BaseTextEditorWidget::setActionHack(QObject *hack)
4966 d->m_actionHack = hack;
4969 QObject *BaseTextEditorWidget::actionHack() const
4971 return d->m_actionHack;
4974 void BaseTextEditorWidget::changeEvent(QEvent *e)
4976 QPlainTextEdit::changeEvent(e);
4977 if (e->type() == QEvent::ApplicationFontChange
4978 || e->type() == QEvent::FontChange) {
4979 if (d->m_extraArea) {
4980 QFont f = d->m_extraArea->font();
4981 f.setPointSizeF(font().pointSizeF());
4982 d->m_extraArea->setFont(f);
4983 slotUpdateExtraAreaWidth();
4984 d->m_extraArea->update();
4989 void BaseTextEditorWidget::focusInEvent(QFocusEvent *e)
4991 QPlainTextEdit::focusInEvent(e);
4995 void BaseTextEditorWidget::focusOutEvent(QFocusEvent *e)
4997 QPlainTextEdit::focusOutEvent(e);
4998 if (viewport()->cursor().shape() == Qt::BlankCursor)
4999 viewport()->setCursor(Qt::IBeamCursor);
5003 void BaseTextEditorWidget::maybeSelectLine()
5005 QTextCursor cursor = textCursor();
5006 if (!cursor.hasSelection()) {
5007 const QTextBlock &block = cursor.block();
5008 if (block.next().isValid()) {
5009 cursor.setPosition(block.position());
5010 cursor.setPosition(block.next().position(), QTextCursor::KeepAnchor);
5012 cursor.movePosition(QTextCursor::EndOfBlock);
5013 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
5014 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
5016 setTextCursor(cursor);
5021 void BaseTextEditorWidget::cutLine()
5027 void BaseTextEditorWidget::deleteLine()
5030 textCursor().removeSelectedText();
5033 void BaseTextEditorWidget::setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections)
5035 if (selections.isEmpty() && d->m_extraSelections[kind].isEmpty())
5037 d->m_extraSelections[kind] = selections;
5039 if (kind == CodeSemanticsSelection) {
5040 d->m_overlay->clear();
5041 foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
5042 d->m_overlay->addOverlaySelection(selection.cursor,
5043 selection.format.background().color(),
5044 selection.format.background().color(),
5045 TextEditorOverlay::LockSize);
5047 d->m_overlay->setVisible(!d->m_overlay->isEmpty());
5048 } else if (kind == SnippetPlaceholderSelection) {
5049 d->m_snippetOverlay->clear();
5050 foreach (const QTextEdit::ExtraSelection &selection, d->m_extraSelections[kind]) {
5051 d->m_snippetOverlay->addOverlaySelection(selection.cursor,
5052 selection.format.background().color(),
5053 selection.format.background().color(),
5054 TextEditorOverlay::ExpandBegin);
5056 d->m_snippetOverlay->mapEquivalentSelections();
5057 d->m_snippetOverlay->setVisible(!d->m_snippetOverlay->isEmpty());
5059 QList<QTextEdit::ExtraSelection> all;
5060 for (int i = 0; i < NExtraSelectionKinds; ++i) {
5061 if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection)
5063 all += d->m_extraSelections[i];
5065 QPlainTextEdit::setExtraSelections(all);
5069 QList<QTextEdit::ExtraSelection> BaseTextEditorWidget::extraSelections(ExtraSelectionKind kind) const
5071 return d->m_extraSelections[kind];
5074 void BaseTextEditorWidget::maybeClearSomeExtraSelections(const QTextCursor &cursor)
5076 const int smallSelectionSize = 50 * 50;
5077 if (cursor.selectionEnd() - cursor.selectionStart() < smallSelectionSize)
5080 d->m_extraSelections[UndefinedSymbolSelection].clear();
5081 d->m_extraSelections[ObjCSelection].clear();
5082 d->m_extraSelections[CodeWarningsSelection].clear();
5084 QList<QTextEdit::ExtraSelection> all;
5085 for (int i = 0; i < NExtraSelectionKinds; ++i) {
5086 if (i == CodeSemanticsSelection || i == SnippetPlaceholderSelection)
5088 all += d->m_extraSelections[i];
5090 QPlainTextEdit::setExtraSelections(all);
5093 QString BaseTextEditorWidget::extraSelectionTooltip(int pos) const
5095 QList<QTextEdit::ExtraSelection> all;
5096 for (int i = 0; i < NExtraSelectionKinds; ++i) {
5097 const QList<QTextEdit::ExtraSelection> &sel = d->m_extraSelections[i];
5098 for (int j = 0; j < sel.size(); ++j) {
5099 const QTextEdit::ExtraSelection &s = sel.at(j);
5100 if (s.cursor.selectionStart() <= pos
5101 && s.cursor.selectionEnd() >= pos
5102 && !s.format.toolTip().isEmpty())
5103 return s.format.toolTip();
5109 // the blocks list must be sorted
5110 void BaseTextEditorWidget::setIfdefedOutBlocks(const QList<BaseTextEditorWidget::BlockRange> &blocks)
5112 QTextDocument *doc = document();
5113 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5114 QTC_ASSERT(documentLayout, return);
5116 bool needUpdate = false;
5118 QTextBlock block = doc->firstBlock();
5120 int rangeNumber = 0;
5121 int braceDepthDelta = 0;
5122 while (block.isValid()) {
5123 bool cleared = false;
5125 if (rangeNumber < blocks.size()) {
5126 const BlockRange &range = blocks.at(rangeNumber);
5127 if (block.position() >= range.first && ((block.position() + block.length() - 1) <= range.last || !range.last)) {
5128 set = BaseTextDocumentLayout::setIfdefedOut(block);
5130 cleared = BaseTextDocumentLayout::clearIfdefedOut(block);
5132 if (block.contains(range.last))
5135 cleared = BaseTextDocumentLayout::clearIfdefedOut(block);
5138 if (cleared || set) {
5140 int delta = BaseTextDocumentLayout::braceDepthDelta(block);
5142 braceDepthDelta += delta;
5144 braceDepthDelta -= delta;
5147 if (braceDepthDelta) {
5148 BaseTextDocumentLayout::changeBraceDepth(block,braceDepthDelta);
5149 BaseTextDocumentLayout::changeFoldingIndent(block, braceDepthDelta); // ### C++ only, refactor!
5152 block = block.next();
5156 documentLayout->requestUpdate();
5159 void BaseTextEditorWidget::format()
5161 QTextCursor cursor = textCursor();
5162 cursor.beginEditBlock();
5163 indent(document(), cursor, QChar::Null);
5164 cursor.endEditBlock();
5167 void BaseTextEditorWidget::rewrapParagraph()
5169 const int paragraphWidth = displaySettings().m_wrapColumn;
5170 const QRegExp anyLettersOrNumbers = QRegExp("\\w");
5171 const int tabSize = tabSettings().m_tabSize;
5173 QTextCursor cursor = textCursor();
5174 cursor.beginEditBlock();
5176 // Find start of paragraph.
5178 while (cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor)) {
5179 QTextBlock block = cursor.block();
5180 QString text = block.text();
5182 // If this block is empty, move marker back to previous and terminate.
5183 if (!text.contains(anyLettersOrNumbers)) {
5184 cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
5189 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
5191 // Find indent level of current block.
5193 int indentLevel = 0;
5194 QString text = cursor.block().text();
5196 for (int i = 0; i < text.length(); i++) {
5197 const QChar ch = text.at(i);
5199 if (ch == QLatin1Char(' '))
5201 else if (ch == QLatin1Char('\t'))
5202 indentLevel += tabSize - (indentLevel % tabSize);
5207 // If there is a common prefix, it should be kept and expanded to all lines.
5208 // this allows nice reflowing of doxygen style comments.
5209 QTextCursor nextBlock = cursor;
5210 QString commonPrefix;
5212 if (nextBlock.movePosition(QTextCursor::NextBlock))
5214 QString nText = nextBlock.block().text();
5215 int maxLength = qMin(text.length(), nText.length());
5217 for (int i = 0; i < maxLength; ++i) {
5218 const QChar ch = text.at(i);
5220 if (ch != nText[i] || ch.isLetterOrNumber())
5222 commonPrefix.append(ch);
5227 // Find end of paragraph.
5228 while (cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor)) {
5229 QString text = cursor.block().text();
5231 if (!text.contains(anyLettersOrNumbers))
5236 QString selectedText = cursor.selectedText();
5238 // Preserve initial indent level.or common prefix.
5241 if (commonPrefix.isEmpty()) {
5242 spacing = tabSettings().indentationString(0, indentLevel, textCursor().block());
5244 spacing = commonPrefix;
5245 indentLevel = commonPrefix.length();
5248 int currentLength = indentLevel;
5250 result.append(spacing);
5252 // Remove existing instances of any common prefix from paragraph to
5254 selectedText.remove(0, commonPrefix.length());
5255 commonPrefix.prepend(QChar::ParagraphSeparator);
5256 selectedText.replace(commonPrefix, QLatin1String("\n"));
5258 // remove any repeated spaces, trim lines to PARAGRAPH_WIDTH width and
5259 // keep the same indentation level as first line in paragraph.
5260 QString currentWord;
5262 for (int i = 0; i < selectedText.length(); ++i) {
5263 QChar ch = selectedText.at(i);
5265 if (!currentWord.isEmpty()) {
5266 currentLength += currentWord.length() + 1;
5268 if (currentLength > paragraphWidth) {
5269 currentLength = currentWord.length() + 1 + indentLevel;
5270 result.chop(1); // remove trailing space
5271 result.append(QChar::ParagraphSeparator);
5272 result.append(spacing);
5275 result.append(currentWord);
5276 result.append(QLatin1Char(' '));
5277 currentWord.clear();
5283 currentWord.append(ch);
5286 result.append(QChar::ParagraphSeparator);
5288 cursor.insertText(result);
5289 cursor.endEditBlock();
5292 void BaseTextEditorWidget::unCommentSelection()
5296 void BaseTextEditorWidget::showEvent(QShowEvent* e)
5298 if (!d->m_fontSettings.isEmpty()) {
5299 setFontSettings(d->m_fontSettings);
5300 d->m_fontSettings.clear();
5302 QPlainTextEdit::showEvent(e);
5306 void BaseTextEditorWidget::setFontSettingsIfVisible(const TextEditor::FontSettings &fs)
5309 d->m_fontSettings = fs;
5312 setFontSettings(fs);
5315 void BaseTextEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
5317 const QTextCharFormat textFormat = fs.toTextCharFormat(QLatin1String(Constants::C_TEXT));
5318 const QTextCharFormat selectionFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SELECTION));
5319 const QTextCharFormat lineNumberFormat = fs.toTextCharFormat(QLatin1String(Constants::C_LINE_NUMBER));
5320 const QTextCharFormat searchResultFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_RESULT));
5321 d->m_searchScopeFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_SCOPE));
5322 const QTextCharFormat parenthesesFormat = fs.toTextCharFormat(QLatin1String(Constants::C_PARENTHESES));
5323 d->m_currentLineFormat = fs.toTextCharFormat(QLatin1String(Constants::C_CURRENT_LINE));
5324 d->m_currentLineNumberFormat = fs.toTextCharFormat(QLatin1String(Constants::C_CURRENT_LINE_NUMBER));
5325 d->m_linkFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_LINK));
5326 d->m_ifdefedOutFormat = fs.toTextCharFormat(QLatin1String(Constants::C_DISABLED_CODE));
5327 QFont font(textFormat.font());
5329 const QColor foreground = textFormat.foreground().color();
5330 const QColor background = textFormat.background().color();
5331 QPalette p = palette();
5332 p.setColor(QPalette::Text, foreground);
5333 p.setColor(QPalette::Foreground, foreground);
5334 p.setColor(QPalette::Base, background);
5335 p.setColor(QPalette::Highlight, (selectionFormat.background().style() != Qt::NoBrush) ?
5336 selectionFormat.background().color() :
5337 QApplication::palette().color(QPalette::Highlight));
5339 p.setBrush(QPalette::HighlightedText, selectionFormat.foreground());
5341 p.setBrush(QPalette::Inactive, QPalette::Highlight, p.highlight());
5342 p.setBrush(QPalette::Inactive, QPalette::HighlightedText, p.highlightedText());
5345 setTabSettings(d->m_document->tabSettings()); // update tabs, they depend on the font
5348 QPalette ep = d->m_extraArea->palette();
5349 ep.setColor(QPalette::Dark, lineNumberFormat.foreground().color());
5350 ep.setColor(QPalette::Background, lineNumberFormat.background().style() != Qt::NoBrush ?
5351 lineNumberFormat.background().color() : background);
5352 d->m_extraArea->setPalette(ep);
5355 d->m_searchResultFormat.setBackground(searchResultFormat.background());
5358 d->m_matchFormat.setForeground(parenthesesFormat.foreground());
5359 d->m_rangeFormat.setBackground(parenthesesFormat.background());
5363 d->m_occurrencesFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES));
5364 d->m_occurrencesFormat.clearForeground();
5365 d->m_occurrenceRenameFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES_RENAME));
5366 d->m_occurrenceRenameFormat.clearForeground();
5368 slotUpdateExtraAreaWidth(); // Adjust to new font width
5369 updateCurrentLineHighlight(); // Make sure it takes the new color
5372 void BaseTextEditorWidget::setTabSettings(const TabSettings &ts)
5374 d->m_document->setTabSettings(ts);
5375 int charWidth = QFontMetrics(font()).width(QChar(' '));
5376 setTabStopWidth(charWidth * ts.m_tabSize);
5379 void BaseTextEditorWidget::setDisplaySettings(const DisplaySettings &ds)
5381 setLineWrapMode(ds.m_textWrapping ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap);
5382 setLineNumbersVisible(ds.m_displayLineNumbers);
5383 setVisibleWrapColumn(ds.m_showWrapColumn ? ds.m_wrapColumn : 0);
5384 setHighlightCurrentLine(ds.m_highlightCurrentLine);
5385 setRevisionsVisible(ds.m_markTextChanges);
5386 setCenterOnScroll(ds.m_centerCursorOnScroll);
5388 if (d->m_displaySettings.m_visualizeWhitespace != ds.m_visualizeWhitespace) {
5389 if (SyntaxHighlighter *highlighter = baseTextDocument()->syntaxHighlighter())
5390 highlighter->rehighlight();
5391 QTextOption option = document()->defaultTextOption();
5392 if (ds.m_visualizeWhitespace)
5393 option.setFlags(option.flags() | QTextOption::ShowTabsAndSpaces);
5395 option.setFlags(option.flags() & ~QTextOption::ShowTabsAndSpaces);
5396 option.setFlags(option.flags() | QTextOption::AddSpaceForLineAndParagraphSeparators);
5397 document()->setDefaultTextOption(option);
5400 d->m_displaySettings = ds;
5401 if (!ds.m_highlightBlocks) {
5402 d->extraAreaHighlightFoldedBlockNumber = -1;
5403 d->m_highlightBlocksInfo = BaseTextEditorPrivateHighlightBlocks();
5406 updateCodeFoldingVisible();
5408 viewport()->update();
5409 extraArea()->update();
5412 void BaseTextEditorWidget::setBehaviorSettings(const TextEditor::BehaviorSettings &bs)
5414 setMouseNavigationEnabled(bs.m_mouseNavigation);
5415 setScrollWheelZoomingEnabled(bs.m_scrollWheelZooming);
5418 void BaseTextEditorWidget::setStorageSettings(const StorageSettings &storageSettings)
5420 d->m_document->setStorageSettings(storageSettings);
5423 void BaseTextEditorWidget::setCompletionSettings(const TextEditor::CompletionSettings &completionSettings)
5425 d->m_autoCompleter->setAutoParenthesesEnabled(completionSettings.m_autoInsertBrackets);
5426 d->m_autoCompleter->setSurroundWithEnabled(completionSettings.m_autoInsertBrackets);
5429 void BaseTextEditorWidget::setExtraEncodingSettings(const ExtraEncodingSettings &extraEncodingSettings)
5431 d->m_document->setExtraEncodingSettings(extraEncodingSettings);
5434 void BaseTextEditorWidget::fold()
5436 QTextDocument *doc = document();
5437 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5438 QTC_ASSERT(documentLayout, return);
5439 QTextBlock block = textCursor().block();
5440 if (!(BaseTextDocumentLayout::canFold(block) && block.next().isVisible())) {
5441 // find the closest previous block which can fold
5442 int indent = BaseTextDocumentLayout::foldingIndent(block);
5443 while (block.isValid() && (BaseTextDocumentLayout::foldingIndent(block) >= indent || !block.isVisible()))
5444 block = block.previous();
5446 if (block.isValid()) {
5447 BaseTextDocumentLayout::doFoldOrUnfold(block, false);
5448 d->moveCursorVisible();
5449 documentLayout->requestUpdate();
5450 documentLayout->emitDocumentSizeChanged();
5454 void BaseTextEditorWidget::unfold()
5456 QTextDocument *doc = document();
5457 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5458 QTC_ASSERT(documentLayout, return);
5459 QTextBlock block = textCursor().block();
5460 while (block.isValid() && !block.isVisible())
5461 block = block.previous();
5462 BaseTextDocumentLayout::doFoldOrUnfold(block, true);
5463 d->moveCursorVisible();
5464 documentLayout->requestUpdate();
5465 documentLayout->emitDocumentSizeChanged();
5468 void BaseTextEditorWidget::unfoldAll()
5470 QTextDocument *doc = document();
5471 BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
5472 QTC_ASSERT(documentLayout, return);
5474 QTextBlock block = doc->firstBlock();
5475 bool makeVisible = true;
5476 while (block.isValid()) {
5477 if (block.isVisible() && BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
5478 makeVisible = false;
5481 block = block.next();
5484 block = doc->firstBlock();
5486 while (block.isValid()) {
5487 if (BaseTextDocumentLayout::canFold(block))
5488 BaseTextDocumentLayout::doFoldOrUnfold(block, makeVisible);
5489 block = block.next();
5492 d->moveCursorVisible();
5493 documentLayout->requestUpdate();
5494 documentLayout->emitDocumentSizeChanged();
5498 void BaseTextEditorWidget::setTextCodec(QTextCodec *codec)
5500 baseTextDocument()->setCodec(codec);
5503 QTextCodec *BaseTextEditorWidget::textCodec() const
5505 return baseTextDocument()->codec();
5508 void BaseTextEditorWidget::setReadOnly(bool b)
5510 QPlainTextEdit::setReadOnly(b);
5512 setTextInteractionFlags(textInteractionFlags() | Qt::TextSelectableByKeyboard);
5515 void BaseTextEditorWidget::cut()
5517 if (d->m_inBlockSelectionMode) {
5519 d->removeBlockSelection();
5522 QPlainTextEdit::cut();
5525 void BaseTextEditorWidget::paste()
5527 if (d->m_inBlockSelectionMode) {
5528 d->removeBlockSelection();
5530 QPlainTextEdit::paste();
5533 QMimeData *BaseTextEditorWidget::createMimeDataFromSelection() const
5535 if (d->m_inBlockSelectionMode) {
5536 QMimeData *mimeData = new QMimeData;
5537 QString text = d->copyBlockSelection();
5538 mimeData->setData(QLatin1String("application/vnd.nokia.qtcreator.vblocktext"), text.toUtf8());
5539 mimeData->setText(text); // for exchangeability
5541 } else if (textCursor().hasSelection()) {
5542 QTextCursor cursor = textCursor();
5543 QMimeData *mimeData = new QMimeData;
5545 // Copy the selected text as plain text
5546 QString text = cursor.selectedText();
5547 convertToPlainText(text);
5548 mimeData->setText(text);
5550 // Copy the selected text as HTML
5552 // Create a new document from the selected text document fragment
5553 QTextDocument *tempDocument = new QTextDocument;
5554 QTextCursor tempCursor(tempDocument);
5555 tempCursor.insertFragment(cursor.selection());
5557 // Apply the additional formats set by the syntax highlighter
5558 QTextBlock start = document()->findBlock(cursor.selectionStart());
5559 QTextBlock end = document()->findBlock(cursor.selectionEnd());
5562 const int selectionStart = cursor.selectionStart();
5563 const int endOfDocument = tempDocument->characterCount() - 1;
5564 for (QTextBlock current = start; current.isValid() && current != end; current = current.next()) {
5565 const QTextLayout *layout = current.layout();
5566 foreach (const QTextLayout::FormatRange &range, layout->additionalFormats()) {
5567 const int start = current.position() + range.start - selectionStart;
5568 const int end = start + range.length;
5569 if (end <= 0 || start >= endOfDocument)
5571 tempCursor.setPosition(qMax(start, 0));
5572 tempCursor.setPosition(qMin(end, endOfDocument), QTextCursor::KeepAnchor);
5573 tempCursor.setCharFormat(range.format);
5577 // Reset the user states since they are not interesting
5578 for (QTextBlock block = tempDocument->begin(); block.isValid(); block = block.next())
5579 block.setUserState(-1);
5581 // Make sure the text appears pre-formatted
5582 tempCursor.setPosition(0);
5583 tempCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
5584 QTextBlockFormat blockFormat = tempCursor.blockFormat();
5585 blockFormat.setNonBreakableLines(true);
5586 tempCursor.setBlockFormat(blockFormat);
5588 mimeData->setHtml(tempCursor.selection().toHtml());
5589 delete tempDocument;
5593 Try to figure out whether we are copying an entire block, and store the complete block
5594 including indentation in the qtcreator.blocktext mimetype.
5596 QTextCursor selstart = cursor;
5597 selstart.setPosition(cursor.selectionStart());
5598 QTextCursor selend = cursor;
5599 selend.setPosition(cursor.selectionEnd());
5600 const TabSettings &ts = d->m_document->tabSettings();
5602 bool startOk = ts.cursorIsAtBeginningOfLine(selstart);
5603 bool multipleBlocks = (selend.block() != selstart.block());
5605 if (startOk && multipleBlocks) {
5606 selstart.movePosition(QTextCursor::StartOfBlock);
5607 if (ts.cursorIsAtBeginningOfLine(selend))
5608 selend.movePosition(QTextCursor::StartOfBlock);
5609 cursor.setPosition(selstart.position());
5610 cursor.setPosition(selend.position(), QTextCursor::KeepAnchor);
5611 text = cursor.selectedText();
5612 mimeData->setData(QLatin1String("application/vnd.nokia.qtcreator.blocktext"), text.toUtf8());
5619 bool BaseTextEditorWidget::canInsertFromMimeData(const QMimeData *source) const
5621 return QPlainTextEdit::canInsertFromMimeData(source);
5624 void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source)
5629 if (source->hasFormat(QLatin1String("application/vnd.nokia.qtcreator.vblocktext"))) {
5630 QString text = QString::fromUtf8(source->data(QLatin1String("application/vnd.nokia.qtcreator.vblocktext")));
5634 if (CompletionSupport::instance()->isActive())
5637 QStringList lines = text.split(QLatin1Char('\n'));
5638 QTextCursor cursor = textCursor();
5639 cursor.beginEditBlock();
5640 const TabSettings &ts = d->m_document->tabSettings();
5641 int initialCursorPosition = cursor.position();
5642 int column = ts.columnAt(cursor.block().text(), cursor.positionInBlock());
5643 cursor.insertText(lines.first());
5644 for (int i = 1; i < lines.count(); ++i) {
5645 QTextBlock next = cursor.block().next();
5646 if (next.isValid()) {
5647 cursor.setPosition(next.position());
5649 cursor.movePosition(QTextCursor::EndOfBlock);
5650 cursor.insertBlock();
5653 int position = ts.positionAtColumn(cursor.block().text(), column, &offset);
5654 cursor.setPosition(cursor.block().position() + position);
5656 cursor.deleteChar();
5657 cursor.insertText(QString(-offset, QLatin1Char(' ')));
5659 cursor.insertText(QString(offset, QLatin1Char(' ')));
5661 cursor.insertText(lines.at(i));
5663 cursor.setPosition(initialCursorPosition);
5664 cursor.endEditBlock();
5665 setTextCursor(cursor);
5666 ensureCursorVisible();
5668 if (d->m_snippetOverlay->isVisible() && lines.count() > 1) {
5669 d->m_snippetOverlay->hide();
5670 d->m_snippetOverlay->clear();
5676 QString text = source->text();
5680 if (CompletionSupport::instance()->isActive())
5683 if (d->m_snippetOverlay->isVisible() && (text.contains(QLatin1Char('\n'))
5684 || text.contains(QLatin1Char('\t')))) {
5685 d->m_snippetOverlay->hide();
5686 d->m_snippetOverlay->clear();
5689 const TabSettings &ts = d->m_document->tabSettings();
5690 QTextCursor cursor = textCursor();
5691 if (!ts.m_autoIndent) {
5692 cursor.beginEditBlock();
5693 cursor.insertText(text);
5694 cursor.endEditBlock();
5695 setTextCursor(cursor);
5699 cursor.beginEditBlock();
5700 cursor.removeSelectedText();
5702 bool insertAtBeginningOfLine = ts.cursorIsAtBeginningOfLine(cursor);
5704 if (insertAtBeginningOfLine
5705 && source->hasFormat(QLatin1String("application/vnd.nokia.qtcreator.blocktext"))) {
5706 text = QString::fromUtf8(source->data(QLatin1String("application/vnd.nokia.qtcreator.blocktext")));
5711 int reindentBlockStart = cursor.blockNumber() + (insertAtBeginningOfLine?0:1);
5713 bool hasFinalNewline = (text.endsWith(QLatin1Char('\n'))
5714 || text.endsWith(QChar::ParagraphSeparator)
5715 || text.endsWith(QLatin1Char('\r')));
5717 if (insertAtBeginningOfLine
5718 && hasFinalNewline) // since we'll add a final newline, preserve current line's indentation
5719 cursor.setPosition(cursor.block().position());
5721 int cursorPosition = cursor.position();
5722 cursor.insertText(text);
5724 int reindentBlockEnd = cursor.blockNumber() - (hasFinalNewline?1:0);
5726 if (reindentBlockStart < reindentBlockEnd
5727 || (reindentBlockStart == reindentBlockEnd
5728 && (!insertAtBeginningOfLine || hasFinalNewline))) {
5729 if (insertAtBeginningOfLine && !hasFinalNewline) {
5730 QTextCursor unnecessaryWhitespace = cursor;
5731 unnecessaryWhitespace.setPosition(cursorPosition);
5732 unnecessaryWhitespace.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
5733 unnecessaryWhitespace.removeSelectedText();
5735 QTextCursor c = cursor;
5736 c.setPosition(cursor.document()->findBlockByNumber(reindentBlockStart).position());
5737 c.setPosition(cursor.document()->findBlockByNumber(reindentBlockEnd).position(),
5738 QTextCursor::KeepAnchor);
5739 reindent(document(), c);
5742 cursor.endEditBlock();
5743 setTextCursor(cursor);
5746 void BaseTextEditorWidget::appendStandardContextMenuActions(QMenu *menu)
5748 menu->addSeparator();
5749 Core::ActionManager *am = Core::ICore::instance()->actionManager();
5751 QAction *a = am->command(Core::Constants::CUT)->action();
5752 if (a && a->isEnabled())
5754 a = am->command(Core::Constants::COPY)->action();
5755 if (a && a->isEnabled())
5757 a = am->command(Core::Constants::PASTE)->action();
5758 if (a && a->isEnabled())
5763 BaseTextEditor::BaseTextEditor(BaseTextEditorWidget *editor)
5766 using namespace Find;
5767 Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
5768 BaseTextFind *baseTextFind = new BaseTextFind(editor);
5769 connect(baseTextFind, SIGNAL(highlightAll(QString,Find::FindFlags)),
5770 editor, SLOT(highlightSearchResults(QString,Find::FindFlags)));
5771 connect(baseTextFind, SIGNAL(findScopeChanged(QTextCursor,QTextCursor,int,int)),
5772 editor, SLOT(setFindScope(QTextCursor,QTextCursor,int,int)));
5773 aggregate->add(baseTextFind);
5774 aggregate->add(editor);
5776 m_cursorPositionLabel = new Utils::LineColumnLabel;
5778 m_stretchWidget = new QWidget;
5779 m_stretchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
5781 m_toolBar = new QToolBar;
5782 m_toolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
5783 m_toolBar->addWidget(m_stretchWidget);
5784 m_cursorPositionLabelAction = m_toolBar->addWidget(m_cursorPositionLabel);
5786 connect(editor, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition()));
5789 BaseTextEditor::~BaseTextEditor()
5795 QWidget *BaseTextEditor::toolBar()
5800 void BaseTextEditor::insertExtraToolBarWidget(BaseTextEditor::Side side,
5803 if (widget->sizePolicy().horizontalPolicy() & QSizePolicy::ExpandFlag) {
5804 if (m_stretchWidget)
5805 m_stretchWidget->deleteLater();
5806 m_stretchWidget = 0;
5810 m_toolBar->insertWidget(m_cursorPositionLabelAction, widget);
5812 m_toolBar->insertWidget(m_toolBar->actions().first(), widget);
5815 int BaseTextEditor::find(const QString &) const
5820 int BaseTextEditor::currentLine() const
5822 return e->textCursor().blockNumber() + 1;
5825 int BaseTextEditor::currentColumn() const
5827 QTextCursor cursor = e->textCursor();
5828 return cursor.position() - cursor.block().position() + 1;
5831 int BaseTextEditor::columnCount() const
5833 return e->columnCount();
5836 int BaseTextEditor::rowCount() const
5838 return e->rowCount();
5841 QRect BaseTextEditor::cursorRect(int pos) const
5843 QTextCursor tc = e->textCursor();
5845 tc.setPosition(pos);
5846 QRect result = e->cursorRect(tc);
5847 result.moveTo(e->viewport()->mapToGlobal(result.topLeft()));
5851 QString BaseTextEditor::contents() const
5853 return e->toPlainText();
5856 QString BaseTextEditor::selectedText() const
5858 if (e->textCursor().hasSelection())
5859 return e->textCursor().selectedText();
5863 QString BaseTextEditor::textAt(int pos, int length) const
5865 QTextCursor c = e->textCursor();
5869 c.movePosition(QTextCursor::End);
5870 if (pos + length > c.position())
5871 length = c.position() - pos;
5874 c.setPosition(pos + length, QTextCursor::KeepAnchor);
5876 return c.selectedText();
5879 void BaseTextEditor::remove(int length)
5881 QTextCursor tc = e->textCursor();
5882 tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
5883 tc.removeSelectedText();
5886 void BaseTextEditor::insert(const QString &string)
5888 QTextCursor tc = e->textCursor();
5889 tc.insertText(string);
5892 void BaseTextEditor::replace(int length, const QString &string)
5894 QTextCursor tc = e->textCursor();
5895 tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
5896 tc.insertText(string);
5899 void BaseTextEditor::setCursorPosition(int pos)
5901 QTextCursor tc = e->textCursor();
5902 tc.setPosition(pos);
5903 e->setTextCursor(tc);
5906 void BaseTextEditor::select(int toPos)
5908 QTextCursor tc = e->textCursor();
5909 tc.setPosition(toPos, QTextCursor::KeepAnchor);
5910 e->setTextCursor(tc);
5913 void BaseTextEditor::updateCursorPosition()
5915 const QTextCursor cursor = e->textCursor();
5916 const QTextBlock block = cursor.block();
5917 const int line = block.blockNumber() + 1;
5918 const int column = cursor.position() - block.position();
5919 m_cursorPositionLabel->setText(tr("Line: %1, Col: %2").arg(line).arg(e->tabSettings().columnAt(block.text(), column)+1),
5920 tr("Line: 9999, Col: 999"));
5921 m_contextHelpId.clear();
5923 if (!block.isVisible())
5924 e->ensureCursorVisible();
5928 QString BaseTextEditor::contextHelpId() const
5930 if (m_contextHelpId.isEmpty())
5931 emit const_cast<BaseTextEditor*>(this)->contextHelpIdRequested(e->editor(),
5932 e->textCursor().position());
5933 return m_contextHelpId;
5937 void BaseTextEditorWidget::setRefactorMarkers(const Internal::RefactorMarkers &markers)
5939 foreach (const RefactorMarker &marker, d->m_refactorOverlay->markers())
5940 requestBlockUpdate(marker.cursor.block());
5941 d->m_refactorOverlay->setMarkers(markers);
5942 foreach (const RefactorMarker &marker, markers)
5943 requestBlockUpdate(marker.cursor.block());
5946 void BaseTextEditorWidget::doFoo() {
5948 qDebug() << Q_FUNC_INFO;
5949 RefactorMarkers markers = d->m_refactorOverlay->markers();
5950 RefactorMarker marker;
5951 marker.tooltip = "Hello World";
5952 marker.cursor = textCursor();
5954 setRefactorMarkers(markers);
5958 void Internal::BaseTextBlockSelection::moveAnchor(int blockNumber, int visualColumn)
5960 if (visualColumn >= 0) {
5962 lastVisualColumn = visualColumn;
5963 if (lastVisualColumn < firstVisualColumn) {
5964 qSwap(firstVisualColumn, lastVisualColumn);
5965 anchor = (Anchor) (anchor - 1);
5968 firstVisualColumn = visualColumn;
5969 if (firstVisualColumn > lastVisualColumn) {
5970 qSwap(firstVisualColumn, lastVisualColumn);
5971 anchor = (Anchor) (anchor + 1);
5976 if (blockNumber >= 0 && blockNumber < firstBlock.document()->blockCount()) {
5977 if (anchor <= TopRight) {
5978 firstBlock.setPosition(firstBlock.document()->findBlockByNumber(blockNumber).position());
5979 if (firstBlock.blockNumber() > lastBlock.blockNumber()) {
5980 qSwap(firstBlock, lastBlock);
5981 anchor = (Anchor) (anchor + 2);
5984 lastBlock.setPosition(firstBlock.document()->findBlockByNumber(blockNumber).position());
5985 if (lastBlock.blockNumber() < firstBlock.blockNumber()) {
5986 qSwap(firstBlock, lastBlock);
5987 anchor = (Anchor) (anchor - 2);
5991 firstBlock.movePosition(QTextCursor::StartOfBlock);
5992 lastBlock.movePosition(QTextCursor::EndOfBlock);
5995 QTextCursor Internal::BaseTextBlockSelection::selection(const TabSettings &ts) const
5997 QTextCursor cursor = firstBlock;
5998 if (anchor <= TopRight) {
5999 cursor.setPosition(lastBlock.block().position() + ts.positionAtColumn(lastBlock.block().text(), lastVisualColumn));
6000 cursor.setPosition(firstBlock.block().position() + ts.positionAtColumn(firstBlock.block().text(), firstVisualColumn),
6001 QTextCursor::KeepAnchor);
6003 cursor.setPosition(firstBlock.block().position() + ts.positionAtColumn(firstBlock.block().text(), firstVisualColumn));
6004 cursor.setPosition(lastBlock.block().position() + ts.positionAtColumn(lastBlock.block().text(), lastVisualColumn),
6005 QTextCursor::KeepAnchor);
6010 void Internal::BaseTextBlockSelection::fromSelection(const TabSettings &ts, const QTextCursor &selection)
6012 firstBlock = selection;
6013 firstBlock.setPosition(selection.selectionStart());
6014 firstVisualColumn = ts.columnAt(firstBlock.block().text(), firstBlock.positionInBlock());
6015 lastBlock = selection;
6016 lastBlock.setPosition(selection.selectionEnd());
6017 lastVisualColumn = ts.columnAt(lastBlock.block().text(), lastBlock.positionInBlock());
6018 if (selection.anchor() > selection.position())
6021 anchor = BottomRight;
6023 firstBlock.movePosition(QTextCursor::StartOfBlock);
6024 lastBlock.movePosition(QTextCursor::EndOfBlock);
6026 bool BaseTextEditorWidget::inFindScope(const QTextCursor &cursor)
6028 if (cursor.isNull())
6030 return inFindScope(cursor.selectionStart(), cursor.selectionEnd());
6033 bool BaseTextEditorWidget::inFindScope(int selectionStart, int selectionEnd)
6035 if (d->m_findScopeStart.isNull())
6036 return true; // no scope, everything is included
6037 if (selectionStart < d->m_findScopeStart.position())
6039 if (selectionEnd > d->m_findScopeEnd.position())
6041 if (d->m_findScopeVerticalBlockSelectionFirstColumn < 0)
6043 QTextBlock block = document()->findBlock(selectionStart);
6044 if (block != document()->findBlock(selectionEnd))
6046 QString text = block.text();
6047 const TabSettings &ts = tabSettings();
6048 int startPosition = ts.positionAtColumn(text, d->m_findScopeVerticalBlockSelectionFirstColumn);
6049 int endPosition = ts.positionAtColumn(text, d->m_findScopeVerticalBlockSelectionLastColumn);
6050 if (selectionStart - block.position() < startPosition)
6052 if (selectionEnd - block.position() > endPosition)
6057 void BaseTextEditorWidget::setBlockSelection(bool on)
6059 if (d->m_inBlockSelectionMode != on) {
6060 d->m_inBlockSelectionMode = on;
6062 d->m_blockSelection.fromSelection(tabSettings(), textCursor());
6064 viewport()->update();
6067 bool BaseTextEditorWidget::hasBlockSelection() const
6069 return d->m_inBlockSelectionMode;
6072 void BaseTextEditorWidget::handleBlockSelection(int diff_row, int diff_col)
6075 if (!d->m_inBlockSelectionMode) {
6076 d->m_blockSelection.fromSelection(tabSettings(), textCursor());
6077 d->m_inBlockSelectionMode = true;
6080 d->m_blockSelection.moveAnchor(d->m_blockSelection.anchorBlockNumber() + diff_row,
6081 d->m_blockSelection.anchorColumnNumber() + diff_col);
6082 setTextCursor(d->m_blockSelection.selection(tabSettings()));
6084 viewport()->update();
6086 // ### TODO ensure horizontal visibility
6087 // const bool rtl = q->isRightToLeft();
6088 // if (cr.left() < visible.left() || cr.right() > visible.right()) {
6089 // int x = cr.center().x() + horizontalOffset() - visible.width()/2;
6090 // hbar->setValue(rtl ? hbar->maximum() - x : x);
6095 int BaseTextEditorWidget::columnCount() const
6097 QFontMetricsF fm(font());
6098 return viewport()->rect().width() / fm.width(QLatin1Char('x'));
6101 int BaseTextEditorWidget::rowCount() const
6103 QFontMetricsF fm(font());
6104 return viewport()->rect().height() / fm.lineSpacing();
6108 Helper method to transform a selected text. If nothing is selected at the moment
6109 the word under the cursor is used.
6110 The type of the transformation is determined by the method pointer given.
6112 @param method pointer to the QString method to use for the transformation
6114 @see uppercaseSelection, lowercaseSelection
6116 void BaseTextEditorWidget::transformSelection(Internal::TransformationMethod method)
6118 QTextCursor cursor = textCursor();
6120 int pos = cursor.position();
6121 int anchor = cursor.anchor();
6123 if (!cursor.hasSelection()) {
6124 // if nothing is selected, select the word over the cursor
6125 cursor.select(QTextCursor::WordUnderCursor);
6128 QString text = cursor.selectedText();
6129 QString transformedText = (text.*method)();
6131 if (transformedText == text) {
6132 // if the transformation does not do anything to the selection, do no create an undo step
6136 cursor.insertText(transformedText);
6138 // (re)select the changed text
6139 // Note: this assumes the transformation did not change the length,
6140 cursor.setPosition(anchor);
6141 cursor.setPosition(pos, QTextCursor::KeepAnchor);
6142 setTextCursor(cursor);