LAMPCAE/src/PluginWBFZExchangePlugin/QConsoleWidget.cpp

629 lines
17 KiB
C++

#include "QConsoleWidget.h"
#include "QConsoleIODevice.h"
#include <QMenu>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QApplication>
#include <QTextDocumentFragment>
#include <QTextBlock>
#include <QTextCursor>
#include <QDebug>
#include <QStringListModel>
#include <QScrollBar>
#include <QAbstractItemView>
#include <QClipboard>
#include <QMimeData>
void QConsoleWidget::setModelProcess(ModelProcess* app)
{
this->application = app;
// 创建dockerWidget
qDebug() << u8"initCommandDocker,等待初始化命令行窗口";
QDockWidget* dockWidget_command = this->application->getdockWidget_command(u8"命令行窗口");
dockWidget_command->setHidden(false);
dockWidget_command->setWidget(this);
this->qs = new ScriptSession(this); // 创建脚本
QTimer::singleShot(200,qs, SLOT(REPL()));
// 绑定application
//this->qs->settingApplication()
this->qs->settingApplication(this->application);
qDebug() << u8"initCommandDocker,结束初始化命令行窗口";
}
QConsoleWidget::QConsoleWidget(QWidget* parent)
: QPlainTextEdit(parent), mode_(Output), completer_(0)
{
iodevice_ = new QConsoleIODevice(this,this);
QTextCharFormat fmt = currentCharFormat();
for(int i=0; i<nConsoleChannels; i++)
chanFormat_[i] = fmt;
chanFormat_[StandardOutput].setForeground(Qt::darkBlue);
chanFormat_[StandardError].setForeground(Qt::red);
setTextInteractionFlags(Qt::TextEditorInteraction);
setUndoRedoEnabled(false);
}
QConsoleWidget::~QConsoleWidget()
{
}
void QConsoleWidget::setMode(ConsoleMode m)
{
if (m==mode_) return;
if (m==Input) {
QTextCursor cursor = textCursor();
cursor.movePosition(QTextCursor::End);
setTextCursor(cursor);
setCurrentCharFormat(chanFormat_[StandardInput]);
inpos_ = cursor.position();
mode_ = Input;
}
if (m==Output) {
mode_ = Output;
}
}
QString QConsoleWidget::getCommandLine()
{
if (mode_==Output) return QString();
// select text in edit zone (from the input pos to the end)
QTextCursor textCursor = this->textCursor();
textCursor.movePosition(QTextCursor::End);
textCursor.setPosition(inpos_,QTextCursor::KeepAnchor);
QString code = textCursor.selectedText();
code.replace(QChar::ParagraphSeparator,QChar::LineFeed);
return code;
}
void QConsoleWidget::handleReturnKey()
{
QString code = getCommandLine();
// start new block
appendPlainText(QString());
setMode(Output);
QTextCursor textCursor = this->textCursor();
textCursor.movePosition(QTextCursor::End);
setTextCursor(textCursor);
// Update the history
if (!code.isEmpty()) history_.add(code);
// append the newline char and
// send signal / update iodevice
code += "\n";
if (iodevice_->isOpen())
iodevice_->consoleWidgetInput(code);
else {
emit consoleCommand(code);
}
}
void QConsoleWidget::handleTabKey()
{
QTextCursor tc = this->textCursor();
int anchor = tc.anchor();
int position = tc.position();
tc.setPosition(inpos_);
tc.setPosition(position, QTextCursor::KeepAnchor);
QString text = tc.selectedText().trimmed();
tc.setPosition(anchor, QTextCursor::MoveAnchor);
tc.setPosition(position, QTextCursor::KeepAnchor);
if (text.isEmpty())
{
tc.insertText(" ");
}
else
{
updateCompleter();
if (completer_ && completer_->completionCount() == 1)
{
insertCompletion(completer_->currentCompletion());
completer_->popup()->hide();
}
}
}
void QConsoleWidget::updateCompleter()
{
if (!completer_) return;
// if the completer is first shown, mark
// the text position
QTextCursor textCursor = this->textCursor();
if (!completer_->popup()->isVisible()) {
completion_pos_ = textCursor.position();
// qDebug() << "show completer, pos " << completion_pos_;
}
// Get the text between the current cursor position
// and the start of the input
textCursor.setPosition(inpos_, QTextCursor::KeepAnchor);
QString commandText = textCursor.selectedText();
// qDebug() << "code to complete: " << commandText;
// Call the completer to update the completion model
// Place and show the completer if there are available completions
int count;
if ((count = completer_->updateCompletionModel(commandText)))
{
// qDebug() << "found " << count << " completions";
// Get a QRect for the cursor at the start of the
// current word and then translate it down 8 pixels.
textCursor = this->textCursor();
textCursor.movePosition(QTextCursor::StartOfWord);
QRect cr = this->cursorRect(textCursor);
cr.translate(0, 8);
cr.setWidth(completer_->popup()->sizeHintForColumn(0) +
completer_->popup()->verticalScrollBar()->sizeHint().width());
completer_->complete(cr);
}
else
{
// qDebug() << "no completions - hiding";
completer_->popup()->hide();
}
}
void QConsoleWidget::checkCompletionTriggers(const QString &txt)
{
if (!completer_ || completion_triggers_.isEmpty() || txt.isEmpty()) return;
foreach(const QString& tr, completion_triggers_)
{
if (tr.endsWith(txt))
{
QTextCursor tc = this->textCursor();
tc.movePosition(QTextCursor::Left,QTextCursor::KeepAnchor,tr.length());
if (tc.selectedText()==tr) {
updateCompleter();
return;
}
}
}
}
void QConsoleWidget::insertCompletion(const QString& completion)
{
QTextCursor tc = textCursor();
tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor,
tc.position() - inpos_ - completer_->insertPos());
tc.insertText(completion,chanFormat_[StandardInput]);
setTextCursor(tc);
}
void QConsoleWidget::setCompleter(QConsoleWidgetCompleter *c)
{
if (completer_)
{
completer_->setWidget(0);
QObject::disconnect(completer_, SIGNAL(activated(const QString&)), this,
SLOT(insertCompletion(const QString&)));
}
completer_ = c;
if (completer_)
{
completer_->setWidget(this);
QObject::connect(completer_, SIGNAL(activated(const QString&)), this,
SLOT(insertCompletion(const QString&)));
}
}
void QConsoleWidget::keyPressEvent(QKeyEvent* e)
{
if (completer_ && completer_->popup()->isVisible())
{
// The following keys are forwarded by the completer to the widget
switch (e->key())
{
case Qt::Key_Tab:
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Backtab:
e->ignore();
return; // let the completer do default behavior
default:
break;
}
}
QTextCursor textCursor = this->textCursor();
bool selectionInEditZone = isSelectionInEditZone();
// check for user abort request
if (e->modifiers() & Qt::ControlModifier)
{
if (e->key()==Qt::Key_Q) // Ctrl-Q aborts
{
emit abortEvaluation();
e->accept();
return;
}
}
// Allow copying anywhere in the console ...
if (e->key() == Qt::Key_C && e->modifiers() == Qt::ControlModifier)
{
if (textCursor.hasSelection()) copy();
e->accept();
return;
}
// the rest of key events are ignored during output mode
if (mode()!=Input) {
e->ignore();
return;
}
// Allow cut only if the selection is limited to the interactive area ...
if (e->key() == Qt::Key_X && e->modifiers() == Qt::ControlModifier)
{
if (selectionInEditZone) cut();
e->accept();
return;
}
// Allow paste only if the selection is in the interactive area ...
if (e->key() == Qt::Key_V && e->modifiers() == Qt::ControlModifier)
{
if (selectionInEditZone || isCursorInEditZone())
{
const QMimeData* const clipboard = QApplication::clipboard()->mimeData();
const QString text = clipboard->text();
if (!text.isNull())
{
textCursor.insertText(text,channelCharFormat(StandardInput));
}
}
e->accept();
return;
}
int key = e->key();
int shiftMod = e->modifiers() == Qt::ShiftModifier;
if (history_.isActive() && key!=Qt::Key_Up && key!=Qt::Key_Down)
history_.deactivate();
// Force the cursor back to the interactive area
// for all keys except modifiers
if (!isCursorInEditZone() &&
key != Qt::Key_Control &&
key != Qt::Key_Shift &&
key != Qt::Key_Alt)
{
textCursor.movePosition(QTextCursor::End);
setTextCursor(textCursor);
}
switch (key)
{
case Qt::Key_Up:
// Activate the history and move to the 1st matching history item
if (!history_.isActive()) history_.activate(getCommandLine());
if (history_.move(true))
replaceCommandLine(history_.currentValue());
else QApplication::beep();
e->accept();
break;
case Qt::Key_Down:
if (history_.move(false))
replaceCommandLine(history_.currentValue());
else QApplication::beep();
e->accept();
case Qt::Key_Left:
if (textCursor.position() > inpos_)
QPlainTextEdit::keyPressEvent(e);
else
{
QApplication::beep();
e->accept();
}
break;
case Qt::Key_Delete:
e->accept();
if (selectionInEditZone) cut();
else
{
// cursor must be in edit zone
if (textCursor.position() < inpos_) QApplication::beep();
else QPlainTextEdit::keyPressEvent(e);
}
break;
case Qt::Key_Backspace:
e->accept();
if (selectionInEditZone) cut();
else
{
// cursor must be in edit zone
if (textCursor.position() <= inpos_) QApplication::beep();
else QPlainTextEdit::keyPressEvent(e);
}
break;
case Qt::Key_Tab:
e->accept();
handleTabKey();
return;
case Qt::Key_Home:
e->accept();
textCursor.setPosition(inpos_,
shiftMod ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
setTextCursor(textCursor);
break;
case Qt::Key_Enter:
case Qt::Key_Return:
e->accept();
handleReturnKey();
break;
case Qt::Key_Escape:
e->accept();
replaceCommandLine(QString());
break;
default:
e->accept();
setCurrentCharFormat(chanFormat_[StandardInput]);
QPlainTextEdit::keyPressEvent(e);
// check if the last key triggers a completion
checkCompletionTriggers(e->text());
break;
}
if (completer_ && completer_->popup()->isVisible())
{
// if the completer is visible check if it should be updated
if (this->textCursor().position()<completion_pos_)
completer_->popup()->hide();
else
updateCompleter();
}
}
void QConsoleWidget::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = createStandardContextMenu();
QAction* a;
if ((a = menu->findChild<QAction*>("edit-cut")))
a->setEnabled(canCut());
if ((a = menu->findChild<QAction*>("edit-delete")))
a->setEnabled(canCut());
if ((a = menu->findChild<QAction*>("edit-paste")))
a->setEnabled(canPaste());
menu->exec(event->globalPos());
delete menu;
}
bool QConsoleWidget::isSelectionInEditZone() const
{
QTextCursor textCursor = this->textCursor();
if (!textCursor.hasSelection()) return false;
int selectionStart = textCursor.selectionStart();
int selectionEnd = textCursor.selectionEnd();
return selectionStart >= inpos_ && selectionEnd >= inpos_;
}
bool QConsoleWidget::isCursorInEditZone() const
{
return textCursor().position()>=inpos_;
}
bool QConsoleWidget::canPaste() const
{
QTextCursor textCursor = this->textCursor();
if (textCursor.position()<inpos_) return false;
if (textCursor.anchor()<inpos_) return false;
return true;
}
void QConsoleWidget::replaceCommandLine(const QString& str) {
// Select the text after the last command prompt ...
QTextCursor textCursor = this->textCursor();
textCursor.movePosition(QTextCursor::End);
textCursor.setPosition(inpos_, QTextCursor::KeepAnchor);
// ... and replace it with new string.
textCursor.insertText(str,chanFormat_[StandardInput]);
// move to the end of the document
textCursor.movePosition(QTextCursor::End);
setTextCursor(textCursor);
}
void QConsoleWidget::write(const QString & message, const QTextCharFormat& fmt)
{
QTextCharFormat currfmt = currentCharFormat();
QTextCursor tc = textCursor();
if (mode()==Input)
{
// in Input mode output messages are inserted
// before the edit block
// get offset of current pos from the end
int editpos = tc.position();
tc.movePosition(QTextCursor::End);
editpos = tc.position() - editpos;
// convert the input pos as relative from the end
inpos_ = tc.position() - inpos_;
// insert block
tc.movePosition(QTextCursor::StartOfBlock);
tc.insertBlock();
tc.movePosition(QTextCursor::PreviousBlock);
tc.insertText(message,fmt);
tc.movePosition(QTextCursor::End);
// restore input pos
inpos_ = tc.position() - inpos_;
// restore the edit pos
tc.movePosition(QTextCursor::Left,QTextCursor::MoveAnchor,editpos);
setTextCursor(tc);
setCurrentCharFormat(currfmt);
}
else
{
// in output mode messages are appended
QTextCursor tc1 = tc;
tc1.movePosition(QTextCursor::End);
// check is cursor was not at the end
// (e.g. had been moved per mouse action)
bool needsRestore = tc1.position()!=tc.position();
// insert text
setTextCursor(tc1);
textCursor().insertText(message,fmt);
ensureCursorVisible();
// restore cursor if needed
if (needsRestore) setTextCursor(tc);
}
}
void QConsoleWidget::writeStdOut(const QString& s)
{
write(s,chanFormat_[StandardOutput]);
}
void QConsoleWidget::writeStdErr(const QString& s)
{
write(s,chanFormat_[StandardError]);
}
/////////////////// QConsoleWidget::History /////////////////////
#define HISTORY_FILE ".command_history.lst"
QConsoleWidget::History QConsoleWidget::history_;
QConsoleWidget::History::History(void) : pos_(0), active_(false), maxsize_(10000)
{
QFile f(HISTORY_FILE);
if (f.open(QFile::ReadOnly)) {
QTextStream is(&f);
while(!is.atEnd()) add(is.readLine());
}
}
QConsoleWidget::History::~History(void)
{
QFile f(HISTORY_FILE);
if (f.open(QFile::WriteOnly | QFile::Truncate)) {
QTextStream os(&f);
int n = strings_.size();
while(n>0) os << strings_.at(--n) << endl;
}
}
void QConsoleWidget::History::add(const QString& str)
{
active_ = false;
//if (strings_.contains(str)) return;
if (strings_.size() == maxsize_) strings_.pop_back();
strings_.push_front(str);
}
void QConsoleWidget::History::activate(const QString& tk)
{
active_ = true;
token_ = tk;
pos_ = -1;
}
bool QConsoleWidget::History::move(bool dir)
{
if (active_)
{
int next = indexOf ( dir, pos_ );
if (pos_!=next)
{
pos_=next;
return true;
}
else return false;
}
else return false;
}
int QConsoleWidget::History::indexOf(bool dir, int from) const
{
int i = from, to = from;
if (dir)
{
while(i < strings_.size()-1)
{
const QString& si = strings_.at(++i);
if (si.startsWith(token_)) { return i; }
}
}
else
{
while(i > 0)
{
const QString& si = strings_.at(--i);
if (si.startsWith(token_)) { return i; }
}
return -1;
}
return to;
}
/////////////////// Stream manipulators /////////////////////
QTextStream &waitForInput(QTextStream &s)
{
QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
if (d) d->waitForReadyRead(-1);
return s;
}
QTextStream &inputMode(QTextStream &s)
{
QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
if (d && d->widget()) d->widget()->setMode(QConsoleWidget::Input);
return s;
}
QTextStream &outChannel(QTextStream &s)
{
QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
if (d) d->setCurrentWriteChannel(QConsoleWidget::StandardOutput);
return s;
}
QTextStream &errChannel(QTextStream &s)
{
QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
if (d) d->setCurrentWriteChannel(QConsoleWidget::StandardError);
return s;
}