Something went wrong on our end
-
marco authored
git-svn-id: https://dng.biozentrum.unibas.ch/svn/openstructure/trunk@2643 5a81b35b-ba03-0410-adc8-b2c5c5119f08
marco authoredgit-svn-id: https://dng.biozentrum.unibas.ch/svn/openstructure/trunk@2643 5a81b35b-ba03-0410-adc8-b2c5c5119f08
python_shell_widget.cc 37.75 KiB
//------------------------------------------------------------------------------
// This file is part of the OpenStructure project <www.openstructure.org>
//
// Copyright (C) 2008-2010 by the OpenStructure authors
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3.0 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 Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//------------------------------------------------------------------------------
/*
Authors: Marco Biasini, Andreas Schenk
*/
#include <iostream>
#include <QApplication>
#include <QFontMetrics>
#include <QClipboard>
#include <QPainter>
#include <QScrollBar>
#include <QDirModel>
#include <QStringList>
#include "python_shell_widget.hh"
#include "gutter.hh"
#include "string_literal_positions.hh"
#include "python_shell_text_document_layout.hh"
#include "python_completer.hh"
#include "path_completer.hh"
#include "transition.hh"
/*
Authors: Marco Biasini, Andreas Schenk
*/
namespace ost { namespace gui {
PythonShellWidget::PythonShellWidget(QWidget* parent):
QPlainTextEdit(parent),
tab_width_(2),
highlighter_(document()),
completer_(new PythonCompleter(viewport())),
path_completer_(new PathCompleter(viewport())),
block_edit_mode_(EDITMODE_SINGLELINE),
gutter_(new Gutter(this)),
output_visible_(true),
completion_start_(0),
completion_end_(0),
block_edit_start_(document()->begin()),
output_blocks_(),
machine_(new StateMachine(this)),
readonly_machine_(new StateMachine(this)),
readwrite_state_(new State),
multiline_active_state_(new State)
{
setLineWrapMode(QPlainTextEdit::WidgetWidth);
this->setFrameShape(QFrame::NoFrame);
document()->setDocumentLayout(new PythonShellTextDocumentLayout(document()));
setViewportMargins(Gutter::GUTTER_WIDTH, 0, 0, 0);
setUndoRedoEnabled(false);
setFont(QFont("Courier",font().pointSize()+2));
QFontMetrics metrics(font());
setTabStopWidth(2*metrics.width(" "));
setMaximumBlockCount(1000000);
textCursor().block().setUserState(BLOCKTYPE_ACTIVE);
completer_->setWidget(viewport());
connect(&PythonInterpreter::Instance(), SIGNAL(Output(unsigned int, const QString &)),
this,SLOT(AppendOutput(unsigned int, const QString &)));
connect(&PythonInterpreter::Instance(), SIGNAL(Finished(unsigned int, bool)),
this,SLOT(OutputFinished(unsigned int,bool)));
connect(&PythonInterpreter::Instance(), SIGNAL(ErrorOutput(unsigned int, const QString &)),
this,SLOT(AppendError(unsigned int, const QString &)));
connect(this, SIGNAL(updateRequest(QRect, int)) ,
gutter_,SLOT(Update(QRect, int)));
connect(completer_,SIGNAL(activated(const QString&)),this,
SLOT(InsertCompletion(const QString&)));
connect(completer_,SIGNAL(recomplete(const QString&)),this,
SLOT(Recomplete(const QString&)));
connect(this,SIGNAL(RequestCompletion(const QRect&,bool)),completer_,
SLOT(complete(const QRect&,bool)));
connect(this,SIGNAL(SetCompletionPrefix(const QString&)),completer_,
SLOT(SetCompletionPrefix(const QString&)));
path_completer_->setWidget(viewport());
connect(path_completer_,SIGNAL(activated(const QString&)),this,
SLOT(InsertPathCompletion(const QString&)));
connect(path_completer_,SIGNAL(recomplete(const QString&)),this,
SLOT(Recomplete(const QString&)));
connect(this,SIGNAL(RequestPathCompletion(const QRect&,bool)),path_completer_,
SLOT(complete(const QRect&,bool)));
connect(this,SIGNAL(SetPathCompletionPrefix(const QString&)),path_completer_,
SLOT(setCompletionPrefix(const QString&)));
set_block_edit_mode_(EDITMODE_SINGLELINE);
setup_readonly_state_machine_();
setup_state_machine_();
}
void PythonShellWidget::setup_readonly_state_machine_()
{
State* readonly=new State;
State* mixed=new State;
readonly_machine_->addState(mixed);
readonly_machine_->addState(readonly);
readonly_machine_->addState(readwrite_state_);
readonly->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
readwrite_state_,
new EditPositionGuard(this,EditPositionGuard::EQUAL |EditPositionGuard::BIGGER,
EditPositionGuard::ANCHOREQUAL |EditPositionGuard::ANCHORBIGGER)));
readonly->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
mixed,
new EditPositionGuard(this,EditPositionGuard::EQUAL |EditPositionGuard::BIGGER,
EditPositionGuard::ANCHORSMALLER)));
readonly->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
mixed,
new EditPositionGuard(this,EditPositionGuard::ANCHOREQUAL |EditPositionGuard::ANCHORBIGGER,
EditPositionGuard::SMALLER)));
readonly->addTransition(new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Backspace,
Qt::NoModifier,
readwrite_state_,
true));
readonly->addTransition(new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Any,
Qt::NoModifier,
readwrite_state_,
false));
readwrite_state_->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
readonly,
new EditPositionGuard(this,EditPositionGuard::SMALLER | EditPositionGuard::EQUAL,
EditPositionGuard::ANCHORSMALLER)));
readwrite_state_->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
readonly,
new EditPositionGuard(this,EditPositionGuard::ANCHORSMALLER | EditPositionGuard::ANCHOREQUAL,
EditPositionGuard::SMALLER)));
readwrite_state_->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
mixed,
new EditPositionGuard(this,EditPositionGuard::EQUAL |EditPositionGuard::BIGGER,
EditPositionGuard::ANCHORSMALLER)));
readwrite_state_->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
mixed,
new EditPositionGuard(this,EditPositionGuard::ANCHOREQUAL |EditPositionGuard::ANCHORBIGGER,
EditPositionGuard::SMALLER)));
readwrite_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Backspace,
Qt::NoModifier,
readwrite_state_,
true,
new EditPositionGuard(this,EditPositionGuard::EQUAL,
EditPositionGuard::ANCHOREQUAL)));
mixed->addTransition(new SignalTransition(this,
SIGNAL(cursorPositionChanged()),
readwrite_state_,
new EditPositionGuard(this,EditPositionGuard::EQUAL |EditPositionGuard::BIGGER,
EditPositionGuard::ANCHOREQUAL |EditPositionGuard::ANCHORBIGGER)));
KeyEventTransition* kt1=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Backspace,
Qt::NoModifier,
readwrite_state_,
true);
mixed->addTransition(kt1);
connect(kt1,SIGNAL(triggered()),this,SLOT(OnMixedToReadwrite()));
KeyEventTransition* kt2=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Any,
Qt::NoModifier,
readwrite_state_,
false);
mixed->addTransition(kt2);
connect(kt2,SIGNAL(triggered()),this,SLOT(OnMixedToReadwrite()));
connect(readonly,SIGNAL(entered()),this,SLOT(OnReadonlyEntered()));
connect(readwrite_state_,SIGNAL(entered()),this,SLOT(OnReadwriteEntered()));
readonly_machine_->setInitialState(readwrite_state_);
readonly_machine_->start();
}
void PythonShellWidget::setup_state_machine_()
{
#ifdef __APPLE__
QFlags<Qt::KeyboardModifier> DNG_ARROW_MODIFIERS = Qt::KeypadModifier;
QFlags<Qt::KeyboardModifier> DNG_ENTER_MODIFIERS = Qt::NoModifier;
#else
QFlags<Qt::KeyboardModifier> DNG_ARROW_MODIFIERS = Qt::NoModifier;
QFlags<Qt::KeyboardModifier> DNG_ENTER_MODIFIERS = Qt::KeypadModifier;
#endif
State* single_line=new State;
State* multi_line_inactive=new State;
State* completing=new State;
State* executing=new State;
State* history_up=new State;
State* history_down=new State;
machine_->addState(single_line);
machine_->addState(multiline_active_state_);
machine_->addState(multi_line_inactive);
machine_->addState(completing);
machine_->addState(executing);
machine_->addState(history_up);
machine_->addState(history_down);
connect(single_line,SIGNAL(entered()),this,SLOT(OnSingleLineStateEntered()));
connect(multiline_active_state_,SIGNAL(entered()),this,SLOT(OnMultiLineActiveStateEntered()));
connect(multi_line_inactive,SIGNAL(entered()),this,SLOT(OnMultiLineInactiveStateEntered()));
connect(history_up,SIGNAL(entered()),this,SLOT(OnHistoryUpStateEntered()));
connect(history_down,SIGNAL(entered()),this,SLOT(OnHistoryDownStateEntered()));
connect(executing,SIGNAL(entered()),this,SLOT(OnExecuteStateEntered()));
KeyEventTransition* tr1=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::NoModifier,
executing,
true,
new BlockStatusGuard(this,CODE_BLOCK_COMPLETE | CODE_BLOCK_ERROR));
single_line->addTransition(tr1);
KeyEventTransition* tr3=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::NoModifier,
multiline_active_state_,
true,
new BlockStatusGuard(this,CODE_BLOCK_INCOMPLETE));
single_line->addTransition(tr3);
KeyEventTransition* keypad_enter1=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS| Qt::NoModifier,
multiline_active_state_,
true);
single_line->addTransition(keypad_enter1);
connect(keypad_enter1,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
KeyEventTransition* keypad_enter2=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS| Qt::ShiftModifier,
multiline_active_state_,
true);
single_line->addTransition(keypad_enter2);
connect(keypad_enter2,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
KeyEventTransition* keypad_enter3=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS| Qt::MetaModifier,
multiline_active_state_,
true);
single_line->addTransition(keypad_enter3);
connect(keypad_enter3,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
single_line->addTransition(new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::ControlModifier,
multiline_active_state_,
false));
// just to make OSX happy we also add the transitions with Meta (-> Ctrl o OSX)
single_line->addTransition(new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::MetaModifier,
multiline_active_state_,
false));
connect(tr3,SIGNAL(triggered()),this,SLOT(OnEnterTransition()));
single_line->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Up,DNG_ARROW_MODIFIERS,history_up));
single_line->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Down,DNG_ARROW_MODIFIERS,history_down));
KeyEventTransition* tr4=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::NoModifier,
executing,
true,
new BlockStatusGuard(this,CODE_BLOCK_COMPLETE | CODE_BLOCK_ERROR));
multi_line_inactive->addTransition(tr4);
KeyEventTransition* tr6=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::NoModifier,
multiline_active_state_,
true,
new BlockStatusGuard(this,CODE_BLOCK_INCOMPLETE));
multi_line_inactive->addTransition(tr6);
connect(tr6,SIGNAL(triggered()),this,SLOT(OnEnterTransition()));
multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Left,DNG_ARROW_MODIFIERS,multiline_active_state_));
multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Right,DNG_ARROW_MODIFIERS,multiline_active_state_));
multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Return,Qt::ControlModifier,multiline_active_state_));
multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Up,DNG_ARROW_MODIFIERS,history_up));
multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Down,DNG_ARROW_MODIFIERS,history_down));
KeyEventTransition* keypad_enter4=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS| Qt::NoModifier,
multiline_active_state_,
true);
multi_line_inactive->addTransition(keypad_enter4);
connect(keypad_enter4,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
KeyEventTransition* keypad_enter5=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS| Qt::ShiftModifier,
multiline_active_state_,
true);
multi_line_inactive->addTransition(keypad_enter5);
connect(keypad_enter5,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
KeyEventTransition* keypad_enter6=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS | Qt::MetaModifier,
multiline_active_state_,
true);
multi_line_inactive->addTransition(keypad_enter6);
connect(keypad_enter6,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::ControlModifier,
multiline_active_state_,
false));
// just to make OSX happy we also add the transitions with Meta (-> Ctrl o OSX)
multi_line_inactive->addTransition(new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::MetaModifier,
multiline_active_state_,
false));
KeyEventTransition* tr7=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::NoModifier,
executing,
true,
new BlockStatusGuard(this,CODE_BLOCK_COMPLETE | CODE_BLOCK_ERROR));
multiline_active_state_->addTransition(tr7);
KeyEventTransition* tr8=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Return,
Qt::NoModifier,
multiline_active_state_,
true,
new BlockStatusGuard(this,CODE_BLOCK_INCOMPLETE));
multiline_active_state_->addTransition(tr8);
connect(tr8,SIGNAL(triggered()),this,SLOT(OnEnterTransition()));
multiline_active_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Escape,Qt::NoModifier,multi_line_inactive));
multiline_active_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Up,Qt::ControlModifier | DNG_ARROW_MODIFIERS,history_up));
multiline_active_state_->addTransition(new KeyEventTransition(QEvent::KeyPress,Qt::Key_Down,Qt::ControlModifier | DNG_ARROW_MODIFIERS,history_down));
KeyEventTransition* keypad_enter7=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS | Qt::NoModifier,
multiline_active_state_,
true);
multiline_active_state_->addTransition(keypad_enter7);
connect(keypad_enter7,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
KeyEventTransition* keypad_enter8=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS | Qt::ShiftModifier,
multiline_active_state_,
true);
multiline_active_state_->addTransition(keypad_enter8);
connect(keypad_enter8,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
KeyEventTransition* keypad_enter9=new KeyEventTransition(QEvent::KeyPress,
Qt::Key_Enter,
DNG_ENTER_MODIFIERS | Qt::MetaModifier,
multiline_active_state_,
true);
multiline_active_state_->addTransition(keypad_enter9);
connect(keypad_enter9,SIGNAL(triggered()),this,SLOT(OnKeypadEnterTransition()));
history_up->addTransition(new AutomaticTransition(multi_line_inactive,new HistoryGuard(&history_,EDITMODE_MULTILINE_INACTIVE)));
history_up->addTransition(new AutomaticTransition(single_line,new HistoryGuard(&history_,EDITMODE_SINGLELINE)));
history_down->addTransition(new AutomaticTransition(multi_line_inactive,new HistoryGuard(&history_,EDITMODE_MULTILINE_INACTIVE)));
history_down->addTransition(new AutomaticTransition(single_line,new HistoryGuard(&history_,EDITMODE_SINGLELINE)));
executing->addTransition(new AutomaticTransition(single_line));
machine_->setInitialState(single_line);
machine_->start();
}
void PythonShellWidget::OnReadonlyEntered()
{
setReadOnly(true);
setTextInteractionFlags(textInteractionFlags() | Qt::TextSelectableByKeyboard);
}
void PythonShellWidget::OnReadwriteEntered()
{
if(textCursor().position()< GetEditStartBlock().position()){
moveCursor(QTextCursor::End);
}
setReadOnly(false);
}
void PythonShellWidget::OnMixedToReadwrite()
{
QTextCursor tc=textCursor();
if(tc.position()< GetEditStartBlock().position()){
tc.setPosition(GetEditStartBlock().position(),QTextCursor::KeepAnchor);
setTextCursor(tc);
}else{
tc.setPosition(GetEditStartBlock().position(),QTextCursor::MoveAnchor);
tc.setPosition(textCursor().position(),QTextCursor::KeepAnchor);
setTextCursor(tc);
}
}
void PythonShellWidget::OnKeypadEnterTransition()
{
insertPlainText(QString(QChar::LineSeparator));
}
void PythonShellWidget::OnEnterTransition()
{
QTextCursor cursor=textCursor();
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
setTextCursor(cursor);
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
bool new_indent=cursor.selectedText()==":";
cursor.movePosition(QTextCursor::StartOfLine);
cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
insertPlainText(QString(QChar::ParagraphSeparator));
if(cursor.selectedText()[0].isSpace() && ! cursor.atBlockEnd()){
insertPlainText(QString(cursor.selectedText()));
}
if (new_indent){
insertPlainText(QString("\t"));
}
}
void PythonShellWidget::OnSingleLineStateEntered()
{
set_block_edit_mode_(EDITMODE_SINGLELINE);
}
void PythonShellWidget::OnMultiLineActiveStateEntered()
{
set_block_edit_mode_(EDITMODE_MULTILINE_ACTIVE);
}
void PythonShellWidget::OnMultiLineInactiveStateEntered()
{
set_block_edit_mode_(EDITMODE_MULTILINE_INACTIVE);
}
void PythonShellWidget::OnHistoryUpStateEntered()
{
if(history_.AtEnd()){
history_.SetCurrentCommand(GetCommand(),get_block_edit_mode_());
}
history_.MoveToPreviousMatch();
set_command_(history_.GetCommand());
set_block_edit_mode_(history_.GetCommandMode());
}
void PythonShellWidget::OnHistoryDownStateEntered()
{
history_.MoveToNextMatch();
set_command_(history_.GetCommand());
set_block_edit_mode_(history_.GetCommandMode());
}
void PythonShellWidget::OnExecuteStateEntered()
{
QTextCursor cursor=textCursor();
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
setTextCursor(cursor);
set_block_type_(block_edit_start_,textCursor().block(),BLOCKTYPE_CODE);
insertPlainText(QString(QChar::ParagraphSeparator));
QString command=GetCommand();
QString command_trimmed=command.trimmed();
if (command_trimmed.size()>0) {
unsigned int id=PythonInterpreter::Instance().RunCommand(command);
output_blocks_.insert(id,textCursor().block());
history_.AppendCommand(command_trimmed,get_block_edit_mode_());
insertPlainText(QString(QChar::ParagraphSeparator));
}
block_edit_start_=textCursor().block();
}
void PythonShellWidget::SetTabWidth(int width) {
tab_width_=width;
QFontMetrics metrics(font());
setTabStopWidth(tab_width_*metrics.width(" "));
}
int PythonShellWidget::GetTabWidth() const {
return tab_width_;
}
void PythonShellWidget::wrap_into_function_(const QString& command)
{
QString tmp_command=command;
tmp_command.replace(QString(QChar::LineSeparator),
QString(QChar::LineSeparator)+QString("\t"));
QString tmp="def func():"+QString(QChar::LineSeparator)+"\t";
tmp+=tmp_command;
set_command_(tmp);
QTextCursor tc=textCursor();
tc.setPosition(document()->lastBlock().position()+QString("def ").length());
tc.setPosition(document()->lastBlock().position()+
QString("def func").length(),QTextCursor::KeepAnchor);
setTextCursor(tc);
}
void PythonShellWidget::Recomplete(const QString& completion)
{
InsertCompletion(completion);
Complete(false);
}
void PythonShellWidget::InsertCompletion(const QString& completion)
{
QTextCursor tc = textCursor();
tc.setPosition(completion_start_);
tc.setPosition(completion_end_,QTextCursor::KeepAnchor);
tc.insertText(completion);
setTextCursor(tc);
}
void PythonShellWidget::InsertPathCompletion(const QString& completion)
{
QString path=QDir::fromNativeSeparators(completion);
// append dir separator for directories if none present (Windows adds it already for the inline completion)
if(QFileInfo(path).isDir() && ! completion.endsWith("/")){
path+="/";
}
InsertCompletion(path);
}
void PythonShellWidget::AppendOutput(unsigned int id,const QString& output)
{
OutputBlockList::iterator it=output_blocks_.find(id);
if(it==output_blocks_.end()){
return;
}
if(! it->isValid()){
return;
}
QTextCursor cursor(*it);
cursor.movePosition(QTextCursor::EndOfBlock);
cursor.insertText(output);
set_block_type_(output_blocks_[id],cursor.block(),BLOCKTYPE_OUTPUT);
output_blocks_[id]=cursor.block();
verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
ensureCursorVisible();
repaint();
QApplication::processEvents();
}
void PythonShellWidget::AppendError(unsigned int id,const QString& output)
{
OutputBlockList::iterator it=output_blocks_.find(id);
if(it==output_blocks_.end()){
return;
}
if(! it->isValid()){
return;
}
QTextCursor cursor(*it);
cursor.movePosition(QTextCursor::EndOfBlock);
cursor.insertText(output);
set_block_type_(output_blocks_[id],cursor.block(),BLOCKTYPE_ERROR);
output_blocks_[id]=cursor.block();
verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
ensureCursorVisible();
repaint();
QApplication::processEvents();
}
void PythonShellWidget::set_block_type_(const QTextBlock& start, const QTextBlock& end, BlockType type)
{
QTextBlock block=start;
while(block!=end){
block.setUserState(type);
block=block.next();
}
block.setUserState(type);
document()->markContentsDirty(start.position(),end.position()+end.length()-start.position());
}
void PythonShellWidget::OutputFinished(unsigned int id, bool error)
{
OutputBlockList::iterator it=output_blocks_.find(id);
if(it==output_blocks_.end()){
return;
}
if(it->isValid()){
QTextCursor cursor(*it);
cursor.movePosition(QTextCursor::Left,QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
output_blocks_.erase(it);
}
void PythonShellWidget::set_command_(const QString& command)
{
QTextCursor cursor(block_edit_start_);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
if(block_edit_start_.isValid()){
block_edit_start_=document()->lastBlock();
}
cursor.insertText(command);
QAbstractTextDocumentLayout* layout=document()->documentLayout();
dynamic_cast<PythonShellTextDocumentLayout*>(layout)->EmitSizeChange();
verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
ensureCursorVisible();
}
void PythonShellWidget::Complete(bool inline_completion)
{
QTextCursor cursor=textCursor();
if(cursor.position()<document()->lastBlock().position()){
return;
}
StringLiteralPositions* positions=dynamic_cast<StringLiteralPositions*>(document()->lastBlock().userData());
int literal_index=-1;
if(positions){
literal_index=positions->GetStringLiteralIndex(textCursor().position());
}
if (literal_index>=0) {
cursor.setPosition(positions->GetStart(literal_index));
cursor.setPosition(positions->GetEnd(literal_index),
QTextCursor::KeepAnchor);
completion_start_=positions->GetStart(literal_index);
completion_end_=positions->GetEnd(literal_index);
QTextCursor cp=textCursor();
QString text=cursor.selectedText();
int sep=text.lastIndexOf("/");
cp.setPosition(positions->GetStart(literal_index));
if (sep!=-1) {
cp.movePosition(QTextCursor::NextCharacter,
QTextCursor::MoveAnchor, sep);
}
if (QDir(text).isAbsolute()) {
emit SetPathCompletionPrefix(text);
} else {
emit SetPathCompletionPrefix(QDir::currentPath()+"/"+text);
}
emit RequestPathCompletion(cursorRect(cp),
inline_completion);
} else {
cursor.clearSelection();
cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
QString command=cursor.selectedText();
QRegExp rx("[\\w,\\.]*$");
int index=rx.indexIn(command);
completion_start_=cursor.position()+index;
completion_end_=cursor.position()+index+rx.cap().size();
emit SetCompletionPrefix(rx.cap());
// determine position for completion popup. This is basically the position
// of the first character after a dot or the beginning of the completion
// prefix if there is no dot.
QTextCursor cp=textCursor();
int dot=command.lastIndexOf('.');
int left_shift=rx.cap().size();
if (dot>=index) {
left_shift=rx.cap().size()-(dot-index);
}
cp.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor,
left_shift);
emit RequestCompletion(cursorRect(cp), inline_completion);
}
}
bool PythonShellWidget::handle_custom_commands_(QKeyEvent* event)
{
if ((event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) &&
event->key() == Qt::Key_H) {
set_output_visible_(!output_visible_);
return true;
}
if ((event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) &&
event->key() == Qt::Key_W) {
wrap_into_function_(textCursor().selectedText());
return true;
}
// custom handling of CTRL+A to select only text in edit or output section
if (event->modifiers() == Qt::ControlModifier &&
event->key() == Qt::Key_A) {
QTextCursor cursor=textCursor();
if(cursor.position()<GetEditStartBlock().position()){
// select all output
cursor.setPosition(0);
cursor.setPosition(GetEditStartBlock().position(),QTextCursor::KeepAnchor);
setTextCursor(cursor);
}else{
//select whole edit section
cursor.setPosition(GetEditStartBlock().position());
cursor.movePosition(QTextCursor::End,QTextCursor::KeepAnchor);
setTextCursor(cursor);
}
return true;
}
return false;
}
bool PythonShellWidget::handle_completion_(QKeyEvent* event)
{
if(event->key() == Qt::Key_Tab){
QRegExp rx("^\\s*$"); // only white spaces from beginning of line
QTextCursor cursor= textCursor();
cursor.clearSelection();
cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
QString command=cursor.selectedText();
if(!command.contains(rx)){
Complete();
return true;
}
}
return false;
}
QTextBlock PythonShellWidget::GetEditStartBlock()
{
return block_edit_start_;
}
void PythonShellWidget::keyPressEvent(QKeyEvent* event)
{
if (this->handle_custom_commands_(event)){
return;
}
if (this->handle_completion_(event)){
return;
}
QPlainTextEdit::keyPressEvent(event);
}
void PythonShellWidget::mouseReleaseEvent(QMouseEvent * event)
{
QTextCursor mouse_cursor=cursorForPosition(event->pos());
if (get_block_edit_mode_()==EDITMODE_MULTILINE_INACTIVE &&
event->button() == Qt::LeftButton &&
mouse_cursor.position()>=document()->lastBlock().position()) {
// switch to active edit mode upon mouse click in edit section
set_block_edit_mode_(EDITMODE_MULTILINE_ACTIVE);
}
if (event->button() == Qt::MidButton &&
mouse_cursor.position()<document()->lastBlock().position()) {
// fix cursor position before inserting text
setTextCursor(mouse_cursor);
return;
}
QPlainTextEdit::mouseReleaseEvent(event);
}
void PythonShellWidget::set_block_edit_mode_(BlockEditMode flag)
{
block_edit_mode_=flag;
if(flag==EDITMODE_MULTILINE_ACTIVE){
set_block_type_(block_edit_start_,document()->lastBlock(),BLOCKTYPE_BLOCKEDIT);
}else if(flag==EDITMODE_MULTILINE_INACTIVE){
set_block_type_(block_edit_start_,document()->lastBlock(),BLOCKTYPE_ACTIVE);
}else {
set_block_type_(block_edit_start_,document()->lastBlock(),BLOCKTYPE_ACTIVE);
}
gutter_->update();
}
BlockEditMode PythonShellWidget::get_block_edit_mode_()
{
return block_edit_mode_;
}
QString PythonShellWidget::GetCommand()
{
QTextCursor cursor(block_edit_start_);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
QString text= cursor.selectedText();
text.replace(QChar::LineSeparator,"\n");
text.replace(QChar::ParagraphSeparator,"\n");
return text;
}
void PythonShellWidget::insertFromMimeData(const QMimeData * source)
{
if(!source->hasText()){
return;
}
QString text=source->text();
readonly_machine_->setActive(readwrite_state_);
text=text.replace("\r\n", QString(1, QChar::LineSeparator))
.replace('\n', QChar::LineSeparator)
.replace('\r', QChar::LineSeparator);
// replace spaces at the beginning of lines by tabs, because spaces are evil!
QStringList lines=text.split(QChar::LineSeparator);
for (int i=0; i <lines.size(); ++i) {
int num_spaces=0;
for (int j=0; j<lines[i].size()-1; j+=2) {
if (lines[i][j]==' ' && lines[i][j+1]==' ') {
num_spaces+=1;
} else {
break;
}
}
QString right=lines[i].right(lines[i].size()-2*num_spaces);
lines[i]=QString(num_spaces, '\t')+right;
}
text=lines.join(QString(1, QChar::LineSeparator));
if(lines.size()>0){
machine_->setActive(multiline_active_state_);
}
QPlainTextEdit::insertFromMimeData(source);
if(lines.size()>0){
set_block_type_(block_edit_start_,document()->lastBlock(),BLOCKTYPE_BLOCKEDIT);
}
}
GutterBlockList PythonShellWidget::GetGutterBlocks(const QRect& rect)
{
GutterBlockList result;
for (QTextBlock i=firstVisibleBlock(); i!=document()->end(); i=i.next()) {
QRectF bounds=blockBoundingGeometry(i);
QRectF geom=bounds.translated(contentOffset());
if (geom.top()<=rect.bottom() && (geom.top()>=rect.top() ||
geom.bottom() <= rect.bottom() || (geom.top() <= rect.top() &&
geom.bottom() >= rect.bottom())) ) {
BlockType bt=static_cast<BlockType>(i.userState());
result.push_back(GutterBlock(geom.top(),geom.bottom(),bt));
} else {
break;
}
}
return result;
}
void PythonShellWidget::set_output_visible_(bool flag)
{
output_visible_=flag;
for (QTextBlock i=document()->begin(); i!=document()->end(); i=i.next()) {
if(i.userState()==BLOCKTYPE_ERROR || i.userState()==BLOCKTYPE_OUTPUT){
i.setVisible(flag);
i.setLineCount(flag ? qMax(1, i.layout()->lineCount()) : 0);
}
}
dynamic_cast<PythonShellTextDocumentLayout*>(document()->documentLayout())->requestUpdate();
dynamic_cast<PythonShellTextDocumentLayout*>(document()->documentLayout())->EmitSizeChange();
ensureCursorVisible();
repaint();
gutter_->update();
viewport()->update();
this->update();
}
void PythonShellWidget::resizeEvent(QResizeEvent* event)
{
QPlainTextEdit::resizeEvent(event);
QRect cr=contentsRect();
gutter_->setGeometry(QRect(cr.left(), cr.top(),Gutter::GUTTER_WIDTH,
cr.height()));
}
void PythonShellWidget::showEvent(QShowEvent* event)
{
static bool init=false;
if(!init) {
init=true;
}
}
}}