729 lines
24 KiB
C++
729 lines
24 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#include <QApplication>
|
|
#include <QKeyEvent>
|
|
#include <QPainter>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QShortcut>
|
|
#include <QTextCursor>
|
|
|
|
#include <FCConfig.h>
|
|
|
|
#include "CallTips.h"
|
|
#include "TextEdit.h"
|
|
#include "SyntaxHighlighter.h"
|
|
#include "Tools.h"
|
|
#include <Base/Color.h>
|
|
|
|
|
|
using namespace Gui;
|
|
|
|
/**
|
|
* Constructs a TextEdit which is a child of 'parent'.
|
|
*/
|
|
TextEdit::TextEdit(QWidget* parent)
|
|
: QPlainTextEdit(parent)
|
|
, cursorPosition(0)
|
|
, listBox(nullptr)
|
|
{
|
|
// create the window for call tips
|
|
callTipsList = new CallTipsList(this);
|
|
callTipsList->setFrameStyle(QFrame::Box);
|
|
callTipsList->setFrameShadow(QFrame::Raised);
|
|
callTipsList->setLineWidth(2);
|
|
installEventFilter(callTipsList);
|
|
viewport()->installEventFilter(callTipsList);
|
|
callTipsList->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
callTipsList->hide();
|
|
|
|
// Note: Set the correct context to this shortcut as we may use several instances of this
|
|
// class at a time
|
|
auto shortcut = new QShortcut(this);
|
|
shortcut->setKey(QKeySequence(QStringLiteral("CTRL+Space")));
|
|
shortcut->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut, &QShortcut::activated, this, &TextEdit::complete);
|
|
|
|
auto shortcutFind = new QShortcut(this);
|
|
shortcutFind->setKey(QKeySequence::Find);
|
|
shortcutFind->setContext(Qt::WidgetShortcut);
|
|
connect(shortcutFind, &QShortcut::activated, this, &TextEdit::showSearchBar);
|
|
|
|
auto shortcutNext = new QShortcut(this);
|
|
shortcutNext->setKey(QKeySequence::FindNext);
|
|
shortcutNext->setContext(Qt::WidgetShortcut);
|
|
connect(shortcutNext, &QShortcut::activated, this, &TextEdit::findNext);
|
|
|
|
auto shortcutPrev = new QShortcut(this);
|
|
shortcutPrev->setKey(QKeySequence::FindPrevious);
|
|
shortcutPrev->setContext(Qt::WidgetShortcut);
|
|
connect(shortcutPrev, &QShortcut::activated, this, &TextEdit::findPrevious);
|
|
}
|
|
|
|
/** Destroys the object and frees any allocated resources */
|
|
TextEdit::~TextEdit() = default;
|
|
|
|
/**
|
|
* Set the approproriate item of the completion box or hide it, if needed.
|
|
*/
|
|
void TextEdit::keyPressEvent(QKeyEvent* e)
|
|
{
|
|
// We want input to be appended to document before doing calltips
|
|
QPlainTextEdit::keyPressEvent(e);
|
|
|
|
// This can't be done in CompletionList::eventFilter() because we must first perform
|
|
// the event and afterwards update the list widget
|
|
if (listBox && listBox->isVisible()) {
|
|
// Get the word under the cursor
|
|
QTextCursor cursor = textCursor();
|
|
cursor.movePosition(QTextCursor::StartOfWord);
|
|
// the cursor has moved to outside the word prefix
|
|
if (cursor.position() < cursorPosition - wordPrefix.length()
|
|
|| cursor.position() > cursorPosition) {
|
|
listBox->hide();
|
|
return;
|
|
}
|
|
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
|
|
listBox->keyboardSearch(cursor.selectedText());
|
|
cursor.clearSelection();
|
|
}
|
|
|
|
|
|
if (e->key() == Qt::Key_Period) {
|
|
// QTextCursor cursor = this->textCursor();
|
|
// In Qt 4.8 there is a strange behaviour because when pressing ":"
|
|
// then key is also set to 'Period' instead of 'Colon'. So we have
|
|
// to make sure we only handle the period.
|
|
if (e->text() == QLatin1String(".")) {
|
|
// analyse context and show available call tips
|
|
// TODO: idk why we need to remove the . from the input string (- 1). This shouldn't be
|
|
// needed
|
|
QString textToBeCompleted = getInputString().left(getInputStringPosition() - 1);
|
|
callTipsList->showTips(textToBeCompleted);
|
|
}
|
|
}
|
|
|
|
// This can't be done in CallTipsList::eventFilter() because we must first perform
|
|
// the event and afterwards update the list widget
|
|
if (callTipsList->isVisible()) {
|
|
callTipsList->validateCursor();
|
|
}
|
|
}
|
|
|
|
int TextEdit::getInputStringPosition()
|
|
{
|
|
return textCursor().positionInBlock();
|
|
}
|
|
|
|
QString TextEdit::getInputString()
|
|
{
|
|
return textCursor().block().text();
|
|
}
|
|
|
|
void TextEdit::wheelEvent(QWheelEvent* e)
|
|
{
|
|
// Reimplement from QPlainText::wheelEvent as zoom is only allowed natively if !isReadOnly
|
|
if (e->modifiers() & Qt::ControlModifier) {
|
|
float delta = e->angleDelta().y() / 120.f;
|
|
zoomInF(delta);
|
|
return;
|
|
}
|
|
QPlainTextEdit::wheelEvent(e);
|
|
}
|
|
|
|
/**
|
|
* Completes the word.
|
|
*/
|
|
void TextEdit::complete()
|
|
{
|
|
QTextBlock block = textCursor().block();
|
|
if (!block.isValid()) {
|
|
return;
|
|
}
|
|
int cursorPos = textCursor().position() - block.position();
|
|
QString para = block.text();
|
|
int wordStart = cursorPos;
|
|
while (wordStart > 0 && para[wordStart - 1].isLetterOrNumber()) {
|
|
--wordStart;
|
|
}
|
|
wordPrefix = para.mid(wordStart, cursorPos - wordStart);
|
|
if (wordPrefix.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QStringList list = toPlainText().split(QRegularExpression(QLatin1String("\\W+")));
|
|
QMap<QString, QString> map;
|
|
QStringList::Iterator it = list.begin();
|
|
while (it != list.end()) {
|
|
if ((*it).startsWith(wordPrefix) && (*it).length() > wordPrefix.length()) {
|
|
map[(*it).toLower()] = *it;
|
|
}
|
|
++it;
|
|
}
|
|
|
|
if (map.count() == 1) {
|
|
insertPlainText((*map.begin()).mid(wordPrefix.length()));
|
|
}
|
|
else if (map.count() > 1) {
|
|
if (!listBox) {
|
|
createListBox();
|
|
}
|
|
listBox->clear();
|
|
listBox->addItems(map.values());
|
|
listBox->setFont(QFont(font().family(), 8));
|
|
|
|
this->cursorPosition = textCursor().position();
|
|
|
|
// get the minimum width and height of the box
|
|
int h = 0;
|
|
int w = 0;
|
|
for (int i = 0; i < listBox->count(); ++i) {
|
|
QRect r = listBox->visualItemRect(listBox->item(i));
|
|
w = qMax(w, r.width());
|
|
h += r.height();
|
|
}
|
|
|
|
// Add an offset
|
|
w += 2 * listBox->frameWidth();
|
|
h += 2 * listBox->frameWidth();
|
|
|
|
// get the start position of the word prefix
|
|
QTextCursor cursor = textCursor();
|
|
cursor.movePosition(QTextCursor::StartOfWord);
|
|
QRect rect = cursorRect(cursor);
|
|
int posX = rect.x();
|
|
int posY = rect.y();
|
|
int boxH = h;
|
|
|
|
// Decide whether to show downstairs or upstairs
|
|
if (posY > viewport()->height() / 2) {
|
|
h = qMin(qMin(h, posY), 250);
|
|
if (h < boxH) {
|
|
w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
|
|
}
|
|
listBox->setGeometry(posX, posY - h, w, h);
|
|
}
|
|
else {
|
|
h = qMin(qMin(h, viewport()->height() - fontMetrics().height() - posY), 250);
|
|
if (h < boxH) {
|
|
w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
|
|
}
|
|
listBox->setGeometry(posX, posY + fontMetrics().height(), w, h);
|
|
}
|
|
|
|
listBox->setCurrentRow(0);
|
|
listBox->show();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the listbox containing all possibilities for the completion.
|
|
* The listbox is closed when ESC is pressed, the text edit field loses focus or a
|
|
* mouse button was pressed.
|
|
*/
|
|
void TextEdit::createListBox()
|
|
{
|
|
listBox = new CompletionList(this);
|
|
listBox->setFrameStyle(QFrame::Box);
|
|
listBox->setFrameShadow(QFrame::Raised);
|
|
listBox->setLineWidth(2);
|
|
installEventFilter(listBox);
|
|
viewport()->installEventFilter(listBox);
|
|
listBox->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
listBox->hide();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
|
|
namespace Gui
|
|
{
|
|
struct TextEditorP
|
|
{
|
|
bool highlightLine = true;
|
|
bool visibleMarker = true;
|
|
QMap<QString, QColor> colormap; // Color map
|
|
TextEditorP()
|
|
{
|
|
colormap[QLatin1String("Text")] = qApp->palette().windowText().color();
|
|
colormap[QLatin1String("Bookmark")] = Qt::cyan;
|
|
colormap[QLatin1String("Breakpoint")] = Qt::red;
|
|
colormap[QLatin1String("Keyword")] = Qt::blue;
|
|
colormap[QLatin1String("Comment")] = QColor(0, 170, 0);
|
|
colormap[QLatin1String("Block comment")] = QColor(160, 160, 164);
|
|
colormap[QLatin1String("Number")] = Qt::blue;
|
|
colormap[QLatin1String("String")] = Qt::red;
|
|
colormap[QLatin1String("Character")] = Qt::red;
|
|
colormap[QLatin1String("Class name")] = QColor(255, 170, 0);
|
|
colormap[QLatin1String("Define name")] = QColor(255, 170, 0);
|
|
colormap[QLatin1String("Operator")] = QColor(160, 160, 164);
|
|
colormap[QLatin1String("Python output")] = QColor(170, 170, 127);
|
|
colormap[QLatin1String("Python error")] = Qt::red;
|
|
colormap[QLatin1String("Current line highlight")] = QColor(224, 224, 224);
|
|
}
|
|
};
|
|
} // namespace Gui
|
|
|
|
/**
|
|
* Constructs a TextEditor which is a child of 'parent' and does the
|
|
* syntax highlighting for the Python language.
|
|
*/
|
|
TextEditor::TextEditor(QWidget* parent)
|
|
: TextEdit(parent)
|
|
, WindowParameter("Editor")
|
|
, highlighter(nullptr)
|
|
{
|
|
d = new TextEditorP();
|
|
lineNumberArea = new LineMarker(this);
|
|
|
|
QFont serifFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
serifFont.setPointSize(10);
|
|
setFont(serifFont);
|
|
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
hPrefGrp->Attach(this);
|
|
|
|
// set colors and font
|
|
hPrefGrp->NotifyAll();
|
|
|
|
connect(this, &QPlainTextEdit::cursorPositionChanged, this, &TextEditor::highlightCurrentLine);
|
|
connect(this, &QPlainTextEdit::blockCountChanged, this, &TextEditor::updateLineNumberAreaWidth);
|
|
connect(this, &QPlainTextEdit::updateRequest, this, &TextEditor::updateLineNumberArea);
|
|
|
|
updateLineNumberAreaWidth(0);
|
|
highlightCurrentLine();
|
|
}
|
|
|
|
/** Destroys the object and frees any allocated resources */
|
|
TextEditor::~TextEditor()
|
|
{
|
|
getWindowParameter()->Detach(this);
|
|
delete highlighter;
|
|
delete d;
|
|
}
|
|
|
|
void TextEditor::setVisibleLineNumbers(bool value)
|
|
{
|
|
lineNumberArea->setVisible(value);
|
|
d->visibleMarker = value;
|
|
}
|
|
|
|
bool TextEditor::isVisibleLineNumbers() const
|
|
{
|
|
return d->visibleMarker;
|
|
}
|
|
|
|
void TextEditor::setEnabledHighlightCurrentLine(bool value)
|
|
{
|
|
d->highlightLine = value;
|
|
}
|
|
|
|
bool TextEditor::isEnabledHighlightCurrentLine() const
|
|
{
|
|
return d->highlightLine;
|
|
}
|
|
|
|
int TextEditor::lineNumberAreaWidth()
|
|
{
|
|
return QtTools::horizontalAdvance(fontMetrics(), QLatin1String("0000")) + 10;
|
|
}
|
|
|
|
void TextEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
|
|
{
|
|
int left = isVisibleLineNumbers() ? lineNumberAreaWidth() : 0;
|
|
setViewportMargins(left, 0, 0, 0);
|
|
}
|
|
|
|
void TextEditor::updateLineNumberArea(const QRect& rect, int dy)
|
|
{
|
|
if (isVisibleLineNumbers()) {
|
|
if (dy) {
|
|
lineNumberArea->scroll(0, dy);
|
|
}
|
|
else {
|
|
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
|
|
}
|
|
|
|
if (rect.contains(viewport()->rect())) {
|
|
updateLineNumberAreaWidth(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextEditor::resizeEvent(QResizeEvent* e)
|
|
{
|
|
QPlainTextEdit::resizeEvent(e);
|
|
|
|
if (isVisibleLineNumbers()) {
|
|
QRect cr = contentsRect();
|
|
int width = lineNumberAreaWidth();
|
|
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), width, cr.height()));
|
|
}
|
|
}
|
|
|
|
void TextEditor::highlightCurrentLine()
|
|
{
|
|
QList<QTextEdit::ExtraSelection> extraSelections;
|
|
|
|
if (!isReadOnly() && isEnabledHighlightCurrentLine()) {
|
|
QTextEdit::ExtraSelection selection;
|
|
QColor lineColor = d->colormap[QLatin1String("Current line highlight")];
|
|
unsigned int col = Base::Color::asPackedRGB<QColor>(lineColor);
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
auto value = static_cast<unsigned long>(col);
|
|
value = hPrefGrp->GetUnsigned("Current line highlight", value);
|
|
col = static_cast<unsigned int>(value);
|
|
lineColor.setRgb((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff);
|
|
selection.format.setBackground(lineColor);
|
|
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
|
selection.cursor = textCursor();
|
|
selection.cursor.clearSelection();
|
|
extraSelections.append(selection);
|
|
}
|
|
|
|
setExtraSelections(extraSelections);
|
|
}
|
|
|
|
void TextEditor::drawMarker(int line, int x, int y, QPainter* p)
|
|
{
|
|
Q_UNUSED(line);
|
|
Q_UNUSED(x);
|
|
Q_UNUSED(y);
|
|
Q_UNUSED(p);
|
|
}
|
|
|
|
void TextEditor::lineNumberAreaPaintEvent(QPaintEvent* event)
|
|
{
|
|
if (!isVisibleLineNumbers()) {
|
|
return;
|
|
}
|
|
QPainter painter(lineNumberArea);
|
|
// painter.fillRect(event->rect(), Qt::lightGray);
|
|
|
|
QTextBlock block = firstVisibleBlock();
|
|
int blockNumber = block.blockNumber();
|
|
int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top();
|
|
int bottom = top + (int)blockBoundingRect(block).height();
|
|
|
|
while (block.isValid() && top <= event->rect().bottom()) {
|
|
if (block.isVisible() && bottom >= event->rect().top()) {
|
|
QString number = QString::number(blockNumber + 1);
|
|
QPalette pal = palette();
|
|
QColor color = pal.windowText().color();
|
|
painter.setPen(color);
|
|
painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number);
|
|
drawMarker(blockNumber + 1, 1, top, &painter);
|
|
}
|
|
|
|
block = block.next();
|
|
top = bottom;
|
|
bottom = top + (int)blockBoundingRect(block).height();
|
|
++blockNumber;
|
|
}
|
|
}
|
|
|
|
void TextEditor::setSyntaxHighlighter(SyntaxHighlighter* sh)
|
|
{
|
|
sh->setDocument(this->document());
|
|
this->highlighter = sh;
|
|
}
|
|
|
|
/** Sets the font, font size and tab size of the editor. */
|
|
void TextEditor::OnChange(Base::Subject<const char*>& rCaller, const char* sReason)
|
|
{
|
|
Q_UNUSED(rCaller);
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
|
|
#ifdef FC_OS_LINUX
|
|
int fontSize = hPrefGrp->GetInt("FontSize", 15);
|
|
#else
|
|
int fontSize = hPrefGrp->GetInt("FontSize", 10);
|
|
#endif
|
|
QFont font;
|
|
auto fontName = hPrefGrp->GetASCII("Font");
|
|
if (fontName.empty()) {
|
|
font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
font.setPointSize(fontSize);
|
|
}
|
|
else {
|
|
font = QFont(QString::fromStdString(fontName), fontSize);
|
|
}
|
|
setFont(font);
|
|
lineNumberArea->setFont(font);
|
|
}
|
|
else {
|
|
QMap<QString, QColor>::Iterator it = d->colormap.find(QString::fromLatin1(sReason));
|
|
if (it != d->colormap.end()) {
|
|
QColor color = it.value();
|
|
unsigned int col = Base::Color::asPackedRGB<QColor>(color);
|
|
auto value = static_cast<unsigned long>(col);
|
|
value = hPrefGrp->GetUnsigned(sReason, value);
|
|
col = static_cast<unsigned int>(value);
|
|
color.setRgb((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff);
|
|
if (this->highlighter) {
|
|
this->highlighter->setColor(QLatin1String(sReason), color);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strcmp(sReason, "TabSize") == 0 || strcmp(sReason, "FontSize") == 0) {
|
|
int tabWidth = hPrefGrp->GetInt("TabSize", 4);
|
|
QFontMetrics metric(font());
|
|
int fontSize = QtTools::horizontalAdvance(metric, QLatin1Char('0'));
|
|
setTabStopDistance(tabWidth * fontSize);
|
|
}
|
|
|
|
// Enables/Disables Line number in the Macro Editor from Edit->Preferences->Editor menu.
|
|
if (strcmp(sReason, "EnableLineNumber") == 0) {
|
|
int width = 0;
|
|
QRect cr = contentsRect();
|
|
if (hPrefGrp->GetBool("EnableLineNumber", true)) {
|
|
width = lineNumberAreaWidth();
|
|
}
|
|
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), width, cr.height()));
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
|
|
PythonTextEditor::PythonTextEditor(QWidget* parent)
|
|
: TextEditor(parent)
|
|
{}
|
|
|
|
PythonTextEditor::~PythonTextEditor() = default;
|
|
|
|
void PythonTextEditor::prepend(const QString& str)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
// for each selected block insert a tab or spaces
|
|
int selStart = cursor.selectionStart();
|
|
int selEnd = cursor.selectionEnd();
|
|
QTextBlock block;
|
|
cursor.beginEditBlock();
|
|
for (block = document()->begin(); block.isValid(); block = block.next()) {
|
|
int pos = block.position();
|
|
int off = block.length() - 1;
|
|
// at least one char of the block is part of the selection
|
|
if (pos >= selStart || pos + off >= selStart) {
|
|
if (pos + 1 > selEnd) {
|
|
break; // end of selection reached
|
|
}
|
|
cursor.setPosition(block.position());
|
|
cursor.insertText(str);
|
|
selEnd += str.length();
|
|
}
|
|
}
|
|
|
|
cursor.endEditBlock();
|
|
}
|
|
|
|
void PythonTextEditor::remove(const QString& str)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
int selStart = cursor.selectionStart();
|
|
int selEnd = cursor.selectionEnd();
|
|
QTextBlock block;
|
|
cursor.beginEditBlock();
|
|
for (block = document()->begin(); block.isValid(); block = block.next()) {
|
|
int pos = block.position();
|
|
int off = block.length() - 1;
|
|
// at least one char of the block is part of the selection
|
|
if (pos >= selStart || pos + off >= selStart) {
|
|
if (pos + 1 > selEnd) {
|
|
break; // end of selection reached
|
|
}
|
|
QString text = block.text();
|
|
if (text.startsWith(str)) {
|
|
cursor.setPosition(block.position());
|
|
for (int i = 0; i < str.length(); i++) {
|
|
cursor.deleteChar();
|
|
selEnd--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cursor.endEditBlock();
|
|
}
|
|
|
|
void PythonTextEditor::keyPressEvent(QKeyEvent* e)
|
|
{
|
|
if (e->key() == Qt::Key_Tab) {
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
bool space = hPrefGrp->GetBool("Spaces", true);
|
|
int indent = hPrefGrp->GetInt("IndentSize", 4);
|
|
QString ch = space ? QString(indent, QLatin1Char(' ')) : QStringLiteral("\t");
|
|
|
|
QTextCursor cursor = textCursor();
|
|
if (!cursor.hasSelection()) {
|
|
// insert a single tab or several spaces
|
|
cursor.beginEditBlock();
|
|
cursor.insertText(ch);
|
|
cursor.endEditBlock();
|
|
}
|
|
else {
|
|
prepend(ch);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (e->key() == Qt::Key_Backtab) {
|
|
QTextCursor cursor = textCursor();
|
|
if (!cursor.hasSelection()) {
|
|
return; // Shift+Tab should not do anything
|
|
}
|
|
// If some text is selected we remove a leading tab or
|
|
// spaces from each selected block
|
|
ParameterGrp::handle hPrefGrp = getWindowParameter();
|
|
bool space = hPrefGrp->GetBool("Spaces", true);
|
|
int indent = hPrefGrp->GetInt("IndentSize", 4);
|
|
QString ch = space ? QString(indent, QLatin1Char(' ')) : QStringLiteral("\t");
|
|
|
|
// if possible remove one tab or several spaces
|
|
remove(ch);
|
|
return;
|
|
}
|
|
|
|
TextEditor::keyPressEvent(e);
|
|
}
|
|
|
|
|
|
LineMarker::LineMarker(TextEditor* editor)
|
|
: QWidget(editor)
|
|
, textEditor(editor)
|
|
{}
|
|
|
|
LineMarker::~LineMarker() = default;
|
|
|
|
QSize LineMarker::sizeHint() const
|
|
{
|
|
return {textEditor->lineNumberAreaWidth(), 0};
|
|
}
|
|
|
|
void LineMarker::paintEvent(QPaintEvent* e)
|
|
{
|
|
textEditor->lineNumberAreaPaintEvent(e);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
|
|
CompletionList::CompletionList(QPlainTextEdit* parent)
|
|
: QListWidget(parent)
|
|
, textEdit(parent)
|
|
{
|
|
// make the user assume that the widget is active
|
|
QPalette pal = parent->palette();
|
|
pal.setColor(
|
|
QPalette::Inactive,
|
|
QPalette::Highlight,
|
|
pal.color(QPalette::Active, QPalette::Highlight)
|
|
);
|
|
pal.setColor(
|
|
QPalette::Inactive,
|
|
QPalette::HighlightedText,
|
|
pal.color(QPalette::Active, QPalette::HighlightedText)
|
|
);
|
|
parent->setPalette(pal);
|
|
|
|
connect(this, &CompletionList::itemActivated, this, &CompletionList::completionItem);
|
|
}
|
|
|
|
CompletionList::~CompletionList() = default;
|
|
|
|
void CompletionList::findCurrentWord(const QString& wordPrefix)
|
|
{
|
|
for (int i = 0; i < count(); ++i) {
|
|
QString text = item(i)->text();
|
|
if (text.startsWith(wordPrefix)) {
|
|
setCurrentRow(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (currentItem()) {
|
|
currentItem()->setSelected(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all incoming events of the text edit and redirect some of them, like key up and
|
|
* down, mouse press events, ... to the widget itself.
|
|
*/
|
|
bool CompletionList::eventFilter(QObject* watched, QEvent* event)
|
|
{
|
|
if (isVisible() && watched == textEdit->viewport()) {
|
|
if (event->type() == QEvent::MouseButtonPress) {
|
|
hide();
|
|
}
|
|
}
|
|
else if (isVisible() && watched == textEdit) {
|
|
if (event->type() == QEvent::KeyPress) {
|
|
auto ke = static_cast<QKeyEvent*>(event);
|
|
if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down) {
|
|
keyPressEvent(ke);
|
|
return true;
|
|
}
|
|
else if (ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
|
|
keyPressEvent(ke);
|
|
return true;
|
|
}
|
|
else if (ke->key() == Qt::Key_Escape) {
|
|
hide();
|
|
return true;
|
|
}
|
|
else if (ke->key() == Qt::Key_Space) {
|
|
hide();
|
|
return false;
|
|
}
|
|
else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
|
Q_EMIT itemActivated(currentItem());
|
|
return true;
|
|
}
|
|
}
|
|
else if (event->type() == QEvent::FocusOut) {
|
|
if (!hasFocus()) {
|
|
hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
return QListWidget::eventFilter(watched, event);
|
|
}
|
|
|
|
/**
|
|
* If an item was chosen (either by clicking or pressing enter) the rest of the word is completed.
|
|
* The listbox is closed without destroying it.
|
|
*/
|
|
void CompletionList::completionItem(QListWidgetItem* item)
|
|
{
|
|
hide();
|
|
QString text = item->text();
|
|
QTextCursor cursor = textEdit->textCursor();
|
|
cursor.movePosition(QTextCursor::StartOfWord);
|
|
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
|
|
cursor.insertText(text);
|
|
textEdit->ensureCursorVisible();
|
|
}
|
|
|
|
#include "moc_TextEdit.cpp"
|