0000489: python console: "RFC"s for even more "rapid" prototyping :-)
git-svn-id: https://free-cad.svn.sourceforge.net/svnroot/free-cad/trunk@5253 e8eeb9e2-ec13-0410-a4a9-efa5cf37419d
This commit is contained in:
parent
f8866986b6
commit
6ed7ad3a15
|
@ -46,10 +46,37 @@
|
|||
#include <Gui/DocumentPy.h>
|
||||
#include "CallTips.h"
|
||||
|
||||
Q_DECLARE_METATYPE( Gui::CallTip ); //< allows use of QVariant
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
|
||||
/**
|
||||
* template class Temporary.
|
||||
* Allows variable changes limited to a scope.
|
||||
*/
|
||||
template <typename TYPE>
|
||||
class Temporary
|
||||
{
|
||||
public:
|
||||
Temporary( TYPE &var, const TYPE tmpVal )
|
||||
: _var(var), _saveVal(var)
|
||||
{ var = tmpVal; }
|
||||
|
||||
~Temporary( void )
|
||||
{ _var = _saveVal; }
|
||||
|
||||
private:
|
||||
TYPE &_var;
|
||||
TYPE _saveVal;
|
||||
};
|
||||
|
||||
} /* namespace Gui */
|
||||
|
||||
using namespace Gui;
|
||||
|
||||
CallTipsList::CallTipsList(QPlainTextEdit* parent)
|
||||
: QListWidget(parent), textEdit(parent), cursorPos(0), validObject(true)
|
||||
: QListWidget(parent), textEdit(parent), cursorPos(0), validObject(true), doCallCompletion(false)
|
||||
{
|
||||
// make the user assume that the widget is active
|
||||
QPalette pal = parent->palette();
|
||||
|
@ -406,7 +433,7 @@ void CallTipsList::showTips(const QString& line)
|
|||
addItem(it.key());
|
||||
QListWidgetItem *item = this->item(this->count()-1);
|
||||
item->setData(Qt::ToolTipRole, QVariant(it.value().description));
|
||||
item->setData(Qt::UserRole, QVariant(it.value().parameter));
|
||||
item->setData(Qt::UserRole, qVariantFromValue( it.value() )); //< store full CallTip data
|
||||
switch (it.value().type)
|
||||
{
|
||||
case CallTip::Module:
|
||||
|
@ -526,10 +553,16 @@ bool CallTipsList::eventFilter(QObject * watched, QEvent * event)
|
|||
itemActivated(currentItem());
|
||||
return false;
|
||||
}
|
||||
else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Tab) {
|
||||
else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
||||
itemActivated(currentItem());
|
||||
return true;
|
||||
}
|
||||
else if (ke->key() == Qt::Key_Tab) {
|
||||
// enable call completion for activating items
|
||||
Temporary<bool> tmp( this->doCallCompletion, true ); //< previous state restored on scope exit
|
||||
itemActivated( currentItem() );
|
||||
return true;
|
||||
}
|
||||
else if (this->compKeys.indexOf(ke->key()) > -1) {
|
||||
itemActivated(currentItem());
|
||||
return false;
|
||||
|
@ -585,6 +618,29 @@ void CallTipsList::callTipItemActivated(QListWidgetItem *item)
|
|||
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
|
||||
}
|
||||
cursor.insertText( text );
|
||||
|
||||
// get CallTip from item's UserRole-data
|
||||
const CallTip &callTip = qVariantValue<CallTip>( item->data(Qt::UserRole) );
|
||||
|
||||
// if call completion enabled and we've something callable (method or class constructor) ...
|
||||
if (this->doCallCompletion
|
||||
&& (callTip.type == CallTip::Method || callTip.type == CallTip::Class))
|
||||
{
|
||||
cursor.insertText( QLatin1String("()") ); //< just append parenthesis to identifier even inserted.
|
||||
|
||||
/**
|
||||
* Try to find out if call needs arguments.
|
||||
* For this we search the description for appropriate hints ...
|
||||
*/
|
||||
QRegExp argumentMatcher( QRegExp::escape( callTip.name ) + QLatin1String("\\s*\\(\\s*\\w+.*\\)") );
|
||||
argumentMatcher.setMinimal( true ); //< set regex non-greedy!
|
||||
if (argumentMatcher.indexIn( callTip.description ) != -1)
|
||||
{
|
||||
// if arguments are needed, we just move the cursor one left, to between the parentheses.
|
||||
cursor.movePosition( QTextCursor::Left, QTextCursor::MoveAnchor, 1 );
|
||||
textEdit->setTextCursor( cursor );
|
||||
}
|
||||
}
|
||||
textEdit->ensureCursorVisible();
|
||||
|
||||
QRect rect = textEdit->cursorRect(cursor);
|
||||
|
@ -593,7 +649,7 @@ void CallTipsList::callTipItemActivated(QListWidgetItem *item)
|
|||
|
||||
QPoint p(posX, posY);
|
||||
p = textEdit->mapToGlobal(p);
|
||||
QToolTip::showText(p, item->data(Qt::UserRole).toString());
|
||||
QToolTip::showText( p, callTip.parameter );
|
||||
}
|
||||
|
||||
QString CallTipsList::stripWhiteSpace(const QString& str) const
|
||||
|
|
|
@ -81,6 +81,7 @@ private:
|
|||
QPlainTextEdit* textEdit;
|
||||
int cursorPos;
|
||||
bool validObject;
|
||||
bool doCallCompletion;
|
||||
QList<int> hideKeys;
|
||||
QList<int> compKeys;
|
||||
};
|
||||
|
|
|
@ -55,7 +55,11 @@
|
|||
|
||||
using namespace Gui;
|
||||
|
||||
namespace Gui {
|
||||
namespace Gui
|
||||
{
|
||||
|
||||
static size_t promptLength = 4; //< length of prompt string: ">>> " or "... ", in either case 4 characters
|
||||
|
||||
struct PythonConsoleP
|
||||
{
|
||||
enum Output {Error = 20, Message = 21};
|
||||
|
@ -310,6 +314,11 @@ bool InteractiveInterpreter::push(const char* line)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool InteractiveInterpreter::hasPendingInput( void ) const
|
||||
{
|
||||
return (!d->buffer.isEmpty());
|
||||
}
|
||||
|
||||
QStringList InteractiveInterpreter::getBuffer() const
|
||||
{
|
||||
return d->buffer;
|
||||
|
@ -430,97 +439,125 @@ void PythonConsole::OnChange( Base::Subject<const char*> &rCaller,const char* sR
|
|||
*/
|
||||
void PythonConsole::keyPressEvent(QKeyEvent * e)
|
||||
{
|
||||
if (e->modifiers() & Qt::ControlModifier) {
|
||||
switch( e->key() )
|
||||
bool restartHistory = true;
|
||||
QTextCursor cursor = this->textCursor();
|
||||
|
||||
// construct reference cursor at begin of input line ...
|
||||
QTextCursor inputLineBegin = cursor;
|
||||
inputLineBegin.movePosition( QTextCursor::End );
|
||||
inputLineBegin.movePosition( QTextCursor::StartOfLine );
|
||||
inputLineBegin.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptLength );
|
||||
|
||||
if (cursor < inputLineBegin)
|
||||
{
|
||||
/**
|
||||
* The cursor is placed not on the input line (or within the prompt string)
|
||||
* So we handle key input as follows:
|
||||
* - don't allow changing previous lines.
|
||||
* - allow full movement (no prompt restriction)
|
||||
* - allow copying content (Ctrl+C)
|
||||
* - "escape" to end of input line
|
||||
*/
|
||||
switch (e->key())
|
||||
{
|
||||
case Qt::Key_Up:
|
||||
{
|
||||
// no modification, just history facility
|
||||
if (!d->history.isEmpty()) {
|
||||
if (d->history.prev()) {
|
||||
QString cmd = d->history.value();
|
||||
overrideCursor(cmd);
|
||||
} return;
|
||||
}
|
||||
} break;
|
||||
case Qt::Key_Down:
|
||||
{
|
||||
// no modification, just history facility
|
||||
if (!d->history.isEmpty()) {
|
||||
if (d->history.next()) {
|
||||
QString cmd = d->history.value();
|
||||
overrideCursor(cmd);
|
||||
} return;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Escape:
|
||||
this->moveCursor( QTextCursor::End );
|
||||
break;
|
||||
|
||||
default:
|
||||
if (e->text().isEmpty() || e->matches( QKeySequence::Copy ))
|
||||
{ TextEdit::keyPressEvent(e); }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (e->key())
|
||||
else
|
||||
{
|
||||
// running Python interpreter?
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
{
|
||||
// make sure to be at the end
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
|
||||
// get the last paragraph's text
|
||||
QTextBlock block = cursor.block();
|
||||
QString line = block.text();
|
||||
/**
|
||||
* The cursor sits somewhere on the input line (after the prompt)
|
||||
* Here we handle key input a bit different:
|
||||
* - restrict cursor movement to input line range (excluding the prompt characters)
|
||||
* - roam the history by Up/Down keys
|
||||
* - show call tips on period
|
||||
*/
|
||||
QTextBlock inputBlock = inputLineBegin.block(); //< get the last paragraph's text
|
||||
QString inputLine = inputBlock.text().mid(promptLength); //< and skip prompt characters
|
||||
|
||||
// and skip the first 4 characters consisting of either ">>> " or "... "
|
||||
line = line.mid(4);
|
||||
|
||||
// put statement to the history
|
||||
d->history.append(line);
|
||||
|
||||
// evaluate and run the command
|
||||
runSource(line);
|
||||
} break;
|
||||
case Qt::Key_Period:
|
||||
switch (e->key())
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlock block = cursor.block();
|
||||
QString text = block.text();
|
||||
int length = cursor.position() - block.position();
|
||||
TextEdit::keyPressEvent(e);
|
||||
d->callTipsList->showTips(text.left(length));
|
||||
} break;
|
||||
case Qt::Key_Home:
|
||||
{
|
||||
if (e->modifiers() & Qt::ControlModifier) {
|
||||
TextEdit::keyPressEvent(e);
|
||||
} else {
|
||||
QTextCursor::MoveMode mode = e->modifiers() & Qt::ShiftModifier
|
||||
? QTextCursor::KeepAnchor
|
||||
: QTextCursor::MoveAnchor;
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlock block = cursor.block();
|
||||
QString text = block.text();
|
||||
int cursorPos = block.position();
|
||||
if (text.startsWith(QLatin1String(">>> ")) ||
|
||||
text.startsWith(QLatin1String("... ")))
|
||||
cursorPos += 4;
|
||||
cursor.setPosition(cursorPos, mode);
|
||||
setTextCursor(cursor);
|
||||
ensureCursorVisible();
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
TextEdit::keyPressEvent(e);
|
||||
case Qt::Key_Escape:
|
||||
{
|
||||
// disable current input line - i.e. put it to history but don't execute it.
|
||||
if (!inputLine.isEmpty())
|
||||
{
|
||||
d->history.append( QLatin1String("# ") + inputLine ); //< put line to history ...
|
||||
inputLineBegin.insertText( QString::fromAscii("# ") ); //< but comment it on console
|
||||
setTextCursor( inputLineBegin );
|
||||
printPrompt( d->interpreter->hasPendingInput() ); //< print adequate prompt
|
||||
}
|
||||
} break;
|
||||
|
||||
// This can't be done in CallTipsList::eventFilter() because we must first perform
|
||||
// the event and afterwards update the list widget
|
||||
if (d->callTipsList->isVisible()) {
|
||||
d->callTipsList->validateCursor();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
{
|
||||
runSource( inputLine ); //< commit input line
|
||||
d->history.append( inputLine ); //< put statement to history
|
||||
} break;
|
||||
|
||||
case Qt::Key_Period:
|
||||
{
|
||||
// analyse context and show available call tips
|
||||
int contextLength = cursor.position() - inputLineBegin.position();
|
||||
TextEdit::keyPressEvent(e);
|
||||
d->callTipsList->showTips( inputLine.left( contextLength ) );
|
||||
} break;
|
||||
|
||||
case Qt::Key_Home:
|
||||
{
|
||||
QTextCursor::MoveMode mode = (e->modifiers() & Qt::ShiftModifier)? QTextCursor::KeepAnchor
|
||||
/* else */ : QTextCursor::MoveAnchor;
|
||||
cursor.setPosition( inputBlock.position() + promptLength, mode );
|
||||
setTextCursor( cursor );
|
||||
ensureCursorVisible();
|
||||
} break;
|
||||
|
||||
case Qt::Key_Up:
|
||||
{
|
||||
// if possible, move back in history
|
||||
if (d->history.prev( inputLine ))
|
||||
{ overrideCursor( d->history.value() ); }
|
||||
restartHistory = false;
|
||||
} break;
|
||||
|
||||
case Qt::Key_Down:
|
||||
{
|
||||
// if possible, move forward in history
|
||||
if (d->history.next())
|
||||
{ overrideCursor( d->history.value() ); }
|
||||
restartHistory = false;
|
||||
} break;
|
||||
|
||||
case Qt::Key_Backspace:
|
||||
case Qt::Key_Left:
|
||||
{
|
||||
if (cursor > inputLineBegin)
|
||||
{ TextEdit::keyPressEvent(e); }
|
||||
} break;
|
||||
|
||||
default:
|
||||
{
|
||||
TextEdit::keyPressEvent(e);
|
||||
} break;
|
||||
}
|
||||
// This can't be done in CallTipsList::eventFilter() because we must first perform
|
||||
// the event and afterwards update the list widget
|
||||
if (d->callTipsList->isVisible())
|
||||
{ d->callTipsList->validateCursor(); }
|
||||
}
|
||||
// any cursor move resets the history to its latest item.
|
||||
if (restartHistory)
|
||||
{ d->history.restart(); }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -844,8 +881,8 @@ QMimeData * PythonConsole::createMimeDataFromSelection () const
|
|||
if ( pos >= s && pos <= e ) {
|
||||
if (b.userState() > -1 && b.userState() < pythonSyntax->maximumUserState()) {
|
||||
QString line = b.text();
|
||||
// and skip the first 4 characters consisting of either ">>> " or "... "
|
||||
line = line.mid(4);
|
||||
// and skip the prompt characters consisting of either ">>> " or "... "
|
||||
line = line.mid(promptLength);
|
||||
lines << line;
|
||||
}
|
||||
}
|
||||
|
@ -916,7 +953,7 @@ void PythonConsole::runSourceFromMimeData(const QString& source)
|
|||
cursor.removeSelectedText();
|
||||
last = last + select;
|
||||
line = cursor.block().text();
|
||||
line = line.mid(4);
|
||||
line = line.mid(promptLength);
|
||||
}
|
||||
|
||||
// put statement to the history
|
||||
|
@ -972,7 +1009,7 @@ void PythonConsole::overrideCursor(const QString& txt)
|
|||
QTextBlock block = cursor.block();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.movePosition(QTextCursor::StartOfLine);
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 4);
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, promptLength);
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, block.text().length());
|
||||
cursor.removeSelectedText();
|
||||
cursor.insertText(txt);
|
||||
|
@ -1124,7 +1161,7 @@ void PythonConsoleHighlighter::colorChanged(const QString& type, const QColor& c
|
|||
|
||||
ConsoleHistory::ConsoleHistory()
|
||||
{
|
||||
it = _history.end();
|
||||
_it = _history.end();
|
||||
}
|
||||
|
||||
ConsoleHistory::~ConsoleHistory()
|
||||
|
@ -1133,38 +1170,62 @@ ConsoleHistory::~ConsoleHistory()
|
|||
|
||||
void ConsoleHistory::first()
|
||||
{
|
||||
it = _history.begin();
|
||||
_it = _history.begin();
|
||||
}
|
||||
|
||||
bool ConsoleHistory::more()
|
||||
{
|
||||
return (it != _history.end());
|
||||
return (_it != _history.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* next switches the history pointer to the next item.
|
||||
* While searching the next item, the routine respects the search prefix set by prev().
|
||||
* @return true if the pointer was switched to a later item, false otherwise.
|
||||
*/
|
||||
bool ConsoleHistory::next()
|
||||
{
|
||||
if (it != _history.end()) {
|
||||
for (++it; it != _history.end(); ++it) {
|
||||
if (!it->isEmpty())
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool wentNext = false;
|
||||
|
||||
return false;
|
||||
// if we didn't reach history's end ...
|
||||
if (_it != _history.end())
|
||||
{
|
||||
// we go forward until we find an item matching the prefix.
|
||||
for (++_it; _it != _history.end(); ++_it)
|
||||
{
|
||||
if (!_it->isEmpty() && _it->startsWith( _prefix ))
|
||||
{ break; }
|
||||
}
|
||||
// we did a step - no matter of a matching prefix.
|
||||
wentNext = true;
|
||||
}
|
||||
return wentNext;
|
||||
}
|
||||
|
||||
bool ConsoleHistory::prev()
|
||||
/**
|
||||
* prev switches the history pointer to the previous item.
|
||||
* The optional parameter prefix allows to search the history selectively for commands that start
|
||||
* with a certain character sequence.
|
||||
* @param prefix - prefix string for searching backwards in history, empty string by default
|
||||
* @return true if the pointer was switched to an earlier item, false otherwise.
|
||||
*/
|
||||
bool ConsoleHistory::prev( const QString &prefix )
|
||||
{
|
||||
if (it != _history.begin()) {
|
||||
for (--it; it != _history.begin(); --it) {
|
||||
if (!it->isEmpty())
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool wentPrev = false;
|
||||
|
||||
return false;
|
||||
// store prefix if it's the first history access
|
||||
if (_it == _history.end())
|
||||
{ _prefix = prefix; }
|
||||
|
||||
// while we didn't go back or reach history's begin ...
|
||||
while (!wentPrev && _it != _history.begin())
|
||||
{
|
||||
// go back in history and check if item matches prefix
|
||||
// Skip empty items
|
||||
--_it;
|
||||
wentPrev = (!_it->isEmpty() && _it->startsWith( _prefix ));
|
||||
}
|
||||
return wentPrev;
|
||||
}
|
||||
|
||||
bool ConsoleHistory::isEmpty() const
|
||||
|
@ -1172,18 +1233,18 @@ bool ConsoleHistory::isEmpty() const
|
|||
return _history.isEmpty();
|
||||
}
|
||||
|
||||
QString ConsoleHistory::value() const
|
||||
const QString& ConsoleHistory::value() const
|
||||
{
|
||||
if ( it != _history.end() )
|
||||
return *it;
|
||||
else
|
||||
return QString::null;
|
||||
return ((_it != _history.end())? *_it
|
||||
/* else */ : _prefix);
|
||||
}
|
||||
|
||||
void ConsoleHistory::append( const QString& item )
|
||||
{
|
||||
_history.append( item );
|
||||
it = _history.end();
|
||||
// reset iterator to make the next history
|
||||
// access begin with the latest item.
|
||||
_it = _history.end();
|
||||
}
|
||||
|
||||
const QStringList& ConsoleHistory::values() const
|
||||
|
@ -1191,6 +1252,14 @@ const QStringList& ConsoleHistory::values() const
|
|||
return this->_history;
|
||||
}
|
||||
|
||||
/**
|
||||
* restart resets the history access to the latest item.
|
||||
*/
|
||||
void ConsoleHistory::restart( void )
|
||||
{
|
||||
_it = _history.end();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
|
||||
/* TRANSLATOR Gui::PythonInputField */
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
|
||||
bool push(const char*);
|
||||
int compileCommand(const char*) const;
|
||||
bool hasPendingInput( void ) const;
|
||||
void setBuffer(const QStringList&);
|
||||
QStringList getBuffer() const;
|
||||
void clearBuffer();
|
||||
|
@ -72,15 +73,17 @@ public:
|
|||
void first();
|
||||
bool more();
|
||||
bool next();
|
||||
bool prev();
|
||||
bool prev(const QString &prefix = QString());
|
||||
bool isEmpty() const;
|
||||
QString value() const;
|
||||
void append( const QString& );
|
||||
const QString& value() const;
|
||||
void append(const QString &inputLine);
|
||||
const QStringList& values() const;
|
||||
void restart();
|
||||
|
||||
private:
|
||||
QStringList _history;
|
||||
QStringList::ConstIterator it;
|
||||
QStringList _history;
|
||||
QStringList::ConstIterator _it;
|
||||
QString _prefix;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue
Block a user