Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ElmerCSC
GitHub Repository: ElmerCSC/elmerfem
Path: blob/devel/ElmerGUI/PythonQt/src/gui/PythonQtScriptingConsole.cpp
5160 views
1
/*
2
*
3
* Copyright (C) 2006 MeVis Research GmbH All Rights Reserved.
4
*
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Lesser General Public
7
* License as published by the Free Software Foundation; either
8
* version 2.1 of the License, or (at your option) any later version.
9
*
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Lesser General Public License for more details.
14
*
15
* Further, this software is distributed without any warranty that it is
16
* free of the rightful claim of any third person regarding infringement
17
* or the like. Any license provided herein, whether implied or
18
* otherwise, applies only to this software file. Patent licenses, if
19
* any, provided herein do not apply to combinations of this program with
20
* other software, or any other product whatsoever.
21
*
22
* You should have received a copy of the GNU Lesser General Public
23
* License along with this library; if not, write to the Free Software
24
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
*
26
* Contact information: MeVis Research GmbH, Universitaetsallee 29,
27
* 28359 Bremen, Germany or:
28
*
29
* http://www.mevis.de
30
*
31
*/
32
33
//----------------------------------------------------------------------------------
34
/*!
35
// \file PythonQtScriptingConsole.cpp
36
// \author Florian Link
37
// \author Last changed by $Author: florian $
38
// \date 2006-10
39
*/
40
//----------------------------------------------------------------------------------
41
42
#include "PythonQtScriptingConsole.h"
43
44
#include <QApplication>
45
#include <QCompleter>
46
#include <QDebug>
47
#include <QKeyEvent>
48
#include <QMenu>
49
#include <QMouseEvent>
50
#include <QScrollBar>
51
#include <QStringListModel>
52
#include <QTextBlock>
53
#include <QTextCursor>
54
#include <QTextDocumentFragment>
55
56
57
//-----------------------------------------------------------------------------
58
59
PythonQtScriptingConsole::PythonQtScriptingConsole(
60
QWidget *parent, const PythonQtObjectPtr &context,
61
Qt::WindowFlags windowFlags)
62
: QTextEdit(parent) {
63
64
setWindowFlags(windowFlags);
65
66
_defaultTextCharacterFormat = currentCharFormat();
67
_context = context;
68
_historyPosition = 0;
69
70
_completer = new QCompleter(this);
71
_completer->setWidget(this);
72
QObject::connect(_completer, SIGNAL(activated(const QString &)), this,
73
SLOT(insertCompletion(const QString &)));
74
75
clear();
76
77
connect(PythonQt::self(), SIGNAL(pythonStdOut(const QString &)), this,
78
SLOT(stdOut(const QString &)));
79
connect(PythonQt::self(), SIGNAL(pythonStdErr(const QString &)), this,
80
SLOT(stdErr(const QString &)));
81
}
82
83
//-----------------------------------------------------------------------------
84
85
void PythonQtScriptingConsole::stdOut(const QString &s) {
86
_stdOut += s;
87
int idx;
88
while ((idx = _stdOut.indexOf('\n')) != -1) {
89
consoleMessage(_stdOut.left(idx));
90
std::cout << _stdOut.left(idx).toLatin1().data() << std::endl;
91
_stdOut = _stdOut.mid(idx + 1);
92
}
93
}
94
95
void PythonQtScriptingConsole::stdErr(const QString &s) {
96
_stdErr += s;
97
int idx;
98
while ((idx = _stdErr.indexOf('\n')) != -1) {
99
consoleMessage(_stdErr.left(idx));
100
std::cout << _stdErr.left(idx).toLatin1().data() << std::endl;
101
_stdErr = _stdErr.mid(idx + 1);
102
}
103
}
104
105
void PythonQtScriptingConsole::flushStdOut() {
106
if (!_stdOut.isEmpty()) {
107
stdOut("\n");
108
}
109
if (!_stdErr.isEmpty()) {
110
stdErr("\n");
111
}
112
}
113
114
//-----------------------------------------------------------------------------
115
116
PythonQtScriptingConsole::~PythonQtScriptingConsole() {}
117
118
//-----------------------------------------------------------------------------
119
120
void PythonQtScriptingConsole::clear() {
121
122
QTextEdit::clear();
123
appendCommandPrompt();
124
}
125
126
//-----------------------------------------------------------------------------
127
128
void PythonQtScriptingConsole::executeLine(bool storeOnly) {
129
QTextCursor textCursor = this->textCursor();
130
textCursor.movePosition(QTextCursor::End);
131
132
// Select the text from the command prompt until the end of the block
133
// and get the selected text.
134
textCursor.setPosition(commandPromptPosition());
135
textCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
136
QString code = textCursor.selectedText();
137
138
// i don't know where this trailing space is coming from, blast it!
139
if (code.endsWith(" ")) {
140
code.truncate(code.length() - 1);
141
}
142
143
if (!code.isEmpty()) {
144
// Update the history
145
_history << code;
146
_historyPosition = _history.count();
147
_currentMultiLineCode += code + "\n";
148
149
if (!storeOnly) {
150
executeCode(_currentMultiLineCode);
151
_currentMultiLineCode = "";
152
}
153
}
154
// Insert a new command prompt
155
appendCommandPrompt(storeOnly);
156
}
157
158
void PythonQtScriptingConsole::executeCode(const QString &code) {
159
// put visible cursor to the end of the line
160
QTextCursor cursor = QTextEdit::textCursor();
161
cursor.movePosition(QTextCursor::End);
162
setTextCursor(cursor);
163
164
int cursorPosition = this->textCursor().position();
165
166
// evaluate the code
167
_stdOut = "";
168
_stdErr = "";
169
PythonQtObjectPtr p;
170
p.setNewRef(PyRun_String(code.toLatin1().data(), Py_single_input,
171
PyModule_GetDict(_context),
172
PyModule_GetDict(_context)));
173
if (!p) {
174
PythonQt::self()->handleError();
175
}
176
177
flushStdOut();
178
179
bool messageInserted = (this->textCursor().position() != cursorPosition);
180
181
// If a message was inserted, then put another empty line before the command
182
// prompt to improve readability.
183
if (messageInserted) {
184
append(QString());
185
}
186
}
187
188
//-----------------------------------------------------------------------------
189
190
void PythonQtScriptingConsole::appendCommandPrompt(bool storeOnly) {
191
if (storeOnly) {
192
_commandPrompt = "...> ";
193
} else {
194
_commandPrompt = "py> ";
195
}
196
append(_commandPrompt);
197
198
QTextCursor cursor = textCursor();
199
cursor.movePosition(QTextCursor::End);
200
setTextCursor(cursor);
201
}
202
203
//-----------------------------------------------------------------------------
204
205
void PythonQtScriptingConsole::setCurrentFont(const QColor &color, bool bold) {
206
207
QTextCharFormat charFormat(_defaultTextCharacterFormat);
208
209
QFont font(charFormat.font());
210
font.setBold(bold);
211
charFormat.setFont(font);
212
213
QBrush brush(charFormat.foreground());
214
brush.setColor(color);
215
charFormat.setForeground(brush);
216
217
setCurrentCharFormat(charFormat);
218
}
219
220
//-----------------------------------------------------------------------------
221
222
int PythonQtScriptingConsole::commandPromptPosition() {
223
224
QTextCursor textCursor(this->textCursor());
225
textCursor.movePosition(QTextCursor::End);
226
227
return textCursor.block().position() + _commandPrompt.length();
228
}
229
230
//-----------------------------------------------------------------------------
231
232
void PythonQtScriptingConsole::insertCompletion(const QString &completion) {
233
QTextCursor tc = textCursor();
234
tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
235
if (tc.selectedText() == ".") {
236
tc.insertText(QString(".") + completion);
237
} else {
238
tc = textCursor();
239
tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
240
tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
241
tc.insertText(completion);
242
setTextCursor(tc);
243
}
244
}
245
246
//-----------------------------------------------------------------------------
247
void PythonQtScriptingConsole::handleTabCompletion() {
248
QTextCursor textCursor = this->textCursor();
249
int pos = textCursor.position();
250
textCursor.setPosition(commandPromptPosition());
251
textCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
252
int startPos = textCursor.selectionStart();
253
254
int offset = pos - startPos;
255
QString text = textCursor.selectedText();
256
257
QString textToComplete;
258
int cur = offset;
259
while (cur--) {
260
QChar c = text.at(cur);
261
if (c.isLetterOrNumber() || c == '.' || c == '_') {
262
textToComplete.prepend(c);
263
} else {
264
break;
265
}
266
}
267
268
QString lookup;
269
QString compareText = textToComplete;
270
int dot = compareText.lastIndexOf('.');
271
if (dot != -1) {
272
lookup = compareText.mid(0, dot);
273
compareText = compareText.mid(dot + 1, offset);
274
}
275
if (!lookup.isEmpty() || !compareText.isEmpty()) {
276
compareText = compareText.toLower();
277
QStringList found;
278
QStringList l =
279
PythonQt::self()->introspection(_context, lookup, PythonQt::Anything);
280
foreach (QString n, l) {
281
if (n.toLower().startsWith(compareText)) {
282
found << n;
283
}
284
}
285
286
if (!found.isEmpty()) {
287
_completer->setCompletionPrefix(compareText);
288
_completer->setCompletionMode(QCompleter::PopupCompletion);
289
_completer->setModel(new QStringListModel(found, _completer));
290
_completer->setCaseSensitivity(Qt::CaseInsensitive);
291
QTextCursor c = this->textCursor();
292
c.movePosition(QTextCursor::StartOfWord);
293
QRect cr = cursorRect(c);
294
cr.setWidth(_completer->popup()->sizeHintForColumn(0) +
295
_completer->popup()->verticalScrollBar()->sizeHint().width());
296
cr.translate(0, 8);
297
_completer->complete(cr);
298
} else {
299
_completer->popup()->hide();
300
}
301
} else {
302
_completer->popup()->hide();
303
}
304
}
305
306
void PythonQtScriptingConsole::keyPressEvent(QKeyEvent *event) {
307
308
if (_completer && _completer->popup()->isVisible()) {
309
// The following keys are forwarded by the completer to the widget
310
switch (event->key()) {
311
case Qt::Key_Return:
312
if (!_completer->popup()->currentIndex().isValid()) {
313
insertCompletion(_completer->currentCompletion());
314
_completer->popup()->hide();
315
event->accept();
316
}
317
event->ignore();
318
return;
319
break;
320
case Qt::Key_Enter:
321
case Qt::Key_Escape:
322
case Qt::Key_Tab:
323
case Qt::Key_Backtab:
324
325
event->ignore();
326
return; // let the completer do default behavior
327
default:
328
break;
329
}
330
}
331
bool eventHandled = false;
332
QTextCursor textCursor = this->textCursor();
333
334
int key = event->key();
335
switch (key) {
336
337
case Qt::Key_Left:
338
339
// Moving the cursor left is limited to the position
340
// of the command prompt.
341
342
if (textCursor.position() <= commandPromptPosition()) {
343
344
QApplication::beep();
345
eventHandled = true;
346
}
347
break;
348
349
case Qt::Key_Up:
350
351
// Display the previous command in the history
352
if (_historyPosition > 0) {
353
_historyPosition--;
354
changeHistory();
355
}
356
357
eventHandled = true;
358
break;
359
360
case Qt::Key_Down:
361
362
// Display the next command in the history
363
if (_historyPosition + 1 < _history.count()) {
364
_historyPosition++;
365
changeHistory();
366
}
367
368
eventHandled = true;
369
break;
370
371
case Qt::Key_Return:
372
373
executeLine(event->modifiers() & Qt::ShiftModifier);
374
eventHandled = true;
375
break;
376
377
case Qt::Key_Backspace:
378
379
if (textCursor.hasSelection()) {
380
381
cut();
382
eventHandled = true;
383
384
} else {
385
386
// Intercept backspace key event to check if
387
// deleting a character is allowed. It is not
388
// allowed, if the user wants to delete the
389
// command prompt.
390
391
if (textCursor.position() <= commandPromptPosition()) {
392
393
QApplication::beep();
394
eventHandled = true;
395
}
396
}
397
break;
398
399
case Qt::Key_Delete:
400
401
cut();
402
eventHandled = true;
403
break;
404
405
default:
406
407
if (key >= Qt::Key_Space && key <= Qt::Key_division) {
408
409
if (textCursor.hasSelection() && !verifySelectionBeforeDeletion()) {
410
411
// The selection must not be deleted.
412
eventHandled = true;
413
414
} else {
415
416
// The key is an input character, check if the cursor is
417
// behind the last command prompt, else inserting the
418
// character is not allowed.
419
420
int commandPromptPosition = this->commandPromptPosition();
421
if (textCursor.position() < commandPromptPosition) {
422
423
textCursor.setPosition(commandPromptPosition);
424
setTextCursor(textCursor);
425
}
426
}
427
}
428
}
429
430
if (eventHandled) {
431
432
_completer->popup()->hide();
433
event->accept();
434
435
} else {
436
437
QTextEdit::keyPressEvent(event);
438
QString text = event->text();
439
if (!text.isEmpty()) {
440
handleTabCompletion();
441
} else {
442
_completer->popup()->hide();
443
}
444
eventHandled = true;
445
}
446
}
447
448
//-----------------------------------------------------------------------------
449
450
void PythonQtScriptingConsole::cut() {
451
452
bool deletionAllowed = verifySelectionBeforeDeletion();
453
if (deletionAllowed) {
454
QTextEdit::cut();
455
}
456
}
457
458
//-----------------------------------------------------------------------------
459
460
bool PythonQtScriptingConsole::verifySelectionBeforeDeletion() {
461
462
bool deletionAllowed = true;
463
464
QTextCursor textCursor = this->textCursor();
465
466
int commandPromptPosition = this->commandPromptPosition();
467
int selectionStart = textCursor.selectionStart();
468
int selectionEnd = textCursor.selectionEnd();
469
470
if (textCursor.hasSelection()) {
471
472
// Selected text may only be deleted after the last command prompt.
473
// If the selection is partly after the command prompt set the selection
474
// to the part and deletion is allowed. If the selection occurs before the
475
// last command prompt, then deletion is not allowed.
476
477
if (selectionStart < commandPromptPosition ||
478
selectionEnd < commandPromptPosition) {
479
480
// Assure selectionEnd is bigger than selection start
481
if (selectionStart > selectionEnd) {
482
int tmp = selectionEnd;
483
selectionEnd = selectionStart;
484
selectionStart = tmp;
485
}
486
487
if (selectionEnd < commandPromptPosition) {
488
489
// Selection is completely before command prompt,
490
// so deletion is not allowed.
491
QApplication::beep();
492
deletionAllowed = false;
493
494
} else {
495
496
// The selectionEnd is after the command prompt, so set
497
// the selection start to the commandPromptPosition.
498
selectionStart = commandPromptPosition;
499
textCursor.setPosition(selectionStart);
500
textCursor.setPosition(selectionStart, QTextCursor::KeepAnchor);
501
setTextCursor(textCursor);
502
}
503
}
504
505
} else { // if (hasSelectedText())
506
507
// When there is no selected text, deletion is not allowed before the
508
// command prompt.
509
if (textCursor.position() < commandPromptPosition) {
510
511
QApplication::beep();
512
deletionAllowed = false;
513
}
514
}
515
516
return deletionAllowed;
517
}
518
519
//-----------------------------------------------------------------------------
520
521
void PythonQtScriptingConsole::changeHistory() {
522
523
// Select the text after the last command prompt ...
524
QTextCursor textCursor = this->textCursor();
525
textCursor.movePosition(QTextCursor::End);
526
textCursor.setPosition(commandPromptPosition(), QTextCursor::KeepAnchor);
527
528
// ... and replace it with the history text.
529
textCursor.insertText(_history.value(_historyPosition));
530
531
textCursor.movePosition(QTextCursor::End);
532
setTextCursor(textCursor);
533
}
534
535
//-----------------------------------------------------------------------------
536
537
void PythonQtScriptingConsole::consoleMessage(const QString &message) {
538
539
append(QString());
540
insertPlainText(message);
541
542
// Reset all font modifications done by the html string
543
setCurrentCharFormat(_defaultTextCharacterFormat);
544
}
545
546