texteditor.cpp 254 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24 25 26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

31 32
#include "texteditor.h"
#include "texteditor_p.h"
hjk's avatar
hjk committed
33

34
#include "autocompleter.h"
35
#include "basehoverhandler.h"
36
#include "behaviorsettings.h"
37 38
#include "circularclipboard.h"
#include "circularclipboardassist.h"
con's avatar
con committed
39
#include "codecselector.h"
40
#include "completionsettings.h"
41 42
#include "convenience.h"
#include "highlighterutils.h"
Jarek Kobus's avatar
Jarek Kobus committed
43
#include "icodestylepreferences.h"
44
#include "indenter.h"
45 46 47 48 49
#include "snippets/snippet.h"
#include "syntaxhighlighter.h"
#include "tabsettings.h"
#include "textdocument.h"
#include "textdocumentlayout.h"
50
#include "texteditoroverlay.h"
51 52
#include "texteditorsettings.h"
#include "typingsettings.h"
53

54
#include <texteditor/codeassist/assistinterface.h>
55 56
#include <texteditor/codeassist/codeassistant.h>
#include <texteditor/codeassist/completionassistprovider.h>
57 58 59 60 61
#include <texteditor/generichighlighter/context.h>
#include <texteditor/generichighlighter/highlightdefinition.h>
#include <texteditor/generichighlighter/highlighter.h>
#include <texteditor/generichighlighter/highlightersettings.h>
#include <texteditor/generichighlighter/manager.h>
con's avatar
con committed
62

63
#include <coreplugin/icore.h>
64
#include <aggregation/aggregate.h>
65
#include <coreplugin/actionmanager/actionmanager.h>
66
#include <coreplugin/actionmanager/actioncontainer.h>
con's avatar
con committed
67
#include <coreplugin/coreconstants.h>
68
#include <coreplugin/editormanager/editormanager.h>
69
#include <coreplugin/infobar.h>
70
#include <coreplugin/manhattanstyle.h>
71
#include <coreplugin/find/basetextfind.h>
con's avatar
con committed
72
#include <utils/linecolumnlabel.h>
73
#include <utils/fileutils.h>
74
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
75
#include <utils/qtcassert.h>
76
#include <utils/stylehelper.h>
77 78
#include <utils/tooltip/tooltip.h>
#include <utils/tooltip/tipcontents.h>
79
#include <utils/uncommentselection.h>
con's avatar
con committed
80

81 82
#include <QAbstractTextDocumentLayout>
#include <QApplication>
83 84 85
#include <QClipboard>
#include <QCoreApplication>
#include <QDebug>
86
#include <QKeyEvent>
87 88 89
#include <QMenu>
#include <QMessageBox>
#include <QMimeData>
90 91
#include <QPainter>
#include <QPrintDialog>
92
#include <QPrinter>
93
#include <QDrag>
94 95 96
#include <QScrollBar>
#include <QShortcut>
#include <QStyle>
97 98
#include <QTextBlock>
#include <QTextCodec>
99 100 101
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QTextLayout>
102 103 104
#include <QTime>
#include <QTimeLine>
#include <QTimer>
105
#include <QToolBar>
con's avatar
con committed
106

107 108
//#define DO_FOO

Leandro Melo's avatar
Leandro Melo committed
109 110 111 112 113 114 115 116 117 118 119 120
/*!
    \namespace TextEditor
    \brief The TextEditor namespace contains the base text editor and several classes which
    provide supporting functionality like snippets, highlighting, \l{CodeAssist}{code assist},
    indentation and style, and others.
*/

/*!
    \namespace TextEditor::Internal
    \internal
*/

121 122
/*!
    \class TextEditor::BaseTextEditor
123 124 125 126 127 128
    \brief The BaseTextEditor class is base implementation for QPlainTextEdit-based
    text editors. It can use the Kate text highlighting definitions, and some basic
    auto indentation.

    The corresponding document base class is BaseTextDocument, the corresponding
    widget base class is BaseTextEditorWidget.
129

130 131
    It is the default editor for text files used by \QC, if no other editor
    implementation matches the MIME type.
132 133
*/

hjk's avatar
hjk committed
134

135
using namespace Core;
136
using namespace Utils;
con's avatar
con committed
137 138

namespace TextEditor {
139
namespace Internal {
con's avatar
con committed
140

141 142 143 144 145 146 147 148 149 150 151
typedef QString (TransformationMethod)(const QString &);

static QString QString_toUpper(const QString &str)
{
    return str.toUpper();
}

static QString QString_toLower(const QString &str)
{
    return str.toLower();
}
152

153
class TextEditorAnimator : public QObject
154 155 156 157
{
    Q_OBJECT

public:
158
    TextEditorAnimator(QObject *parent);
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177

    inline void setPosition(int position) { m_position = position; }
    inline int position() const { return m_position; }

    void setData(const QFont &f, const QPalette &pal, const QString &text);

    void draw(QPainter *p, const QPointF &pos);
    QRectF rect() const;

    inline qreal value() const { return m_value; }
    inline QPointF lastDrawPos() const { return m_lastDrawPos; }

    void finish();

    bool isRunning() const;

signals:
    void updateRequest(int position, QPointF lastPos, QRectF rect);

178
private:
179 180
    void step(qreal v);

181
    QTimeLine m_timeline;
182 183 184 185 186 187 188 189 190
    qreal m_value;
    int m_position;
    QPointF m_lastDrawPos;
    QFont m_font;
    QPalette m_palette;
    QString m_text;
    QSizeF m_size;
};

191 192 193
class TextEditExtraArea : public QWidget
{
public:
194
    TextEditExtraArea(TextEditorWidget *edit)
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
        : QWidget(edit)
    {
        textEdit = edit;
        setAutoFillBackground(true);
    }

protected:
    QSize sizeHint() const {
        return QSize(textEdit->extraAreaWidth(), 0);
    }
    void paintEvent(QPaintEvent *event) {
        textEdit->extraAreaPaintEvent(event);
    }
    void mousePressEvent(QMouseEvent *event) {
        textEdit->extraAreaMouseEvent(event);
    }
    void mouseMoveEvent(QMouseEvent *event) {
        textEdit->extraAreaMouseEvent(event);
    }
    void mouseReleaseEvent(QMouseEvent *event) {
        textEdit->extraAreaMouseEvent(event);
    }
    void leaveEvent(QEvent *event) {
        textEdit->extraAreaLeaveEvent(event);
    }
    void contextMenuEvent(QContextMenuEvent *event) {
        textEdit->extraAreaContextMenuEvent(event);
    }

    void wheelEvent(QWheelEvent *event) {
        QCoreApplication::sendEvent(textEdit->viewport(), event);
    }

private:
229
    TextEditorWidget *textEdit;
230 231
};

hjk's avatar
hjk committed
232 233 234
class BaseTextEditorPrivate
{
public:
235
    BaseTextEditorPrivate() {}
236

hjk's avatar
hjk committed
237
    TextEditorFactoryPrivate *m_origin;
hjk's avatar
hjk committed
238 239
};

240
class TextEditorWidgetPrivate : public QObject
241 242
{
public:
243 244
    TextEditorWidgetPrivate(TextEditorWidget *parent);
    ~TextEditorWidgetPrivate()
245 246 247
    {
        delete m_toolBar;
    }
248 249 250 251 252 253

    void setupDocumentSignals();
    void updateLineSelectionColor();

    void print(QPrinter *printer);

254 255 256 257
    void maybeSelectLine();
    void updateCannotDecodeInfo();
    void collectToCircularClipboard();

258
    void ctor(const QSharedPointer<TextDocument> &doc);
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    void handleHomeKey(bool anchor);
    void handleBackspaceKey();
    void moveLineUpDown(bool up);
    void copyLineUpDown(bool up);
    void saveCurrentCursorPositionForNavigation();
    void updateHighlights();
    void updateCurrentLineHighlight();

    void drawFoldingMarker(QPainter *painter, const QPalette &pal,
                           const QRect &rect,
                           bool expanded,
                           bool active,
                           bool hovered) const;

    void toggleBlockVisible(const QTextBlock &block);
    QRect foldBox();

    QTextBlock foldedBlockAt(const QPoint &pos, QRect *box = 0) const;

    void updateLink(QMouseEvent *e);
279
    void showLink(const TextEditorWidget::Link &);
280 281 282 283 284 285 286 287 288 289 290 291 292
    void clearLink();

    void universalHelper(); // test function for development

    bool cursorMoveKeyEvent(QKeyEvent *e);
    bool camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode);
    bool camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode);

    void processTooltipRequest(const QTextCursor &c);

    void transformSelection(Internal::TransformationMethod method);
    void transformBlockSelection(Internal::TransformationMethod method);

293 294 295 296 297 298 299 300 301 302 303 304
    bool inFindScope(const QTextCursor &cursor);
    bool inFindScope(int selectionStart, int selectionEnd);

    void slotUpdateExtraAreaWidth();
    void slotUpdateRequest(const QRect &r, int dy);
    void slotUpdateBlockNotify(const QTextBlock &);
    void updateTabStops();
    void applyFontSettingsDelayed();

    void editorContentsChange(int position, int charsRemoved, int charsAdded);
    void documentAboutToBeReloaded();
    void documentReloadFinished(bool success);
305
    void highlightSearchResultsSlot(const QString &txt, FindFlags findFlags);
306 307
    void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int);

308 309
    void updateCursorPosition();

310 311 312 313 314 315 316
    // parentheses matcher
    void _q_matchParentheses();
    void _q_highlightBlocks();
    void slotSelectionChanged();
    void _q_animateUpdate(int position, QPointF lastPos, QRectF rect);
    void updateCodeFoldingVisible();

317 318
    void reconfigure();

319
public:
320
    TextEditorWidget *q;
321 322
    QToolBar *m_toolBar;
    QWidget *m_stretchWidget;
323 324
    LineColumnLabel *m_cursorPositionLabel;
    LineColumnLabel *m_fileEncodingLabel;
325 326 327
    QAction *m_cursorPositionLabelAction;
    QAction *m_fileEncodingLabelAction;

328 329 330
    bool m_contentsChanged;
    bool m_lastCursorChangeWasInteresting;

331
    QSharedPointer<TextDocument> m_document;
332 333 334 335 336 337 338 339 340 341 342 343 344
    QByteArray m_tempState;
    QByteArray m_tempNavigationState;

    bool m_parenthesesMatchingEnabled;

    // parentheses matcher
    bool m_formatRange;
    QTextCharFormat m_mismatchFormat;
    QTimer m_parenthesesMatchingTimer;
    // end parentheses matcher

    QWidget *m_extraArea;

345
    Id m_tabSettingsId;
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
    ICodeStylePreferences *m_codeStylePreferences;
    DisplaySettings m_displaySettings;
    MarginSettings m_marginSettings;
    bool m_fontSettingsNeedsApply;
    BehaviorSettings m_behaviorSettings;

    int extraAreaSelectionAnchorBlockNumber;
    int extraAreaToggleMarkBlockNumber;
    int extraAreaHighlightFoldedBlockNumber;

    TextEditorOverlay *m_overlay;
    TextEditorOverlay *m_snippetOverlay;
    TextEditorOverlay *m_searchResultOverlay;
    bool snippetCheckCursor(const QTextCursor &cursor);
    void snippetTabOrBacktab(bool forward);

    RefactorOverlay *m_refactorOverlay;
363
    QString m_contextHelpId;
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

    QBasicTimer foldedBlockTimer;
    int visibleFoldedBlockNumber;
    int suggestedVisibleFoldedBlockNumber;
    void clearVisibleFoldedBlock();
    bool m_mouseOnFoldedMarker;
    void foldLicenseHeader();

    QBasicTimer autoScrollTimer;
    uint m_marksVisible : 1;
    uint m_codeFoldingVisible : 1;
    uint m_codeFoldingSupported : 1;
    uint m_revisionsVisible : 1;
    uint m_lineNumbersVisible : 1;
    uint m_highlightCurrentLine : 1;
    uint m_requestMarkEnabled : 1;
    uint m_lineSeparatorsAllowed : 1;
    uint autoParenthesisOverwriteBackup : 1;
    uint surroundWithEnabledOverwriteBackup : 1;
    uint m_maybeFakeTooltipEvent : 1;
    int m_visibleWrapColumn;

386
    TextEditorWidget::Link m_currentLink;
387 388 389
    bool m_linkPressed;

    QRegExp m_searchExpr;
390
    FindFlags m_findFlags;
391 392 393
    void highlightSearchResults(const QTextBlock &block, TextEditorOverlay *overlay);
    QTimer m_delayedUpdateTimer;

394 395
    void setExtraSelections(int kind, const QList<QTextEdit::ExtraSelection> &selections);
    QHash<int, QList<QTextEdit::ExtraSelection>> m_extraSelections;
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419

    // block selection mode
    bool m_inBlockSelectionMode;
    QString copyBlockSelection();
    void insertIntoBlockSelection(const QString &text = QString());
    void setCursorToColumn(QTextCursor &cursor, int column,
                          QTextCursor::MoveMode moveMode = QTextCursor::MoveAnchor);
    void removeBlockSelection();
    void enableBlockSelection(const QTextCursor &cursor);
    void enableBlockSelection(int positionBlock, int positionColumn,
                              int anchorBlock, int anchorColumn);
    void disableBlockSelection(bool keepSelection = true);
    void resetCursorFlashTimer();
    QBasicTimer m_cursorFlashTimer;
    bool m_cursorVisible;
    bool m_moveLineUndoHack;

    QTextCursor m_findScopeStart;
    QTextCursor m_findScopeEnd;
    int m_findScopeVerticalBlockSelectionFirstColumn;
    int m_findScopeVerticalBlockSelectionLastColumn;

    QTextCursor m_selectBlockAnchor;

420
    Internal::TextBlockSelection m_blockSelection;
421 422 423 424

    void moveCursorVisible(bool ensureVisible = true);

    int visualIndent(const QTextBlock &block) const;
425
    TextEditorPrivateHighlightBlocks m_highlightBlocksInfo;
426 427
    QTimer m_highlightBlocksTimer;

428
    CodeAssistant m_codeAssistant;
429
    bool m_assistRelevantContentAdded;
430
    QList<BaseHoverHandler *> m_hoverHandlers; // Not owned
431

432
    QPointer<TextEditorAnimator> m_animator;
433 434 435 436 437 438 439
    int m_cursorBlockNumber;
    int m_blockCount;

    QPoint m_markDragStart;
    bool m_markDragging;

    QScopedPointer<Internal::ClipboardAssistProvider> m_clipboardAssistProvider;
440 441

    bool m_isMissingSyntaxDefinition;
442 443

    QScopedPointer<AutoCompleter> m_autoCompleter;
444
    CommentDefinition m_commentDefinition;
445 446
};

447
TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent)
448
  : q(parent),
449 450 451 452 453 454
    m_toolBar(0),
    m_stretchWidget(0),
    m_cursorPositionLabel(0),
    m_fileEncodingLabel(0),
    m_cursorPositionLabelAction(0),
    m_fileEncodingLabelAction(0),
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
    m_contentsChanged(false),
    m_lastCursorChangeWasInteresting(false),
    m_parenthesesMatchingEnabled(false),
    m_formatRange(false),
    m_parenthesesMatchingTimer(0),
    m_extraArea(0),
    m_codeStylePreferences(0),
    m_fontSettingsNeedsApply(true), // apply when making visible the first time, for the split case
    extraAreaSelectionAnchorBlockNumber(-1),
    extraAreaToggleMarkBlockNumber(-1),
    extraAreaHighlightFoldedBlockNumber(-1),
    m_overlay(0),
    m_snippetOverlay(0),
    m_searchResultOverlay(0),
    m_refactorOverlay(0),
    visibleFoldedBlockNumber(-1),
    suggestedVisibleFoldedBlockNumber(-1),
    m_mouseOnFoldedMarker(false),
    m_marksVisible(false),
    m_codeFoldingVisible(false),
    m_codeFoldingSupported(false),
    m_revisionsVisible(false),
    m_lineNumbersVisible(true),
    m_highlightCurrentLine(true),
    m_requestMarkEnabled(true),
    m_lineSeparatorsAllowed(false),
    m_maybeFakeTooltipEvent(false),
    m_visibleWrapColumn(0),
    m_linkPressed(false),
    m_delayedUpdateTimer(0),
    m_inBlockSelectionMode(false),
    m_moveLineUndoHack(false),
    m_findScopeVerticalBlockSelectionFirstColumn(-1),
    m_findScopeVerticalBlockSelectionLastColumn(-1),
    m_highlightBlocksTimer(0),
    m_assistRelevantContentAdded(false),
    m_cursorBlockNumber(-1),
    m_blockCount(0),
    m_markDragging(false),
    m_clipboardAssistProvider(new Internal::ClipboardAssistProvider),
495
    m_isMissingSyntaxDefinition(false),
496
    m_autoCompleter(new AutoCompleter)
497 498 499
{
    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
    BaseTextFind *baseTextFind = new BaseTextFind(q);
hjk's avatar
hjk committed
500
    connect(baseTextFind, &BaseTextFind::highlightAllRequested,
501
            this, &TextEditorWidgetPrivate::highlightSearchResultsSlot);
502
    connect(baseTextFind, &BaseTextFind::findScopeChanged,
503
            this, &TextEditorWidgetPrivate::setFindScope);
504 505
    aggregate->add(baseTextFind);
    aggregate->add(q);
506

507 508 509
    m_extraArea = new TextEditExtraArea(q);
    m_extraArea->setMouseTracking(true);

510 511 512 513 514 515
    m_stretchWidget = new QWidget;
    m_stretchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    m_toolBar = new QToolBar;
    m_toolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
    m_toolBar->addWidget(m_stretchWidget);

516
    m_cursorPositionLabel = new LineColumnLabel;
517 518 519
    const int spacing = q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2;
    m_cursorPositionLabel->setContentsMargins(spacing, 0, spacing, 0);

520
    m_fileEncodingLabel = new LineColumnLabel;
521 522 523 524
    m_fileEncodingLabel->setContentsMargins(spacing, 0, spacing, 0);

    m_cursorPositionLabelAction = m_toolBar->addWidget(m_cursorPositionLabel);
    m_fileEncodingLabelAction = m_toolBar->addWidget(m_fileEncodingLabel);
525 526

    m_extraSelections.reserve(TextEditorWidget::NExtraSelectionKinds);
527
}
528

529
} // namespace Internal
hjk's avatar
hjk committed
530 531

using namespace Internal;
con's avatar
con committed
532

533 534 535 536
/*!
 * Test if syntax highlighter is available (or unneeded) for \a widget.
 * If not found, show a warning with a link to the relevant settings page.
 */
537
static void updateEditorInfoBar(TextEditorWidget *widget)
538 539 540 541 542 543 544 545 546 547
{
    Id infoSyntaxDefinition(Constants::INFO_SYNTAX_DEFINITION);
    InfoBar *infoBar = widget->textDocument()->infoBar();
    if (!widget->isMissingSyntaxDefinition()) {
        infoBar->removeInfo(infoSyntaxDefinition);
    } else if (infoBar->canInfoBeAdded(infoSyntaxDefinition)) {
        InfoBarEntry info(infoSyntaxDefinition,
                          BaseTextEditor::tr("A highlight definition was not found for this file. "
                                             "Would you like to try to find one?"),
                          InfoBarEntry::GlobalSuppressionEnabled);
548 549 550 551 552 553
        info.setCustomButtonInfo(BaseTextEditor::tr("Show Highlighter Options..."), [widget]() {
            ICore::showOptionsDialog(Constants::TEXT_EDITOR_SETTINGS_CATEGORY,
                                     Constants::TEXT_EDITOR_HIGHLIGHTER_SETTINGS,
                                     widget);
        });

554 555 556 557
        infoBar->addInfo(info);
    }
}

558
QString TextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
559
{
560 561 562 563 564
    // Copy the selected text as plain text
    QString text = cursor.selectedText();
    return convertToPlainText(text);
}

565
QString TextEditorWidget::convertToPlainText(const QString &txt)
566 567 568 569
{
    QString ret = txt;
    QChar *uc = ret.data();
    QChar *e = uc + ret.size();
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585

    for (; uc != e; ++uc) {
        switch (uc->unicode()) {
        case 0xfdd0: // QTextBeginningOfFrame
        case 0xfdd1: // QTextEndOfFrame
        case QChar::ParagraphSeparator:
        case QChar::LineSeparator:
            *uc = QLatin1Char('\n');
            break;
        case QChar::Nbsp:
            *uc = QLatin1Char(' ');
            break;
        default:
            ;
        }
    }
586
    return ret;
587 588
}

Orgad Shaneh's avatar
Orgad Shaneh committed
589
static const char kTextBlockMimeType[] = "application/vnd.qtcreator.blocktext";
590

591
TextEditorWidget::TextEditorWidget(QWidget *parent)
592 593
    : QPlainTextEdit(parent)
{
594 595 596
    // "Needed", as the creation below triggers ChildEvents that are
    // passed to this object's event() which uses 'd'.
    d = 0;
597
    d = new TextEditorWidgetPrivate(this);
598 599
}

600
void TextEditorWidget::setTextDocument(const QSharedPointer<TextDocument> &doc)
601
{
602
    d->ctor(doc);
603 604
}

605
void TextEditorWidgetPrivate::ctor(const QSharedPointer<TextDocument> &doc)
con's avatar
con committed
606
{
607
    q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
con's avatar
con committed
608

609 610 611 612
    m_overlay = new TextEditorOverlay(q);
    m_snippetOverlay = new TextEditorOverlay(q);
    m_searchResultOverlay = new TextEditorOverlay(q);
    m_refactorOverlay = new RefactorOverlay(q);
613

614 615
    m_document = doc;
    setupDocumentSignals();
con's avatar
con committed
616 617 618

    // from RESEARCH

619 620
    q->setLayoutDirection(Qt::LeftToRight);
    q->viewport()->setMouseTracking(true);
con's avatar
con committed
621

622 623 624 625 626
    extraAreaSelectionAnchorBlockNumber = -1;
    extraAreaToggleMarkBlockNumber = -1;
    extraAreaHighlightFoldedBlockNumber = -1;
    visibleFoldedBlockNumber = -1;
    suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
627

628
    QObject::connect(&m_codeAssistant, &CodeAssistant::finished,
629
                     q, &TextEditorWidget::assistFinished);
630

631
    QObject::connect(q, &QPlainTextEdit::blockCountChanged,
632
                     this, &TextEditorWidgetPrivate::slotUpdateExtraAreaWidth);
633

634
    QObject::connect(q, &QPlainTextEdit::modificationChanged, m_extraArea,
635 636 637
                     static_cast<void (QWidget::*)()>(&QWidget::update));

    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
638
                     q, &TextEditorWidget::slotCursorPositionChanged);
639

640
    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
641
                     this, &TextEditorWidgetPrivate::updateCursorPosition);
642

643
    QObject::connect(q, &QPlainTextEdit::updateRequest,
644
                     this, &TextEditorWidgetPrivate::slotUpdateRequest);
645 646

    QObject::connect(q, &QPlainTextEdit::selectionChanged,
647
                     this, &TextEditorWidgetPrivate::slotSelectionChanged);
con's avatar
con committed
648 649 650 651 652

//     (void) new QShortcut(tr("CTRL+L"), this, SLOT(centerCursor()), 0, Qt::WidgetShortcut);
//     (void) new QShortcut(tr("F9"), this, SLOT(slotToggleMark()), 0, Qt::WidgetShortcut);
//     (void) new QShortcut(tr("F11"), this, SLOT(slotToggleBlockVisible()));

653 654 655 656
#ifdef DO_FOO
    (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo()));
#endif

con's avatar
con committed
657
    // parentheses matcher
658 659
    m_formatRange = true;
    m_mismatchFormat.setBackground(q->palette().color(QPalette::Base).value() < 128
660
                                      ? Qt::darkMagenta : Qt::magenta);
661
    m_parenthesesMatchingTimer.setSingleShot(true);
662
    QObject::connect(&m_parenthesesMatchingTimer, &QTimer::timeout,
663
                     this, &TextEditorWidgetPrivate::_q_matchParentheses);
con's avatar
con committed
664

665
    m_highlightBlocksTimer.setSingleShot(true);
666
    QObject::connect(&m_highlightBlocksTimer, &QTimer::timeout,
667
                     this, &TextEditorWidgetPrivate::_q_highlightBlocks);
668

669
    m_animator = 0;
con's avatar
con committed
670

671
    slotUpdateExtraAreaWidth();
con's avatar
con committed
672
    updateHighlights();
673
    q->setFrameStyle(QFrame::NoFrame);
con's avatar
con committed
674

675
    m_delayedUpdateTimer.setSingleShot(true);
676 677
    QObject::connect(&m_delayedUpdateTimer, &QTimer::timeout, q->viewport(),
                     static_cast<void (QWidget::*)()>(&QWidget::update));
678

679
    m_moveLineUndoHack = false;
680 681 682 683 684 685 686 687

    updateCannotDecodeInfo();

    connect(m_fileEncodingLabel, &LineColumnLabel::clicked,
            q, &TextEditorWidget::selectEncoding);
    connect(m_document->document(), &QTextDocument::modificationChanged,
            q, &TextEditorWidget::updateTextCodecLabel);
    q->updateTextCodecLabel();
con's avatar
con committed
688 689
}

690
TextEditorWidget::~TextEditorWidget()
con's avatar
con committed
691 692 693 694 695
{
    delete d;
    d = 0;
}

696
void TextEditorWidget::print(QPrinter *printer)
con's avatar
con committed
697
{
hjk's avatar
hjk committed
698
    const bool oldFullPage = printer->fullPage();
con's avatar
con committed
699 700 701
    printer->setFullPage(true);
    QPrintDialog *dlg = new QPrintDialog(printer, this);
    dlg->setWindowTitle(tr("Print Document"));
702
    if (dlg->exec() == QDialog::Accepted)
con's avatar
con committed
703 704 705 706 707
        d->print(printer);
    printer->setFullPage(oldFullPage);
    delete dlg;
}

mae's avatar
mae committed
708
static int foldBoxWidth(const QFontMetrics &fm)
709 710
{
    const int lineSpacing = fm.lineSpacing();
hjk's avatar
hjk committed
711
    return lineSpacing + lineSpacing % 2 + 1;
712 713
}

con's avatar
con committed
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
static void printPage(int index, QPainter *painter, const QTextDocument *doc,
                      const QRectF &body, const QRectF &titleBox,
                      const QString &title)
{
    painter->save();

    painter->translate(body.left(), body.top() - (index - 1) * body.height());
    QRectF view(0, (index - 1) * body.height(), body.width(), body.height());

    QAbstractTextDocumentLayout *layout = doc->documentLayout();
    QAbstractTextDocumentLayout::PaintContext ctx;

    painter->setFont(QFont(doc->defaultFont()));
    QRectF box = titleBox.translated(0, view.top());
    int dpix = painter->device()->logicalDpiX();
    int dpiy = painter->device()->logicalDpiY();
    int mx = 5 * dpix / 72.0;
    int my = 2 * dpiy / 72.0;
    painter->fillRect(box.adjusted(-mx, -my, mx, my), QColor(210, 210, 210));
    if (!title.isEmpty())
        painter->drawText(box, Qt::AlignCenter, title);
    const QString pageString = QString::number(index);
    painter->drawText(box, Qt::AlignRight, pageString);

    painter->setClipRect(view);
    ctx.clip = view;
    // don't use the system palette text as default text color, on HP/UX
    // for example that's white, and white text on white paper doesn't
    // look that nice
    ctx.palette.setColor(QPalette::Text, Qt::black);

    layout->draw(painter, ctx);

    painter->restore();
}

750
void TextEditorWidgetPrivate::print(QPrinter *printer)
con's avatar
con committed
751 752 753
{
    QTextDocument *doc = q->document();

754
    QString title = m_document->displayName();
755
    if (!title.isEmpty())
con's avatar
con committed
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784
        printer->setDocName(title);


    QPainter p(printer);

    // Check that there is a valid device to print to.
    if (!p.isActive())
        return;

    doc = doc->clone(doc);

    QTextOption opt = doc->defaultTextOption();
    opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    doc->setDefaultTextOption(opt);

    (void)doc->documentLayout(); // make sure that there is a layout


    QColor background = q->palette().color(QPalette::Base);
    bool backgroundIsDark = background.value() < 128;

    for (QTextBlock srcBlock = q->document()->firstBlock(), dstBlock = doc->firstBlock();
         srcBlock.isValid() && dstBlock.isValid();
         srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {


        QList<QTextLayout::FormatRange> formatList = srcBlock.layout()->additionalFormats();
        if (backgroundIsDark) {
            // adjust syntax highlighting colors for better contrast
hjk's avatar
hjk committed
785
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
                QTextCharFormat &format = formatList[i].format;
                if (format.background().color() == background) {
                    QBrush brush = format.foreground();
                    QColor color = brush.color();
                    int h,s,v,a;
                    color.getHsv(&h, &s, &v, &a);
                    color.setHsv(h, s, qMin(128, v), a);
                    brush.setColor(color);
                    format.setForeground(brush);
                }
                format.setBackground(Qt::white);
            }
        }

        dstBlock.layout()->setAdditionalFormats(formatList);
    }

    QAbstractTextDocumentLayout *layout = doc->documentLayout();
    layout->setPaintDevice(p.device());

    int dpiy = p.device()->logicalDpiY();
    int margin = (int) ((2/2.54)*dpiy); // 2 cm margins

    QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
    fmt.setMargin(margin);
    doc->rootFrame()->setFrameFormat(fmt);

    QRectF pageRect(printer->pageRect());
    QRectF body = QRectF(0, 0, pageRect.width(), pageRect.height());
    QFontMetrics fontMetrics(doc->defaultFont(), p.device());

    QRectF titleBox(margin,
                    body.top() + margin
                    - fontMetrics.height()
                    - 6 * dpiy / 72.0,
                    body.width() - 2*margin,
                    fontMetrics.height());
    doc->setPageSize(body.size());

    int docCopies;
    int pageCopies;
hjk's avatar
hjk committed
827
    if (printer->collateCopies() == true) {
con's avatar
con committed
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
        docCopies = 1;
        pageCopies = printer->numCopies();
    } else {
        docCopies = printer->numCopies();
        pageCopies = 1;
    }

    int fromPage = printer->fromPage();
    int toPage = printer->toPage();
    bool ascending = true;

    if (fromPage == 0 && toPage == 0) {
        fromPage = 1;
        toPage = doc->pageCount();
    }
    // paranoia check
    fromPage = qMax(1, fromPage);
    toPage = qMin(doc->pageCount(), toPage);

    if (printer->pageOrder() == QPrinter::LastPageFirst) {
        int tmp = fromPage;
        fromPage = toPage;
        toPage = tmp;
        ascending = false;
    }

    for (int i = 0; i < docCopies; ++i) {

        int page = fromPage;
        while (true) {
            for (int j = 0; j < pageCopies; ++j) {
                if (printer->printerState() == QPrinter::Aborted
                    || printer->printerState() == QPrinter::Error)
                    goto UserCanceled;
                printPage(page, &p, doc, body, titleBox, title);
                if (j < pageCopies - 1)
                    printer->newPage();
            }

            if (page == toPage)
                break;

            if (ascending)
                ++page;
            else
                --page;

            printer->newPage();
        }

        if ( i < docCopies - 1)
            printer->newPage();
    }

UserCanceled:
    delete doc;
}


887
int TextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903
{
    if (!block.isValid())
        return 0;
    const QTextDocument *document = block.document();
    int i = 0;
    while (i < block.length()) {
        if (!document->characterAt(block.position() + i).isSpace()) {
            QTextCursor cursor(block);
            cursor.setPosition(block.position() + i);
            return q->cursorRect(cursor).x();
        }
        ++i;
    }

    return 0;
}
con's avatar
con committed
904

905
void TextEditorWidget::selectEncoding()
con's avatar
con committed
906
{
907
    TextDocument *doc = d->m_document.data();
con's avatar
con committed
908 909 910
    CodecSelector codecSelector(this, doc);

    switch (codecSelector.exec()) {
911 912 913 914 915 916 917
    case CodecSelector::Reload: {
        QString errorString;
        if (!doc->reload(&errorString, codecSelector.selectedCodec())) {
            QMessageBox::critical(this, tr("File Error"), errorString);
            break;
        }
        break; }
con's avatar
con committed
918 919
    case CodecSelector::Save:
        doc->setCodec(codecSelector.selectedCodec());
920
        EditorManager::saveDocument(textDocument());
921
        updateTextCodecLabel();
con's avatar
con committed
922 923 924 925 926 927
        break;
    case CodecSelector::Cancel:
        break;
    }
}

928
void TextEditorWidget::updateTextCodecLabel()
929
{
930 931
    QString text = QString::fromLatin1(d->m_document->codec()->name());
    d->m_fileEncodingLabel->setText(text, text);
932 933
}

934
QString TextEditorWidget::msgTextTooLarge(quint64 size)
935 936 937 938 939
{
    return tr("The text is too large to be displayed (%1 MB).").
           arg(size >> 20);
}

940
void TextEditorWidget::insertPlainText(const QString &text)
David Schulz's avatar
David Schulz committed
941 942 943 944 945 946 947
{
    if (d->m_inBlockSelectionMode)
        d->insertIntoBlockSelection(text);
    else
        QPlainTextEdit::insertPlainText(text);
}

948
QString TextEditorWidget::selectedText() const
David Schulz's avatar
David Schulz committed
949 950 951 952 953 954 955
{
    if (d->m_inBlockSelectionMode)
        return d->copyBlockSelection();
    else
        return textCursor().selectedText();
}

956
void TextEditorWidgetPrivate::updateCannotDecodeInfo()
957
{
958
    q->setReadOnly(m_document->hasDecodingError());
959 960
    InfoBar *infoBar = m_document->infoBar();
    Id selectEncodingId(Constants::SELECT_ENCODING);
961
    if (m_document->hasDecodingError()) {
962 963
        if (!infoBar->canInfoBeAdded(selectEncodingId))
            return;
964
        InfoBarEntry info(selectEncodingId,
965
            TextEditorWidget::tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.")
966
            .arg(m_document->displayName()).arg(QString::fromLatin1(m_document->codec()->name())));
967
        info.setCustomButtonInfo(TextEditorWidget::tr("Select Encoding"), [this]() { q->selectEncoding(); });
968
        infoBar->addInfo(info);
969
    } else {
970
        infoBar->removeInfo(selectEncodingId);
971 972 973
    }
}

974
bool TextEditorWidget::open(QString *errorString, const QString &fileName, const QString &realFileName)
con's avatar
con committed
975
{
976
    if (d->m_document->open(errorString, fileName, realFileName)) {
con's avatar
con committed
977
        moveCursor(QTextCursor::Start);
978
        d->updateCannotDecodeInfo();
979
        updateTextCodecLabel();
con's avatar
con committed
980 981 982 983 984
        return true;
    }
    return false;
}

985 986 987
/*
  Collapses the first comment in a file, if there is only whitespace above
  */
988
void TextEditorWidgetPrivate::foldLicenseHeader()
989 990
{
    QTextDocument *doc = q->document();
991
    TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
992 993
    QTC_ASSERT(documentLayout, return);
    QTextBlock block = doc->firstBlock();
994
    while (block.isValid() && block.isVisible()) {
995
        QString text = block.text();
996
        if (TextDocumentLayout::canFold(block) && block.next().isVisible()) {
mae's avatar
mae committed
997
            if (text.trimmed().startsWith(QLatin1String("/*"))) {
998
                TextDocumentLayout::doFoldOrUnfold(block, false);
mae's avatar
mae committed
999 1000 1001 1002 1003 1004
                moveCursorVisible();
                documentLayout->requestUpdate();
                documentLayout->emitDocumentSizeChanged();
                break;
            }
        }
1005
        if (TabSettings::firstNonSpace(text) < text.size())
1006 1007 1008 1009 1010
            break;
        block = block.next();
    }
}

1011
TextDocument *TextEditorWidget::textDocument() const
con's avatar
con committed
1012
{
1013
    return d->m_document.data();
con's avatar
con committed
1014 1015
}

1016
TextDocumentPtr TextEditorWidget::textDocumentPtr() const
1017 1018 1019 1020
{
    return d->m_document;
}

1021
void TextEditorWidgetPrivate::editorContentsChange(int position, int charsRemoved, int charsAdded)
con's avatar
con committed
1022
{
1023 1024
    if (m_animator)
        m_animator->finish();
1025

1026 1027
    m_contentsChanged = true;
    QTextDocument *doc = q->document();
1028
    TextDocumentLayout *documentLayout = static_cast<TextDocumentLayout*>(doc->documentLayout());
1029
    const QTextBlock posBlock = doc->findBlock(position);
con's avatar
con committed
1030 1031 1032

    // Keep the line numbers and the block information for the text marks updated
    if (charsRemoved != 0) {
1033
        documentLayout->updateMarksLineNumber();
1034
        documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1035
    } else {
1036
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
con's avatar
con committed
1037
        if (posBlock != nextBlock) {
1038 1039 1040
            documentLayout->updateMarksLineNumber();
            documentLayout->updateMarksBlock(posBlock);
            documentLayout->updateMarksBlock(nextBlock);
con's avatar
con committed
1041
        } else {
1042
            documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1043 1044
        }
    }
1045

1046 1047
    if (m_snippetOverlay->isVisible()) {
        QTextCursor cursor = q->textCursor();
1048
        cursor.setPosition(position);
1049
        snippetCheckCursor(cursor);
1050 1051
    }

1052 1053
    if (charsAdded != 0 && q->document()->characterAt(position + charsAdded - 1).isPrint())
        m_assistRelevantContentAdded = true;
1054 1055

    int newBlockCount = doc->blockCount();
1056
    if (!q->hasFocus() && newBlockCount != m_blockCount) {
1057
        // lines were inserted or removed from outside, keep viewport on same part of text
1058 1059
        if (q->firstVisibleBlock().blockNumber() > posBlock.blockNumber())
            q->verticalScrollBar()->setValue(q->verticalScrollBar()->value() + newBlockCount - m_blockCount);
1060
    }
1061
    m_blockCount = newBlockCount;
con's avatar
con committed
1062 1063
}

1064
void TextEditorWidgetPrivate::slotSelectionChanged()
con's avatar
con committed
1065
{
1066 1067
    if (!q->textCursor().hasSelection() && !m_selectBlockAnchor.isNull())
        m_selectBlockAnchor = QTextCursor();
1068
    // Clear any link which might be showing when the selection changes
1069
    clearLink();
con's avatar
con committed
1070 1071
}

1072
void TextEditorWidget::gotoBlockStart()
1073 1074
{
    QTextCursor cursor = textCursor();
1075
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
1076
        setTextCursor(cursor);
1077
        d->_q_matchParentheses();
1078
    }
1079 1080
}

1081
void TextEditorWidget::gotoBlockEnd()
1082 1083
{
    QTextCursor cursor = textCursor();
1084
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
1085
        setTextCursor(cursor);
1086
        d->_q_matchParentheses();
1087
    }