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