basetexteditor.cpp 248 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 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

30 31
#include "basetexteditor.h"
#include "basetexteditor_p.h"
hjk's avatar
hjk committed
32

con's avatar
con committed
33
#include "basetextdocument.h"
34
#include "basetextdocumentlayout.h"
35
#include "behaviorsettings.h"
con's avatar
con committed
36
#include "codecselector.h"
37
#include "completionsettings.h"
38
#include "snippets/snippet.h"
39
#include "tabsettings.h"
Jarek Kobus's avatar
Jarek Kobus committed
40 41
#include "typingsettings.h"
#include "icodestylepreferences.h"
42
#include "syntaxhighlighter.h"
43
#include "indenter.h"
44
#include "autocompleter.h"
Leandro Melo's avatar
Leandro Melo committed
45
#include "convenience.h"
46
#include "texteditorsettings.h"
47
#include "texteditoroverlay.h"
48
#include "circularclipboard.h"
49
#include "circularclipboardassist.h"
50
#include "highlighterutils.h"
51
#include "basetexteditor.h"
52 53
#include <texteditor/codeassist/codeassistant.h>
#include <texteditor/codeassist/defaultassistinterface.h>
54 55 56 57 58
#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
59

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

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

102 103
//#define DO_FOO

Leandro Melo's avatar
Leandro Melo committed
104 105 106 107 108 109 110 111 112 113 114 115
/*!
    \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
*/

116 117
/*!
    \class TextEditor::BaseTextEditor
118 119 120 121 122 123
    \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.
124

125 126
    It is the default editor for text files used by \QC, if no other editor
    implementation matches the MIME type.
127 128
*/

hjk's avatar
hjk committed
129

130
using namespace Core;
131
using namespace Utils;
con's avatar
con committed
132 133

namespace TextEditor {
134
namespace Internal {
con's avatar
con committed
135

136 137 138 139 140 141 142 143 144 145 146
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();
}
147

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
class BaseTextEditorAnimator : public QObject
{
    Q_OBJECT

public:
    BaseTextEditorAnimator(QObject *parent);

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

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

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

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

    void finish();

    bool isRunning() const;

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


private slots:
    void step(qreal v);

private:
    QTimeLine *m_timeline;
    qreal m_value;
    int m_position;
    QPointF m_lastDrawPos;
    QFont m_font;
    QPalette m_palette;
    QString m_text;
    QSizeF m_size;
};

hjk's avatar
hjk committed
188 189 190
class BaseTextEditorPrivate
{
public:
191 192 193
    BaseTextEditorPrivate()
    {}

194 195 196
    BaseTextEditor::EditorCreator m_editorCreator;
    BaseTextEditor::DocumentCreator m_documentCreator;
    BaseTextEditor::WidgetCreator m_widgetCreator;
hjk's avatar
hjk committed
197

198
    CommentDefinition m_commentDefinition;
199
    std::function<CompletionAssistProvider *()> m_completionAssistProvider;
hjk's avatar
hjk committed
200 201
};

202
class BaseTextEditorWidgetPrivate : public QObject
203 204
{
public:
205
    BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent);
206
    ~BaseTextEditorWidgetPrivate() { delete m_toolBar; }
207 208 209 210 211 212

    void setupDocumentSignals();
    void updateLineSelectionColor();

    void print(QPrinter *printer);

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    void maybeSelectLine();
    void updateCannotDecodeInfo();
    void collectToCircularClipboard();

    void ctor(const QSharedPointer<BaseTextDocument> &doc);
    void handleHomeKey(bool anchor);
    void handleBackspaceKey();
    void moveLineUpDown(bool up);
    void copyLineUpDown(bool up);
    void saveCurrentCursorPositionForNavigation();
    void updateHighlights();
    void updateCurrentLineHighlight();

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

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

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

    void updateLink(QMouseEvent *e);
    void showLink(const BaseTextEditorWidget::Link &);
    void clearLink();

    void universalHelper(); // test function for development

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

    void processTooltipRequest(const QTextCursor &c);

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

252 253 254 255 256 257 258 259 260 261 262 263
    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);
264
    void highlightSearchResultsSlot(const QString &txt, FindFlags findFlags);
265 266
    void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int);

267 268
    void updateCursorPosition();

269 270 271 272 273 274 275 276
    // parentheses matcher
    void _q_matchParentheses();
    void _q_highlightBlocks();
    void slotSelectionChanged();
    void _q_animateUpdate(int position, QPointF lastPos, QRectF rect);
    void updateCodeFoldingVisible();

public:
277
    BaseTextEditorWidget *q;
278 279
    QToolBar *m_toolBar;
    QWidget *m_stretchWidget;
280 281
    LineColumnLabel *m_cursorPositionLabel;
    LineColumnLabel *m_fileEncodingLabel;
282 283 284
    QAction *m_cursorPositionLabelAction;
    QAction *m_fileEncodingLabelAction;

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    bool m_contentsChanged;
    bool m_lastCursorChangeWasInteresting;

    QSharedPointer<BaseTextDocument> m_document;
    QByteArray m_tempState;
    QByteArray m_tempNavigationState;

    bool m_parenthesesMatchingEnabled;

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

    QWidget *m_extraArea;

302
    Id m_tabSettingsId;
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
    ICodeStylePreferences *m_codeStylePreferences;
    DisplaySettings m_displaySettings;
    MarginSettings m_marginSettings;
    bool m_fontSettingsNeedsApply;
    BehaviorSettings m_behaviorSettings;

    int extraAreaSelectionAnchorBlockNumber;
    int extraAreaToggleMarkBlockNumber;
    int extraAreaHighlightFoldedBlockNumber;

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

    RefactorOverlay *m_refactorOverlay;

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

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

    BaseTextEditorWidget::Link m_currentLink;
    bool m_linkPressed;

    QRegExp m_searchExpr;
346
    FindFlags m_findFlags;
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
    void highlightSearchResults(const QTextBlock &block, TextEditorOverlay *overlay);
    QTimer m_delayedUpdateTimer;

    BaseTextEditor *m_editor;

    QList<QTextEdit::ExtraSelection> m_extraSelections[BaseTextEditorWidget::NExtraSelectionKinds];

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

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

    QTextCursor m_selectBlockAnchor;

    Internal::BaseTextBlockSelection m_blockSelection;

    void moveCursorVisible(bool ensureVisible = true);

    int visualIndent(const QTextBlock &block) const;
    BaseTextEditorPrivateHighlightBlocks m_highlightBlocksInfo;
    QTimer m_highlightBlocksTimer;

385
    CodeAssistant m_codeAssistant;
386 387 388 389 390 391 392 393 394 395
    bool m_assistRelevantContentAdded;

    QPointer<BaseTextEditorAnimator> m_animator;
    int m_cursorBlockNumber;
    int m_blockCount;

    QPoint m_markDragStart;
    bool m_markDragging;

    QScopedPointer<Internal::ClipboardAssistProvider> m_clipboardAssistProvider;
396 397

    bool m_isMissingSyntaxDefinition;
398 399

    QScopedPointer<AutoCompleter> m_autoCompleter;
400 401
};

402 403
BaseTextEditorWidgetPrivate::BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent)
  : q(parent),
404 405 406 407 408 409
    m_toolBar(0),
    m_stretchWidget(0),
    m_cursorPositionLabel(0),
    m_fileEncodingLabel(0),
    m_cursorPositionLabelAction(0),
    m_fileEncodingLabelAction(0),
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
    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_editor(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),
451 452
    m_isMissingSyntaxDefinition(false),
    m_autoCompleter(new AutoCompleter)
453 454 455 456 457 458 459 460 461
{
    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
    BaseTextFind *baseTextFind = new BaseTextFind(q);
    connect(baseTextFind, &BaseTextFind::highlightAll,
            this, &BaseTextEditorWidgetPrivate::highlightSearchResultsSlot);
    connect(baseTextFind, &BaseTextFind::findScopeChanged,
            this, &BaseTextEditorWidgetPrivate::setFindScope);
    aggregate->add(baseTextFind);
    aggregate->add(q);
462 463 464 465 466 467 468

    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);

469
    m_cursorPositionLabel = new LineColumnLabel;
470 471 472
    const int spacing = q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2;
    m_cursorPositionLabel->setContentsMargins(spacing, 0, spacing, 0);

473
    m_fileEncodingLabel = new LineColumnLabel;
474 475 476 477 478 479
    m_fileEncodingLabel->setContentsMargins(spacing, 0, spacing, 0);

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

    connect(m_cursorPositionLabel, &LineColumnLabel::clicked, [this] {
480 481
        EditorManager::activateEditor(q->editor(), EditorManager::IgnoreNavigationHistory);
        if (Core::Command *cmd = ActionManager::command(Core::Constants::GOTO)) {
482 483 484 485
            if (QAction *act = cmd->action())
                act->trigger();
        }
    });
486
}
487

hjk's avatar
hjk committed
488 489
class TextEditExtraArea : public QWidget
{
con's avatar
con committed
490
public:
hjk's avatar
hjk committed
491 492 493
    TextEditExtraArea(BaseTextEditorWidget *edit)
        : QWidget(edit)
    {
con's avatar
con committed
494 495 496 497
        textEdit = edit;
        setAutoFillBackground(true);
    }

hjk's avatar
hjk committed
498
protected:
con's avatar
con committed
499 500 501
    QSize sizeHint() const {
        return QSize(textEdit->extraAreaWidth(), 0);
    }
hjk's avatar
hjk committed
502
    void paintEvent(QPaintEvent *event) {
con's avatar
con committed
503 504
        textEdit->extraAreaPaintEvent(event);
    }
hjk's avatar
hjk committed
505
    void mousePressEvent(QMouseEvent *event) {
con's avatar
con committed
506 507
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
508
    void mouseMoveEvent(QMouseEvent *event) {
con's avatar
con committed
509 510
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
511
    void mouseReleaseEvent(QMouseEvent *event) {
con's avatar
con committed
512 513
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
514
    void leaveEvent(QEvent *event) {
con's avatar
con committed
515 516
        textEdit->extraAreaLeaveEvent(event);
    }
517 518 519
    void contextMenuEvent(QContextMenuEvent *event) {
        textEdit->extraAreaContextMenuEvent(event);
    }
con's avatar
con committed
520 521 522 523

    void wheelEvent(QWheelEvent *event) {
        QCoreApplication::sendEvent(textEdit->viewport(), event);
    }
hjk's avatar
hjk committed
524 525 526

private:
    BaseTextEditorWidget *textEdit;
con's avatar
con committed
527 528
};

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

using namespace Internal;
con's avatar
con committed
532

533
QString BaseTextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
534
{
535 536 537 538 539 540 541 542 543 544
    // Copy the selected text as plain text
    QString text = cursor.selectedText();
    return convertToPlainText(text);
}

QString BaseTextEditorWidget::convertToPlainText(const QString &txt)
{
    QString ret = txt;
    QChar *uc = ret.data();
    QChar *e = uc + ret.size();
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560

    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:
            ;
        }
    }
561
    return ret;
562 563
}

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

566
BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent)
567 568
    : QPlainTextEdit(parent)
{
569
    d = new BaseTextEditorWidgetPrivate(this);
570 571
}

572
void BaseTextEditorWidget::setTextDocument(const QSharedPointer<BaseTextDocument> &doc)
573
{
574
    d->ctor(doc);
575 576
}

577
void BaseTextEditorWidgetPrivate::ctor(const QSharedPointer<BaseTextDocument> &doc)
con's avatar
con committed
578
{
579 580 581
    m_extraArea = new TextEditExtraArea(q);
    m_extraArea->setMouseTracking(true);
    q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
con's avatar
con committed
582

583 584 585 586
    m_overlay = new TextEditorOverlay(q);
    m_snippetOverlay = new TextEditorOverlay(q);
    m_searchResultOverlay = new TextEditorOverlay(q);
    m_refactorOverlay = new RefactorOverlay(q);
587

588 589
    m_document = doc;
    setupDocumentSignals();
con's avatar
con committed
590 591 592

    // from RESEARCH

593 594
    q->setLayoutDirection(Qt::LeftToRight);
    q->viewport()->setMouseTracking(true);
con's avatar
con committed
595

596 597 598 599 600
    extraAreaSelectionAnchorBlockNumber = -1;
    extraAreaToggleMarkBlockNumber = -1;
    extraAreaHighlightFoldedBlockNumber = -1;
    visibleFoldedBlockNumber = -1;
    suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
601

602
    QObject::connect(&m_codeAssistant, &CodeAssistant::finished,
603
                     q, &BaseTextEditorWidget::assistFinished);
604

605 606 607 608 609 610 611 612 613
    QObject::connect(q, &QPlainTextEdit::blockCountChanged,
                     this, &BaseTextEditorWidgetPrivate::slotUpdateExtraAreaWidth);

    QObject::connect(q, &BaseTextEditorWidget::modificationChanged, m_extraArea,
                     static_cast<void (QWidget::*)()>(&QWidget::update));

    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
                     q, &BaseTextEditorWidget::slotCursorPositionChanged);

614 615 616
    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
                     this, &BaseTextEditorWidgetPrivate::updateCursorPosition);

617 618 619 620 621
    QObject::connect(q, &QPlainTextEdit::updateRequest,
                     this, &BaseTextEditorWidgetPrivate::slotUpdateRequest);

    QObject::connect(q, &QPlainTextEdit::selectionChanged,
                     this, &BaseTextEditorWidgetPrivate::slotSelectionChanged);
con's avatar
con committed
622 623 624 625 626

//     (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()));

627 628 629 630
#ifdef DO_FOO
    (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo()));
#endif

con's avatar
con committed
631
    // parentheses matcher
632 633
    m_formatRange = true;
    m_mismatchFormat.setBackground(q->palette().color(QPalette::Base).value() < 128
634
                                      ? Qt::darkMagenta : Qt::magenta);
635
    m_parenthesesMatchingTimer.setSingleShot(true);
636 637
    QObject::connect(&m_parenthesesMatchingTimer, &QTimer::timeout,
                     this, &BaseTextEditorWidgetPrivate::_q_matchParentheses);
con's avatar
con committed
638

639
    m_highlightBlocksTimer.setSingleShot(true);
640 641
    QObject::connect(&m_highlightBlocksTimer, &QTimer::timeout,
                     this, &BaseTextEditorWidgetPrivate::_q_highlightBlocks);
642

643
    m_animator = 0;
con's avatar
con committed
644

645
    slotUpdateExtraAreaWidth();
con's avatar
con committed
646
    updateHighlights();
647
    q->setFrameStyle(QFrame::NoFrame);
con's avatar
con committed
648

649
    m_delayedUpdateTimer.setSingleShot(true);
650 651
    QObject::connect(&m_delayedUpdateTimer, &QTimer::timeout, q->viewport(),
                     static_cast<void (QWidget::*)()>(&QWidget::update));
652

653
    m_moveLineUndoHack = false;
con's avatar
con committed
654 655
}

656
BaseTextEditorWidget::~BaseTextEditorWidget()
con's avatar
con committed
657 658 659 660 661
{
    delete d;
    d = 0;
}

662 663 664 665 666
void BaseTextEditorWidget::setSimpleTextDocument(Id id)
{
    setTextDocument(BaseTextDocumentPtr(new BaseTextDocument(id)));
}

667
void BaseTextEditorWidget::print(QPrinter *printer)
con's avatar
con committed
668
{
hjk's avatar
hjk committed
669
    const bool oldFullPage = printer->fullPage();
con's avatar
con committed
670 671 672
    printer->setFullPage(true);
    QPrintDialog *dlg = new QPrintDialog(printer, this);
    dlg->setWindowTitle(tr("Print Document"));
673
    if (dlg->exec() == QDialog::Accepted)
con's avatar
con committed
674 675 676 677 678
        d->print(printer);
    printer->setFullPage(oldFullPage);
    delete dlg;
}

mae's avatar
mae committed
679
static int foldBoxWidth(const QFontMetrics &fm)
680 681
{
    const int lineSpacing = fm.lineSpacing();
hjk's avatar
hjk committed
682
    return lineSpacing + lineSpacing % 2 + 1;
683 684
}

con's avatar
con committed
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
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();
}

721
void BaseTextEditorWidgetPrivate::print(QPrinter *printer)
con's avatar
con committed
722 723 724
{
    QTextDocument *doc = q->document();

725
    QString title = m_document->displayName();
726
    if (!title.isEmpty())
con's avatar
con committed
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
        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
756
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
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 785 786 787 788 789 790 791 792 793 794 795 796 797
                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
798
    if (printer->collateCopies() == true) {
con's avatar
con committed
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 827 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
        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;
}


858
int BaseTextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
{
    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
875

876
BaseTextEditor *BaseTextEditorWidget::editor() const
con's avatar
con committed
877
{
878
    if (!d->m_editor) {
879 880
        auto that = const_cast<BaseTextEditorWidget *>(this);
        d->m_editor = that->createEditor();
881
        if (!d->m_editor->m_widget)
882
            d->m_editor->setEditorWidget(that);
883
        d->m_codeAssistant.configure(d->m_editor);
con's avatar
con committed
884
    }
885
    return d->m_editor;
con's avatar
con committed
886 887
}

888
void BaseTextEditorWidget::selectEncoding()
con's avatar
con committed
889
{
890
    BaseTextDocument *doc = d->m_document.data();
con's avatar
con committed
891 892 893
    CodecSelector codecSelector(this, doc);

    switch (codecSelector.exec()) {
894 895 896 897 898 899 900
    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
901 902
    case CodecSelector::Save:
        doc->setCodec(codecSelector.selectedCodec());
903
        EditorManager::saveDocument(textDocument());
904
        updateTextCodecLabel();
con's avatar
con committed
905 906 907 908 909 910
        break;
    case CodecSelector::Cancel:
        break;
    }
}

911 912
void BaseTextEditorWidget::updateTextCodecLabel()
{
913 914
    QString text = QString::fromLatin1(d->m_document->codec()->name());
    d->m_fileEncodingLabel->setText(text, text);
915 916
}

917
QString BaseTextEditorWidget::msgTextTooLarge(quint64 size)
918 919 920 921 922
{
    return tr("The text is too large to be displayed (%1 MB).").
           arg(size >> 20);
}

David Schulz's avatar
David Schulz committed
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938
void BaseTextEditorWidget::insertPlainText(const QString &text)
{
    if (d->m_inBlockSelectionMode)
        d->insertIntoBlockSelection(text);
    else
        QPlainTextEdit::insertPlainText(text);
}

QString BaseTextEditorWidget::selectedText() const
{
    if (d->m_inBlockSelectionMode)
        return d->copyBlockSelection();
    else
        return textCursor().selectedText();
}

939
void BaseTextEditorWidgetPrivate::updateCannotDecodeInfo()
940
{
941
    q->setReadOnly(m_document->hasDecodingError());
942 943
    InfoBar *infoBar = m_document->infoBar();
    Id selectEncodingId(Constants::SELECT_ENCODING);
944
    if (m_document->hasDecodingError()) {
945 946
        if (!infoBar->canInfoBeAdded(selectEncodingId))
            return;
947
        InfoBarEntry info(selectEncodingId,
948 949 950
            BaseTextEditorWidget::tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.")
            .arg(m_document->displayName()).arg(QString::fromLatin1(m_document->codec()->name())));
        info.setCustomButtonInfo(BaseTextEditorWidget::tr("Select Encoding"), q, SLOT(selectEncoding()));
951
        infoBar->addInfo(info);
952
    } else {
953
        infoBar->removeInfo(selectEncodingId);
954 955 956
    }
}

957
bool BaseTextEditorWidget::open(QString *errorString, const QString &fileName, const QString &realFileName)
con's avatar
con committed
958
{
959
    if (d->m_document->open(errorString, fileName, realFileName)) {
con's avatar
con committed
960
        moveCursor(QTextCursor::Start);
961
        d->updateCannotDecodeInfo();
962 963
        if (d->m_fileEncodingLabel) {
            connect(d->m_fileEncodingLabel, SIGNAL(clicked()), this,
964 965 966 967 968
                    SLOT(selectEncoding()), Qt::UniqueConnection);
            connect(d->m_document->document(), SIGNAL(modificationChanged(bool)), this,
                    SLOT(updateTextCodecLabel()), Qt::UniqueConnection);
            updateTextCodecLabel();
        }
con's avatar
con committed
969 970 971 972 973
        return true;
    }
    return false;
}

974 975 976
/*
  Collapses the first comment in a file, if there is only whitespace above
  */
977
void BaseTextEditorWidgetPrivate::foldLicenseHeader()
978 979
{
    QTextDocument *doc = q->document();
980
    BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
981 982
    QTC_ASSERT(documentLayout, return);
    QTextBlock block = doc->firstBlock();
983
    while (block.isValid() && block.isVisible()) {
984
        QString text = block.text();
mae's avatar
mae committed
985 986 987 988 989 990 991 992 993
        if (BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
            if (text.trimmed().startsWith(QLatin1String("/*"))) {
                BaseTextDocumentLayout::doFoldOrUnfold(block, false);
                moveCursorVisible();
                documentLayout->requestUpdate();
                documentLayout->emitDocumentSizeChanged();
                break;
            }
        }
994
        if (TabSettings::firstNonSpace(text) < text.size())
995 996 997 998 999
            break;
        block = block.next();
    }
}

1000
BaseTextDocument *BaseTextEditorWidget::textDocument() const
con's avatar
con committed
1001
{
1002
    return d->m_document.data();
con's avatar
con committed
1003 1004
}

1005 1006 1007 1008 1009
BaseTextDocumentPtr BaseTextEditorWidget::textDocumentPtr() const
{
    return d->m_document;
}

1010
void BaseTextEditorWidgetPrivate::editorContentsChange(int position, int charsRemoved, int charsAdded)
con's avatar
con committed
1011
{
1012 1013
    if (m_animator)
        m_animator->finish();
1014

1015 1016
    m_contentsChanged = true;
    QTextDocument *doc = q->document();
1017
    BaseTextDocumentLayout *documentLayout = static_cast<BaseTextDocumentLayout*>(doc->documentLayout());
1018
    const QTextBlock posBlock = doc->findBlock(position);
con's avatar
con committed
1019 1020 1021

    // Keep the line numbers and the block information for the text marks updated
    if (charsRemoved != 0) {
1022
        documentLayout->updateMarksLineNumber();
1023
        documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1024
    } else {
1025
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
con's avatar
con committed
1026
        if (posBlock != nextBlock) {
1027 1028 1029
            documentLayout->updateMarksLineNumber();
            documentLayout->updateMarksBlock(posBlock);
            documentLayout->updateMarksBlock(nextBlock);
con's avatar
con committed
1030
        } else {
1031
            documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1032 1033
        }
    }
1034

1035 1036
    if (m_snippetOverlay->isVisible()) {
        QTextCursor cursor = q->textCursor();
1037
        cursor.setPosition(position);
1038
        snippetCheckCursor(cursor);
1039 1040
    }

1041 1042
    if (charsAdded != 0 && q->document()->characterAt(position + charsAdded - 1).isPrint())
        m_assistRelevantContentAdded = true;
1043 1044

    int newBlockCount = doc->blockCount();
1045
    if (!q->hasFocus() && newBlockCount != m_blockCount) {
1046
        // lines were inserted or removed from outside, keep viewport on same part of text
1047 1048
        if (q->firstVisibleBlock().blockNumber() > posBlock.blockNumber())
            q->verticalScrollBar()->setValue(q->verticalScrollBar()->value() + newBlockCount - m_blockCount);
1049
    }
1050
    m_blockCount = newBlockCount;
con's avatar
con committed
1051 1052
}

1053
void BaseTextEditorWidgetPrivate::slotSelectionChanged()
con's avatar
con committed
1054
{
1055 1056
    if (!q->textCursor().hasSelection() && !m_selectBlockAnchor.isNull())
        m_selectBlockAnchor = QTextCursor();
1057
    // Clear any link which might be showing when the selection changes
1058
    clearLink();
con's avatar
con committed
1059 1060
}

1061
void BaseTextEditorWidget::gotoBlockStart()
1062 1063
{
    QTextCursor cursor = textCursor();
1064
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
1065
        setTextCursor(cursor);
1066
        d->_q_matchParentheses();
1067
    }
1068 1069
}

1070
void BaseTextEditorWidget::gotoBlockEnd()
1071 1072
{
    QTextCursor cursor = textCursor();
1073
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, false))