basetexteditor.cpp 252 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>
54
#include <texteditor/codeassist/assistinterface.h>
55
56
57
58
59
#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
60

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

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

103
104
//#define DO_FOO

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

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

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

hjk's avatar
hjk committed
130

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

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

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

174
private:
175
176
    void step(qreal v);

177
    QTimeLine m_timeline;
178
179
180
181
182
183
184
185
186
    qreal m_value;
    int m_position;
    QPointF m_lastDrawPos;
    QFont m_font;
    QPalette m_palette;
    QString m_text;
    QSizeF m_size;
};

187
188
189
190
191
192
193
194
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
class TextEditExtraArea : public QWidget
{
public:
    TextEditExtraArea(BaseTextEditorWidget *edit)
        : 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:
    BaseTextEditorWidget *textEdit;
};

hjk's avatar
hjk committed
228
229
230
class BaseTextEditorPrivate
{
public:
231
    BaseTextEditorPrivate() {}
232

233
    QPointer<BaseTextEditorFactory> m_origin;
hjk's avatar
hjk committed
234
235
};

236
class BaseTextEditorWidgetPrivate : public QObject
237
238
{
public:
239
    BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent);
240
241
242
243
    ~BaseTextEditorWidgetPrivate()
    {
        delete m_toolBar;
    }
244
245
246
247
248
249

    void setupDocumentSignals();
    void updateLineSelectionColor();

    void print(QPrinter *printer);

250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
    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);

289
290
291
292
293
294
295
296
297
298
299
300
    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);
301
    void highlightSearchResultsSlot(const QString &txt, FindFlags findFlags);
302
303
    void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int);

304
305
    void updateCursorPosition();

306
307
308
309
310
311
312
    // parentheses matcher
    void _q_matchParentheses();
    void _q_highlightBlocks();
    void slotSelectionChanged();
    void _q_animateUpdate(int position, QPointF lastPos, QRectF rect);
    void updateCodeFoldingVisible();

313
314
    void reconfigure();

315
public:
316
    BaseTextEditorWidget *q;
317
318
    QToolBar *m_toolBar;
    QWidget *m_stretchWidget;
319
320
    LineColumnLabel *m_cursorPositionLabel;
    LineColumnLabel *m_fileEncodingLabel;
321
322
323
    QAction *m_cursorPositionLabelAction;
    QAction *m_fileEncodingLabelAction;

324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
    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;

341
    Id m_tabSettingsId;
342
343
344
345
346
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
    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;
385
    FindFlags m_findFlags;
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    void highlightSearchResults(const QTextBlock &block, TextEditorOverlay *overlay);
    QTimer m_delayedUpdateTimer;

    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;

422
    CodeAssistant m_codeAssistant;
423
424
425
426
427
428
429
430
431
432
    bool m_assistRelevantContentAdded;

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

    QPoint m_markDragStart;
    bool m_markDragging;

    QScopedPointer<Internal::ClipboardAssistProvider> m_clipboardAssistProvider;
433
434

    bool m_isMissingSyntaxDefinition;
435
436

    QScopedPointer<AutoCompleter> m_autoCompleter;
437
    CommentDefinition m_commentDefinition;
438
    CompletionAssistProvider *m_completionAssistProvider;
439
440
};

441
442
BaseTextEditorWidgetPrivate::BaseTextEditorWidgetPrivate(BaseTextEditorWidget *parent)
  : q(parent),
443
444
445
446
447
448
    m_toolBar(0),
    m_stretchWidget(0),
    m_cursorPositionLabel(0),
    m_fileEncodingLabel(0),
    m_cursorPositionLabelAction(0),
    m_fileEncodingLabelAction(0),
449
450
451
452
453
454
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
    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),
489
    m_isMissingSyntaxDefinition(false),
490
491
    m_autoCompleter(new AutoCompleter),
    m_completionAssistProvider(0)
492
493
494
{
    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
    BaseTextFind *baseTextFind = new BaseTextFind(q);
hjk's avatar
hjk committed
495
    connect(baseTextFind, &BaseTextFind::highlightAllRequested,
496
497
498
499
500
            this, &BaseTextEditorWidgetPrivate::highlightSearchResultsSlot);
    connect(baseTextFind, &BaseTextFind::findScopeChanged,
            this, &BaseTextEditorWidgetPrivate::setFindScope);
    aggregate->add(baseTextFind);
    aggregate->add(q);
501

502
503
504
    m_extraArea = new TextEditExtraArea(q);
    m_extraArea->setMouseTracking(true);

505
506
507
508
509
510
    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);

511
    m_cursorPositionLabel = new LineColumnLabel;
512
513
514
    const int spacing = q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2;
    m_cursorPositionLabel->setContentsMargins(spacing, 0, spacing, 0);

515
    m_fileEncodingLabel = new LineColumnLabel;
516
517
518
519
    m_fileEncodingLabel->setContentsMargins(spacing, 0, spacing, 0);

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

522
} // namespace Internal
hjk's avatar
hjk committed
523
524

using namespace Internal;
con's avatar
con committed
525

526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
/*!
 * 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.
 */
static void updateEditorInfoBar(BaseTextEditorWidget *widget)
{
    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);
541
542
543
544
545
546
        info.setCustomButtonInfo(BaseTextEditor::tr("Show Highlighter Options..."), [widget]() {
            ICore::showOptionsDialog(Constants::TEXT_EDITOR_SETTINGS_CATEGORY,
                                     Constants::TEXT_EDITOR_HIGHLIGHTER_SETTINGS,
                                     widget);
        });

547
548
549
550
        infoBar->addInfo(info);
    }
}

551
QString BaseTextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
552
{
553
554
555
556
557
558
559
560
561
562
    // 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();
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578

    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:
            ;
        }
    }
579
    return ret;
580
581
}

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

584
BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent)
585
586
    : QPlainTextEdit(parent)
{
587
588
589
    // "Needed", as the creation below triggers ChildEvents that are
    // passed to this object's event() which uses 'd'.
    d = 0;
590
    d = new BaseTextEditorWidgetPrivate(this);
591
592
}

593
void BaseTextEditorWidget::setTextDocument(const QSharedPointer<BaseTextDocument> &doc)
594
{
595
    d->ctor(doc);
596
597
}

598
void BaseTextEditorWidgetPrivate::ctor(const QSharedPointer<BaseTextDocument> &doc)
con's avatar
con committed
599
{
600
    q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
con's avatar
con committed
601

602
603
604
605
    m_overlay = new TextEditorOverlay(q);
    m_snippetOverlay = new TextEditorOverlay(q);
    m_searchResultOverlay = new TextEditorOverlay(q);
    m_refactorOverlay = new RefactorOverlay(q);
606

607
608
    m_document = doc;
    setupDocumentSignals();
con's avatar
con committed
609
610
611

    // from RESEARCH

612
613
    q->setLayoutDirection(Qt::LeftToRight);
    q->viewport()->setMouseTracking(true);
con's avatar
con committed
614

615
616
617
618
619
    extraAreaSelectionAnchorBlockNumber = -1;
    extraAreaToggleMarkBlockNumber = -1;
    extraAreaHighlightFoldedBlockNumber = -1;
    visibleFoldedBlockNumber = -1;
    suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
620

621
    QObject::connect(&m_codeAssistant, &CodeAssistant::finished,
622
                     q, &BaseTextEditorWidget::assistFinished);
623

624
625
626
    QObject::connect(q, &QPlainTextEdit::blockCountChanged,
                     this, &BaseTextEditorWidgetPrivate::slotUpdateExtraAreaWidth);

627
    QObject::connect(q, &QPlainTextEdit::modificationChanged, m_extraArea,
628
629
630
631
632
                     static_cast<void (QWidget::*)()>(&QWidget::update));

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

633
634
635
    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
                     this, &BaseTextEditorWidgetPrivate::updateCursorPosition);

636
637
638
639
640
    QObject::connect(q, &QPlainTextEdit::updateRequest,
                     this, &BaseTextEditorWidgetPrivate::slotUpdateRequest);

    QObject::connect(q, &QPlainTextEdit::selectionChanged,
                     this, &BaseTextEditorWidgetPrivate::slotSelectionChanged);
con's avatar
con committed
641
642
643
644
645

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

646
647
648
649
#ifdef DO_FOO
    (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo()));
#endif

con's avatar
con committed
650
    // parentheses matcher
651
652
    m_formatRange = true;
    m_mismatchFormat.setBackground(q->palette().color(QPalette::Base).value() < 128
653
                                      ? Qt::darkMagenta : Qt::magenta);
654
    m_parenthesesMatchingTimer.setSingleShot(true);
655
656
    QObject::connect(&m_parenthesesMatchingTimer, &QTimer::timeout,
                     this, &BaseTextEditorWidgetPrivate::_q_matchParentheses);
con's avatar
con committed
657

658
    m_highlightBlocksTimer.setSingleShot(true);
659
660
    QObject::connect(&m_highlightBlocksTimer, &QTimer::timeout,
                     this, &BaseTextEditorWidgetPrivate::_q_highlightBlocks);
661

662
    m_animator = 0;
con's avatar
con committed
663

664
    slotUpdateExtraAreaWidth();
con's avatar
con committed
665
    updateHighlights();
666
    q->setFrameStyle(QFrame::NoFrame);
con's avatar
con committed
667

668
    m_delayedUpdateTimer.setSingleShot(true);
669
670
    QObject::connect(&m_delayedUpdateTimer, &QTimer::timeout, q->viewport(),
                     static_cast<void (QWidget::*)()>(&QWidget::update));
671

672
    m_moveLineUndoHack = false;
con's avatar
con committed
673
674
}

675
BaseTextEditorWidget::~BaseTextEditorWidget()
con's avatar
con committed
676
677
678
679
680
{
    delete d;
    d = 0;
}

681
void BaseTextEditorWidget::print(QPrinter *printer)
con's avatar
con committed
682
{
hjk's avatar
hjk committed
683
    const bool oldFullPage = printer->fullPage();
con's avatar
con committed
684
685
686
    printer->setFullPage(true);
    QPrintDialog *dlg = new QPrintDialog(printer, this);
    dlg->setWindowTitle(tr("Print Document"));
687
    if (dlg->exec() == QDialog::Accepted)
con's avatar
con committed
688
689
690
691
692
        d->print(printer);
    printer->setFullPage(oldFullPage);
    delete dlg;
}

mae's avatar
mae committed
693
static int foldBoxWidth(const QFontMetrics &fm)
694
695
{
    const int lineSpacing = fm.lineSpacing();
hjk's avatar
hjk committed
696
    return lineSpacing + lineSpacing % 2 + 1;
697
698
}

con's avatar
con committed
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
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();
}

735
void BaseTextEditorWidgetPrivate::print(QPrinter *printer)
con's avatar
con committed
736
737
738
{
    QTextDocument *doc = q->document();

739
    QString title = m_document->displayName();
740
    if (!title.isEmpty())
con's avatar
con committed
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
        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
770
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
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
798
799
800
801
802
803
804
805
806
807
808
809
810
811
                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
812
    if (printer->collateCopies() == true) {
con's avatar
con committed
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
858
859
860
861
862
863
864
865
866
867
868
869
870
871
        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;
}


872
int BaseTextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
{
    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
889

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

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

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

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

David Schulz's avatar
David Schulz committed
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
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();
}

941
void BaseTextEditorWidgetPrivate::updateCannotDecodeInfo()
942
{
943
    q->setReadOnly(m_document->hasDecodingError());
944
945
    InfoBar *infoBar = m_document->infoBar();
    Id selectEncodingId(Constants::SELECT_ENCODING);
946
    if (m_document->hasDecodingError()) {
947
948
        if (!infoBar->canInfoBeAdded(selectEncodingId))
            return;
949
        InfoBarEntry info(selectEncodingId,
950
951
            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())));
952
        info.setCustomButtonInfo(BaseTextEditorWidget::tr("Select Encoding"), [this]() { q->selectEncoding(); });
953
        infoBar->addInfo(info);
954
    } else {
955
        infoBar->removeInfo(selectEncodingId);
956
957
958
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

1081
void BaseTextEditorWidget::gotoBlockStartWithSelection()
1082
1083
{
    QTextCursor cursor = textCursor();
1084
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
1085
        setTextCursor(cursor);
1086
        d->_q_matchParentheses();
1087
    }
1088
1089
}

1090
void BaseTextEditorWidget::gotoBlockEndWithSelection()
1091
1092
{
    QTextCursor cursor = textCursor();
1093
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
1094
        setTextCursor(cursor);
1095
        d->_q_matchParentheses();
1096
1097
1098
    }
}

1099
void BaseTextEditorWidget::gotoLineStart()
mae's avatar
mae committed
1100
{
1101
    d->handleHomeKey(false);
mae's avatar
mae committed
1102
1103
}

1104
void BaseTextEditorWidget::gotoLineStartWithSelection()
mae's avatar
mae committed
1105
{
1106
    d->handleHomeKey(true);
mae's avatar
mae committed
1107
1108
}

1109
void BaseTextEditorWidget::gotoLineEnd()
mae's avatar
mae committed
1110
1111
1112
1113
{
    moveCursor(QTextCursor::EndOfLine);
}

1114
void BaseTextEditorWidget::gotoLineEndWithSelection()
mae's avatar
mae committed
1115
1116
1117
1118
{
    moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
}

1119
void BaseTextEditorWidget::gotoNextLine()
mae's avatar
mae committed
1120
{
1121
    moveCursor(QTextCursor::Down);
mae's avatar
mae committed
1122
1123
}

1124
void BaseTextEditorWidget::gotoNextLineWithSelection()
mae's avatar
mae committed
1125
{
1126
    moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor);
mae's avatar
mae committed
1127
1128
}

1129
void BaseTextEditorWidget::gotoPreviousLine()
mae's avatar
mae committed
1130
{
1131
    moveCursor(QTextCursor::Up);
mae's avatar
mae committed
1132
1133
}

1134
void BaseTextEditorWidget::gotoPreviousLineWithSelection()
mae's avatar
mae committed
1135
{
1136
    moveCursor(QTextCursor::Up, QTextCursor::KeepAnchor);
mae's avatar
mae committed
1137
1138
}

1139
void BaseTextEditorWidget::gotoPreviousCharacter()
mae's avatar
mae committed
1140
1141
1142
1143
{
    moveCursor(QTextCursor::PreviousCharacter);
}

1144
void BaseTextEditorWidget::gotoPreviousCharacterWithSelection()
mae's avatar
mae committed
1145
1146
1147
1148
{
    moveCursor(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
}

1149
void BaseTextEditorWidget::gotoNextCharacter()
mae's avatar
mae committed
1150
1151
1152
1153
{
    moveCursor(QTextCursor::NextCharacter);
}

1154
void BaseTextEditorWidget::gotoNextCharacterWithSelection()
mae's avatar
mae committed
1155
1156
1157
1158
{
    moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}