1
/***************************************************************************
2
* Copyright (c) 2004 Werner Mayer <werner.wm.mayer@gmx.de> *
4
* This file is part of the FreeCAD CAx development system. *
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. *
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. *
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 *
21
***************************************************************************/
24
#include "PreCompiled.h"
30
#include "SyntaxHighlighter.h"
35
* Constructs a TextEdit which is a child of 'parent'.
37
TextEdit::TextEdit(QWidget* parent)
38
: QTextEdit(parent), listBox(0)
40
//Note: Set the correct context to this shortcut as we may use several instances of this
42
QShortcut* shortcut = new QShortcut(this);
43
shortcut->setKey(Qt::CTRL+Qt::Key_Space);
44
shortcut->setContext(Qt::WidgetShortcut);
45
connect(shortcut, SIGNAL(activated()), this, SLOT(complete()));
48
/** Destroys the object and frees any allocated resources */
54
* Set the approproriate item of the completion box or hide it, if needed.
56
void TextEdit::keyPressEvent(QKeyEvent* e)
58
QTextEdit::keyPressEvent(e);
59
// This can't be done in CompletionList::eventFilter() because we must first perform
60
// the event and afterwards update the list widget
61
if (listBox && listBox->isVisible()) {
62
// Get the word under the cursor
63
QTextCursor cursor = textCursor();
64
cursor.movePosition(QTextCursor::StartOfWord);
65
// the cursor has moved to outside the word prefix
66
if (cursor.position() < cursorPosition-wordPrefix.length() ||
67
cursor.position() > cursorPosition) {
71
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
72
listBox->keyboardSearch(cursor.selectedText());
73
cursor.clearSelection();
80
void TextEdit::complete()
82
QTextBlock block = textCursor().block();
85
int cursorPos = textCursor().position()-block.position();
86
QString para = block.text();
87
int wordStart = cursorPos;
88
while (wordStart > 0 && para[wordStart - 1].isLetterOrNumber())
90
wordPrefix = para.mid(wordStart, cursorPos - wordStart);
91
if (wordPrefix.isEmpty())
94
QStringList list = toPlainText().split(QRegExp(QLatin1String("\\W+")));
95
QMap<QString, QString> map;
96
QStringList::Iterator it = list.begin();
97
while (it != list.end()) {
98
if ((*it).startsWith(wordPrefix) && (*it).length() > wordPrefix.length())
99
map[(*it).toLower()] = *it;
103
if (map.count() == 1) {
104
insertPlainText((*map.begin()).mid(wordPrefix.length()));
105
} else if (map.count() > 1) {
109
listBox->addItems(map.values());
110
listBox->setFont(QFont(font().family(), 8));
112
this->cursorPosition = textCursor().position();
114
// get the minimum width and height of the box
117
for (int i = 0; i < listBox->count(); ++i) {
118
QRect r = listBox->visualItemRect(listBox->item(i));
119
w = qMax(w, r.width());
124
w += 2*listBox->frameWidth();
125
h += 2*listBox->frameWidth();
127
// get the start position of the word prefix
128
QTextCursor cursor = textCursor();
129
cursor.movePosition(QTextCursor::StartOfWord);
130
QRect rect = cursorRect(cursor);
135
// Decide whether to show downstairs or upstairs
136
if (posY > viewport()->height()/2) {
137
h = qMin(qMin(h,posY), 250);
139
w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
140
listBox->setGeometry(posX,posY-h, w, h);
142
h = qMin(qMin(h,viewport()->height()-fontMetrics().height()-posY), 250);
144
w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
145
listBox->setGeometry(posX, posY+fontMetrics().height(), w, h);
148
listBox->setCurrentRow(0);
154
* Creates the listbox containing all possibilities for the completion.
155
* The listbox is closed when ESC is pressed, the text edit field looses focus or a
156
* mouse button was pressed.
158
void TextEdit::createListBox()
160
listBox = new CompletionList(this);
161
listBox->setFrameStyle(QFrame::Box|QFrame::Raised);
162
listBox->setLineWidth(2);
163
installEventFilter(listBox);
164
viewport()->installEventFilter(listBox);
165
listBox->setSelectionMode( QAbstractItemView::SingleSelection );
169
// ------------------------------------------------------------------------------
174
QMap<QString, QColor> colormap; // Color map
177
colormap[QLatin1String("Text")] = Qt::black;
178
colormap[QLatin1String("Bookmark")] = Qt::cyan;
179
colormap[QLatin1String("Breakpoint")] = Qt::red;
180
colormap[QLatin1String("Keyword")] = Qt::blue;
181
colormap[QLatin1String("Comment")] = QColor(0, 170, 0);
182
colormap[QLatin1String("Block comment")] = QColor(160, 160, 164);
183
colormap[QLatin1String("Number")] = Qt::blue;
184
colormap[QLatin1String("String")] = Qt::red;
185
colormap[QLatin1String("Character")] = Qt::red;
186
colormap[QLatin1String("Class name")] = QColor(255, 170, 0);
187
colormap[QLatin1String("Define name")] = QColor(255, 170, 0);
188
colormap[QLatin1String("Operator")] = QColor(160, 160, 164);
189
colormap[QLatin1String("Python output")] = QColor(170, 170, 127);
190
colormap[QLatin1String("Python error")] = Qt::red;
191
colormap[QLatin1String("Line")] = QColor(224,224,224);
197
* Constructs a TextEditor which is a child of 'parent' and does the
198
* syntax highlighting for the Python language.
200
TextEditor::TextEditor(QWidget* parent)
201
: TextEdit(parent), WindowParameter("Editor"), highlighter(0)
203
d = new TextEditorP();
206
QFont serifFont(QLatin1String("Courier"), 15, QFont::Normal );
208
QFont serifFont(QLatin1String("Courier"), 10, QFont::Normal );
212
ParameterGrp::handle hPrefGrp = getWindowParameter();
213
// set default to 4 characters
214
hPrefGrp->SetInt( "TabSize", 4 );
215
hPrefGrp->Attach( this );
217
// set colors and font
218
hPrefGrp->NotifyAll();
219
connect(this, SIGNAL(cursorPositionChanged()),
220
this, SLOT(onCursorPositionChanged()));
223
/** Destroys the object and frees any allocated resources */
224
TextEditor::~TextEditor()
226
getWindowParameter()->Detach(this);
231
void TextEditor::keyPressEvent (QKeyEvent * e)
233
if ( e->key() == Qt::Key_Tab ) {
234
ParameterGrp::handle hPrefGrp = getWindowParameter();
235
int indent = hPrefGrp->GetInt( "IndentSize", 4 );
236
bool space = hPrefGrp->GetBool( "Spaces", false );
237
QString ch = space ? QString(indent, QLatin1Char(' '))
238
: QString::fromAscii("\t");
240
QTextCursor cursor = textCursor();
241
if (!cursor.hasSelection()) {
242
// insert a single tab or several spaces
243
cursor.beginEditBlock();
244
cursor.insertText(ch);
245
cursor.endEditBlock();
247
// for each selected block insert a tab or spaces
248
int selStart = cursor.selectionStart();
249
int selEnd = cursor.selectionEnd();
251
cursor.beginEditBlock();
252
for (block = document()->begin(); block.isValid(); block = block.next()) {
253
int pos = block.position();
254
int off = block.length()-1;
255
// at least one char of the block is part of the selection
256
if ( pos >= selStart || pos+off >= selStart) {
257
if ( pos+1 > selEnd )
258
break; // end of selection reached
259
cursor.setPosition(block.position());
260
cursor.insertText(ch);
261
selEnd += ch.length();
265
cursor.endEditBlock();
269
} else if (e->key() == Qt::Key_Backtab) {
270
QTextCursor cursor = textCursor();
271
if (!cursor.hasSelection())
272
return; // Shift+Tab should not do anything
273
// If some text is selected we remove a leading tab or
274
// spaces from each selected block
275
ParameterGrp::handle hPrefGrp = getWindowParameter();
276
int indent = hPrefGrp->GetInt( "IndentSize", 4 );
278
int selStart = cursor.selectionStart();
279
int selEnd = cursor.selectionEnd();
281
cursor.beginEditBlock();
282
for (block = document()->begin(); block.isValid(); block = block.next()) {
283
int pos = block.position();
284
int off = block.length()-1;
285
// at least one char of the block is part of the selection
286
if ( pos >= selStart || pos+off >= selStart) {
287
if ( pos+1 > selEnd )
288
break; // end of selection reached
289
// if possible remove one tab or several spaces
290
QString text = block.text();
291
if (text.startsWith(QLatin1String("\t"))) {
292
cursor.setPosition(block.position());
296
cursor.setPosition(block.position());
297
for (int i=0; i<indent; i++) {
298
if (!text.startsWith(QLatin1String(" ")))
308
cursor.endEditBlock();
312
TextEdit::keyPressEvent( e );
315
/** Sets the font, font size and tab size of the editor. */
316
void TextEditor::OnChange(Base::Subject<const char*> &rCaller,const char* sReason)
318
ParameterGrp::handle hPrefGrp = getWindowParameter();
319
if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
321
int fontSize = hPrefGrp->GetInt("FontSize", 15);
323
int fontSize = hPrefGrp->GetInt("FontSize", 10);
325
QString fontFamily = QString::fromAscii(hPrefGrp->GetASCII( "Font", "Courier" ).c_str());
327
QFont font(fontFamily, fontSize);
330
QMap<QString, QColor>::ConstIterator it = d->colormap.find(QString::fromAscii(sReason));
331
if (it != d->colormap.end()) {
332
QColor color = it.value();
333
unsigned long col = (color.red() << 24) | (color.green() << 16) | (color.blue() << 8);
334
col = hPrefGrp->GetUnsigned( sReason, col);
335
color.setRgb((col>>24)&0xff, (col>>16)&0xff, (col>>8)&0xff);
336
if (this->highlighter)
337
this->highlighter->setColor(QLatin1String(sReason), color);
341
if (strcmp(sReason, "TabSize") == 0 || strcmp(sReason, "FontSize") == 0) {
342
int tabWidth = hPrefGrp->GetInt("TabSize", 4);
343
QFontMetrics metric(font());
344
int fontSize = metric.width(QLatin1String("0"));
345
setTabStopWidth(tabWidth * fontSize);
349
void TextEditor::onCursorPositionChanged()
351
const QColor& color = d->colormap[QLatin1String("Line")];
352
if ( color.isValid() )
353
viewport()->update();
356
void TextEditor::paintEvent (QPaintEvent * e)
358
const QColor& color = d->colormap[QLatin1String("Line")];
359
if ( color.isValid() )
361
QPainter painter( viewport() );
362
QRect r = cursorRect();
364
r.setWidth( viewport()->width() );
365
painter.fillRect( r, QBrush( color ) );
369
TextEdit::paintEvent( e );
372
// ------------------------------------------------------------------------------
374
CompletionList::CompletionList(QTextEdit* parent)
375
: QListWidget(parent), textEdit(parent)
377
// make the user assume that the widget is active
378
QPalette pal = parent->palette();
379
pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.color(QPalette::Active, QPalette::Highlight));
380
pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.color(QPalette::Active, QPalette::HighlightedText));
381
parent->setPalette( pal );
383
connect(this, SIGNAL(itemActivated(QListWidgetItem *)),
384
this, SLOT(completionItem(QListWidgetItem *)));
387
CompletionList::~CompletionList()
391
void CompletionList::findCurrentWord(const QString& wordPrefix)
393
for (int i=0; i<count(); ++i) {
394
QString text = item(i)->text();
395
if (text.startsWith(wordPrefix)) {
401
setItemSelected(currentItem(), false);
405
* Get all incoming events of the text edit and redirect some of them, like key up and
406
* down, mouse press events, ... to the widget itself.
408
bool CompletionList::eventFilter(QObject * watched, QEvent * event)
410
if (isVisible() && watched == textEdit->viewport()) {
411
if (event->type() == QEvent::MouseButtonPress)
413
} else if (isVisible() && watched == textEdit) {
414
if (event->type() == QEvent::KeyPress) {
415
QKeyEvent* ke = (QKeyEvent*)event;
416
if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down) {
419
} else if (ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
422
} else if (ke->key() == Qt::Key_Escape) {
425
} else if (ke->key() == Qt::Key_Space) {
428
} else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
429
itemActivated(currentItem());
432
} else if (event->type() == QEvent::FocusOut) {
438
return QListWidget::eventFilter(watched, event);
442
* If an item was chosen (either by clicking or pressing enter) the rest of the word is completed.
443
* The listbox is closed without destroying it.
445
void CompletionList::completionItem(QListWidgetItem *item)
448
QString text = item->text();
449
QTextCursor cursor = textEdit->textCursor();
450
cursor.movePosition(QTextCursor::StartOfWord);
451
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
452
cursor.insertText( text );
453
textEdit->ensureCursorVisible();
456
#include "moc_TextEdit.cpp"