FreeCAD

Форк
0
/
TextEdit.cpp 
607 строк · 21.8 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24

25
#ifndef _PreComp_
26
# include <QApplication>
27
# include <QKeyEvent>
28
# include <QPainter>
29
# include <QRegularExpression>
30
# include <QRegularExpressionMatch>
31
# include <QShortcut>
32
# include <QTextCursor>
33
#endif
34

35
#include "TextEdit.h"
36
#include "SyntaxHighlighter.h"
37
#include "Tools.h"
38
#include <App/Color.h>
39

40

41
using namespace Gui;
42

43
/**
44
 *  Constructs a TextEdit which is a child of 'parent'.
45
 */
46
TextEdit::TextEdit(QWidget* parent)
47
    : QPlainTextEdit(parent), cursorPosition(0), listBox(nullptr)
48
{
49
    //Note: Set the correct context to this shortcut as we may use several instances of this
50
    //class at a time
51
    auto shortcut = new QShortcut(this);
52
    shortcut->setKey(QKeySequence(QString::fromLatin1("CTRL+Space")));
53
    shortcut->setContext(Qt::WidgetShortcut);
54
    connect(shortcut, &QShortcut::activated, this, &TextEdit::complete);
55

56
    auto shortcutFind = new QShortcut(this);
57
    shortcutFind->setKey(QKeySequence::Find);
58
    shortcutFind->setContext(Qt::WidgetShortcut);
59
    connect(shortcutFind, &QShortcut::activated, this, &TextEdit::showSearchBar);
60

61
    auto shortcutNext = new QShortcut(this);
62
    shortcutNext->setKey(QKeySequence::FindNext);
63
    shortcutNext->setContext(Qt::WidgetShortcut);
64
    connect(shortcutNext, &QShortcut::activated, this, &TextEdit::findNext);
65

66
    auto shortcutPrev = new QShortcut(this);
67
    shortcutPrev->setKey(QKeySequence::FindPrevious);
68
    shortcutPrev->setContext(Qt::WidgetShortcut);
69
    connect(shortcutPrev, &QShortcut::activated, this, &TextEdit::findPrevious);
70
}
71

72
/** Destroys the object and frees any allocated resources */
73
TextEdit::~TextEdit() = default;
74

75
/**
76
 * Set the approproriate item of the completion box or hide it, if needed.
77
 */
78
void TextEdit::keyPressEvent(QKeyEvent* e)
79
{
80
    QPlainTextEdit::keyPressEvent(e);
81
    // This can't be done in CompletionList::eventFilter() because we must first perform
82
    // the event and afterwards update the list widget
83
    if (listBox && listBox->isVisible()) {
84
        // Get the word under the cursor
85
        QTextCursor cursor = textCursor();
86
        cursor.movePosition(QTextCursor::StartOfWord);
87
        // the cursor has moved to outside the word prefix
88
        if (cursor.position() < cursorPosition-wordPrefix.length() ||
89
            cursor.position() > cursorPosition) {
90
            listBox->hide();
91
            return;
92
        }
93
        cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
94
        listBox->keyboardSearch(cursor.selectedText());
95
        cursor.clearSelection();
96
    }
97
}
98

99
/**
100
 * Completes the word.
101
 */
102
void TextEdit::complete()
103
{
104
    QTextBlock block = textCursor().block();
105
    if (!block.isValid())
106
        return;
107
    int cursorPos = textCursor().position()-block.position();
108
    QString para = block.text();
109
    int wordStart = cursorPos;
110
    while (wordStart > 0 && para[wordStart - 1].isLetterOrNumber())
111
        --wordStart;
112
    wordPrefix = para.mid(wordStart, cursorPos - wordStart);
113
    if (wordPrefix.isEmpty())
114
        return;
115

116
    QStringList list = toPlainText().split(QRegularExpression(QLatin1String("\\W+")));
117
    QMap<QString, QString> map;
118
    QStringList::Iterator it = list.begin();
119
    while (it != list.end()) {
120
        if ((*it).startsWith(wordPrefix) && (*it).length() > wordPrefix.length())
121
            map[(*it).toLower()] = *it;
122
        ++it;
123
    }
124

125
    if (map.count() == 1) {
126
        insertPlainText((*map.begin()).mid(wordPrefix.length()));
127
    } else if (map.count() > 1) {
128
        if (!listBox)
129
            createListBox();
130
        listBox->clear();
131
        listBox->addItems(map.values());
132
        listBox->setFont(QFont(font().family(), 8));
133

134
        this->cursorPosition = textCursor().position();
135

136
        // get the minimum width and height of the box
137
        int h = 0;
138
        int w = 0;
139
        for (int i = 0; i < listBox->count(); ++i) {
140
            QRect r = listBox->visualItemRect(listBox->item(i));
141
            w = qMax(w, r.width());
142
            h += r.height();
143
        }
144

145
        // Add an offset
146
        w += 2*listBox->frameWidth();
147
        h += 2*listBox->frameWidth();
148

149
        // get the start position of the word prefix
150
        QTextCursor cursor = textCursor();
151
        cursor.movePosition(QTextCursor::StartOfWord);
152
        QRect rect = cursorRect(cursor);
153
        int posX = rect.x();
154
        int posY = rect.y();
155
        int boxH = h;
156

157
        // Decide whether to show downstairs or upstairs
158
        if (posY > viewport()->height()/2) {
159
            h = qMin(qMin(h,posY), 250);
160
            if (h < boxH)
161
                w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
162
            listBox->setGeometry(posX,posY-h, w, h);
163
        } else {
164
            h = qMin(qMin(h,viewport()->height()-fontMetrics().height()-posY), 250);
165
            if (h < boxH)
166
                w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
167
            listBox->setGeometry(posX, posY+fontMetrics().height(), w, h);
168
        }
169

170
        listBox->setCurrentRow(0);
171
        listBox->show();
172
    }
173
}
174

175
/**
176
 * Creates the listbox containing all possibilities for the completion.
177
 * The listbox is closed when ESC is pressed, the text edit field loses focus or a
178
 * mouse button was pressed.
179
 */
180
void TextEdit::createListBox()
181
{
182
    listBox = new CompletionList(this);
183
    listBox->setFrameStyle(QFrame::Box);
184
    listBox->setFrameShadow(QFrame::Raised);
185
    listBox->setLineWidth(2);
186
    installEventFilter(listBox);
187
    viewport()->installEventFilter(listBox);
188
    listBox->setSelectionMode( QAbstractItemView::SingleSelection );
189
    listBox->hide();
190
}
191

192
// ------------------------------------------------------------------------------
193

194
namespace Gui {
195
struct TextEditorP
196
{
197
    QMap<QString, QColor> colormap; // Color map
198
    TextEditorP()
199
    {
200
        colormap[QLatin1String("Text")] = qApp->palette().windowText().color();
201
        colormap[QLatin1String("Bookmark")] = Qt::cyan;
202
        colormap[QLatin1String("Breakpoint")] = Qt::red;
203
        colormap[QLatin1String("Keyword")] = Qt::blue;
204
        colormap[QLatin1String("Comment")] = QColor(0, 170, 0);
205
        colormap[QLatin1String("Block comment")] = QColor(160, 160, 164);
206
        colormap[QLatin1String("Number")] = Qt::blue;
207
        colormap[QLatin1String("String")] = Qt::red;
208
        colormap[QLatin1String("Character")] = Qt::red;
209
        colormap[QLatin1String("Class name")] = QColor(255, 170, 0);
210
        colormap[QLatin1String("Define name")] = QColor(255, 170, 0);
211
        colormap[QLatin1String("Operator")] = QColor(160, 160, 164);
212
        colormap[QLatin1String("Python output")] = QColor(170, 170, 127);
213
        colormap[QLatin1String("Python error")] = Qt::red;
214
        colormap[QLatin1String("Current line highlight")] = QColor(224,224,224);
215
    }
216
};
217
} // namespace Gui
218

219
/**
220
 *  Constructs a TextEditor which is a child of 'parent' and does the
221
 *  syntax highlighting for the Python language.
222
 */
223
TextEditor::TextEditor(QWidget* parent)
224
  : TextEdit(parent), WindowParameter("Editor"), highlighter(nullptr)
225
{
226
    d = new TextEditorP();
227
    lineNumberArea = new LineMarker(this);
228

229
    QFont serifFont(QLatin1String("Courier"), 10, QFont::Normal);
230
    setFont(serifFont);
231

232
    ParameterGrp::handle hPrefGrp = getWindowParameter();
233
    hPrefGrp->Attach( this );
234

235
    // set colors and font
236
    hPrefGrp->NotifyAll();
237

238
    connect(this, &QPlainTextEdit::cursorPositionChanged,
239
            this, &TextEditor::highlightCurrentLine);
240
    connect(this, &QPlainTextEdit::blockCountChanged,
241
            this, &TextEditor::updateLineNumberAreaWidth);
242
    connect(this, &QPlainTextEdit::updateRequest,
243
            this, &TextEditor::updateLineNumberArea);
244

245
    updateLineNumberAreaWidth(0);
246
    highlightCurrentLine();
247
}
248

249
/** Destroys the object and frees any allocated resources */
250
TextEditor::~TextEditor()
251
{
252
    getWindowParameter()->Detach(this);
253
    delete highlighter;
254
    delete d;
255
}
256

257
int TextEditor::lineNumberAreaWidth()
258
{
259
    return QtTools::horizontalAdvance(fontMetrics(), QLatin1String("0000")) + 10;
260
}
261

262
void TextEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
263
{
264
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
265
}
266

267
void TextEditor::updateLineNumberArea(const QRect &rect, int dy)
268
{
269
    if (dy)
270
        lineNumberArea->scroll(0, dy);
271
    else
272
        lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
273

274
    if (rect.contains(viewport()->rect()))
275
        updateLineNumberAreaWidth(0);
276
}
277

278
void TextEditor::resizeEvent(QResizeEvent *e)
279
{
280
    QPlainTextEdit::resizeEvent(e);
281

282
    QRect cr = contentsRect();
283
    lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
284
}
285

286
void TextEditor::highlightCurrentLine()
287
{
288
    QList<QTextEdit::ExtraSelection> extraSelections;
289

290
    if (!isReadOnly()) {
291
        QTextEdit::ExtraSelection selection;
292
        QColor lineColor = d->colormap[QLatin1String("Current line highlight")];
293
        unsigned int col = App::Color::asPackedRGB<QColor>(lineColor);
294
        ParameterGrp::handle hPrefGrp = getWindowParameter();
295
        auto value = static_cast<unsigned long>(col);
296
        value = hPrefGrp->GetUnsigned( "Current line highlight", value);
297
        col = static_cast<unsigned int>(value);
298
        lineColor.setRgb((col>>24)&0xff, (col>>16)&0xff, (col>>8)&0xff);
299
        selection.format.setBackground(lineColor);
300
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
301
        selection.cursor = textCursor();
302
        selection.cursor.clearSelection();
303
        extraSelections.append(selection);
304
    }
305

306
    setExtraSelections(extraSelections);
307
}
308

309
void TextEditor::drawMarker(int line, int x, int y, QPainter* p)
310
{
311
    Q_UNUSED(line);
312
    Q_UNUSED(x);
313
    Q_UNUSED(y);
314
    Q_UNUSED(p);
315
}
316

317
void TextEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
318
{
319
    QPainter painter(lineNumberArea);
320
    //painter.fillRect(event->rect(), Qt::lightGray);
321

322
    QTextBlock block = firstVisibleBlock();
323
    int blockNumber = block.blockNumber();
324
    int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
325
    int bottom = top + (int) blockBoundingRect(block).height();
326

327
    while (block.isValid() && top <= event->rect().bottom()) {
328
        if (block.isVisible() && bottom >= event->rect().top()) {
329
            QString number = QString::number(blockNumber + 1);
330
            QPalette pal = palette();
331
            QColor color = pal.windowText().color();
332
            painter.setPen(color);
333
            painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
334
                             Qt::AlignRight, number);
335
            drawMarker(blockNumber + 1, 1, top, &painter);
336
        }
337

338
        block = block.next();
339
        top = bottom;
340
        bottom = top + (int) blockBoundingRect(block).height();
341
        ++blockNumber;
342
    }
343
}
344

345
void TextEditor::setSyntaxHighlighter(SyntaxHighlighter* sh)
346
{
347
    sh->setDocument(this->document());
348
    this->highlighter = sh;
349
}
350

351
void TextEditor::keyPressEvent (QKeyEvent * e)
352
{
353
    if ( e->key() == Qt::Key_Tab ) {
354
        ParameterGrp::handle hPrefGrp = getWindowParameter();
355
        int indent = hPrefGrp->GetInt( "IndentSize", 4 );
356
        bool space = hPrefGrp->GetBool( "Spaces", false );
357
        QString ch = space ? QString(indent, QLatin1Char(' '))
358
                           : QString::fromLatin1("\t");
359

360
        QTextCursor cursor = textCursor();
361
        if (!cursor.hasSelection()) {
362
            // insert a single tab or several spaces
363
            cursor.beginEditBlock();
364
            cursor.insertText(ch);
365
            cursor.endEditBlock();
366
        } else {
367
            // for each selected block insert a tab or spaces
368
            int selStart = cursor.selectionStart();
369
            int selEnd = cursor.selectionEnd();
370
            QTextBlock block;
371
            cursor.beginEditBlock();
372
            for (block = document()->begin(); block.isValid(); block = block.next()) {
373
                int pos = block.position();
374
                int off = block.length()-1;
375
                // at least one char of the block is part of the selection
376
                if ( pos >= selStart || pos+off >= selStart) {
377
                    if ( pos+1 > selEnd )
378
                        break; // end of selection reached
379
                    cursor.setPosition(block.position());
380
                    cursor.insertText(ch);
381
                        selEnd += ch.length();
382
                }
383
            }
384

385
            cursor.endEditBlock();
386
        }
387

388
        return;
389
    }
390
    else if (e->key() == Qt::Key_Backtab) {
391
        QTextCursor cursor = textCursor();
392
        if (!cursor.hasSelection())
393
            return; // Shift+Tab should not do anything
394
        // If some text is selected we remove a leading tab or
395
        // spaces from each selected block
396
        ParameterGrp::handle hPrefGrp = getWindowParameter();
397
        int indent = hPrefGrp->GetInt( "IndentSize", 4 );
398

399
        int selStart = cursor.selectionStart();
400
        int selEnd = cursor.selectionEnd();
401
        QTextBlock block;
402
        cursor.beginEditBlock();
403
        for (block = document()->begin(); block.isValid(); block = block.next()) {
404
            int pos = block.position();
405
            int off = block.length()-1;
406
            // at least one char of the block is part of the selection
407
            if ( pos >= selStart || pos+off >= selStart) {
408
                if ( pos+1 > selEnd )
409
                    break; // end of selection reached
410
                // if possible remove one tab or several spaces
411
                QString text = block.text();
412
                if (text.startsWith(QLatin1String("\t"))) {
413
                    cursor.setPosition(block.position());
414
                    cursor.deleteChar();
415
                    selEnd--;
416
                }
417
                else {
418
                    cursor.setPosition(block.position());
419
                    for (int i=0; i<indent; i++) {
420
                        if (!text.startsWith(QLatin1String(" ")))
421
                            break;
422
                        text = text.mid(1);
423
                        cursor.deleteChar();
424
                        selEnd--;
425
                    }
426
                }
427
            }
428
        }
429

430
        cursor.endEditBlock();
431
        return;
432
    }
433

434
    TextEdit::keyPressEvent( e );
435
}
436

437
/** Sets the font, font size and tab size of the editor. */
438
void TextEditor::OnChange(Base::Subject<const char*> &rCaller,const char* sReason)
439
{
440
    Q_UNUSED(rCaller);
441
    ParameterGrp::handle hPrefGrp = getWindowParameter();
442
    if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
443
#ifdef FC_OS_LINUX
444
        int fontSize = hPrefGrp->GetInt("FontSize", 15);
445
#else
446
        int fontSize = hPrefGrp->GetInt("FontSize", 10);
447
#endif
448
        QString fontFamily = QString::fromLatin1(hPrefGrp->GetASCII( "Font", "Courier" ).c_str());
449

450
        QFont font(fontFamily, fontSize);
451
        setFont(font);
452
        lineNumberArea->setFont(font);
453
    }
454
    else {
455
        QMap<QString, QColor>::Iterator it = d->colormap.find(QString::fromLatin1(sReason));
456
        if (it != d->colormap.end()) {
457
            QColor color = it.value();
458
            unsigned int col = App::Color::asPackedRGB<QColor>(color);
459
            auto value = static_cast<unsigned long>(col);
460
            value = hPrefGrp->GetUnsigned(sReason, value);
461
            col = static_cast<unsigned int>(value);
462
            color.setRgb((col>>24)&0xff, (col>>16)&0xff, (col>>8)&0xff);
463
            if (this->highlighter)
464
                this->highlighter->setColor(QLatin1String(sReason), color);
465
        }
466
    }
467

468
    if (strcmp(sReason, "TabSize") == 0 || strcmp(sReason, "FontSize") == 0) {
469
        int tabWidth = hPrefGrp->GetInt("TabSize", 4);
470
        QFontMetrics metric(font());
471
        int fontSize = QtTools::horizontalAdvance(metric, QLatin1Char('0'));
472
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
473
        setTabStopWidth(tabWidth * fontSize);
474
#else
475
        setTabStopDistance(tabWidth * fontSize);
476
#endif
477
    }
478

479
    // Enables/Disables Line number in the Macro Editor from Edit->Preferences->Editor menu.
480
    if (strcmp(sReason, "EnableLineNumber") == 0) {
481
        QRect cr = contentsRect();
482
        bool show = hPrefGrp->GetBool("EnableLineNumber", true);
483
        if(show)
484
            lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
485
        else
486
            lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), 0, cr.height()));
487
    }
488

489
    if (strcmp(sReason, "EnableBlockCursor") == 0 ||
490
        strcmp(sReason, "FontSize") == 0 ||
491
        strcmp(sReason, "Font") == 0) {
492
        bool block = hPrefGrp->GetBool("EnableBlockCursor", false);
493
        if (block)
494
            setCursorWidth(QFontMetrics(font()).averageCharWidth());
495
        else
496
            setCursorWidth(1);
497
    }
498
}
499

500
void TextEditor::paintEvent (QPaintEvent * e)
501
{
502
    TextEdit::paintEvent( e );
503
}
504

505
// ------------------------------------------------------------------------------
506

507
LineMarker::LineMarker(TextEditor* editor)
508
    : QWidget(editor), textEditor(editor)
509
{
510
}
511

512
LineMarker::~LineMarker() = default;
513

514
QSize LineMarker::sizeHint() const
515
{
516
    return {textEditor->lineNumberAreaWidth(), 0};
517
}
518

519
void LineMarker::paintEvent(QPaintEvent* e)
520
{
521
    textEditor->lineNumberAreaPaintEvent(e);
522
}
523

524
// ------------------------------------------------------------------------------
525

526
CompletionList::CompletionList(QPlainTextEdit* parent)
527
  :  QListWidget(parent), textEdit(parent)
528
{
529
    // make the user assume that the widget is active
530
    QPalette pal = parent->palette();
531
    pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.color(QPalette::Active, QPalette::Highlight));
532
    pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.color(QPalette::Active, QPalette::HighlightedText));
533
    parent->setPalette( pal );
534

535
    connect(this, &CompletionList::itemActivated,
536
            this, &CompletionList::completionItem);
537
}
538

539
CompletionList::~CompletionList() = default;
540

541
void CompletionList::findCurrentWord(const QString& wordPrefix)
542
{
543
    for (int i=0; i<count(); ++i) {
544
        QString text = item(i)->text();
545
        if (text.startsWith(wordPrefix)) {
546
            setCurrentRow(i);
547
            return;
548
        }
549
    }
550

551
    if (currentItem())
552
        currentItem()->setSelected(false);
553
}
554

555
/**
556
 * Get all incoming events of the text edit and redirect some of them, like key up and
557
 * down, mouse press events, ... to the widget itself.
558
 */
559
bool CompletionList::eventFilter(QObject * watched, QEvent * event)
560
{
561
    if (isVisible() && watched == textEdit->viewport()) {
562
        if (event->type() == QEvent::MouseButtonPress)
563
            hide();
564
    } else if (isVisible() && watched == textEdit) {
565
        if (event->type() == QEvent::KeyPress) {
566
            auto ke = static_cast<QKeyEvent*>(event);
567
            if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down) {
568
                keyPressEvent(ke);
569
                return true;
570
            } else if (ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
571
                keyPressEvent(ke);
572
                return true;
573
            } else if (ke->key() == Qt::Key_Escape) {
574
                hide();
575
                return true;
576
            } else if (ke->key() == Qt::Key_Space) {
577
                hide();
578
                return false;
579
            } else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
580
                Q_EMIT itemActivated(currentItem());
581
                return true;
582
            }
583
        } else if (event->type() == QEvent::FocusOut) {
584
            if (!hasFocus())
585
                hide();
586
        }
587
    }
588

589
    return QListWidget::eventFilter(watched, event);
590
}
591

592
/**
593
 * If an item was chosen (either by clicking or pressing enter) the rest of the word is completed.
594
 * The listbox is closed without destroying it.
595
 */
596
void CompletionList::completionItem(QListWidgetItem *item)
597
{
598
    hide();
599
    QString text = item->text();
600
    QTextCursor cursor = textEdit->textCursor();
601
    cursor.movePosition(QTextCursor::StartOfWord);
602
    cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
603
    cursor.insertText( text );
604
    textEdit->ensureCursorVisible();
605
}
606

607
#include "moc_TextEdit.cpp"
608

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.