在CSDN论坛看到有网友问用Qt如何实现一个类似shell的东西。
同时呢,前两天V8已经成为Qt5的基础模块了,刚好,可以做个简单的javascript的shell试试看(只支持单行输入)。
于是,便有了本文。
何处着手?
先用google搜索一下,未找到自己想要的答案(当然,有些比较靠谱的答案:比如去看现有shell(像konsole等)的源码,只是自己懒,不想看)。
初看起来,似乎是个比较简单的东西。
- 用一个只读的控件来显示输入和输出
- 用一个输入控件来接受输入
按照这个思路,加上对shell的期望:
- 选择只读的 QPlainTextEdit 作为主控件
- 用一个 QLineEdit 接受输入
于是:就是下面的效果了,
注意
难点?
在贴出源码之前,先提一点这个:
但是,获取这个位置有些困难,为此,动用了 QPlainTextEditPrivate 这个私有类!
源码
分为三个文件
- shelldemo.h
- shelldemo.cpp
- main.cpp
shelldemo.h
创建QPlainTextEdit的派生类
- runCommand() 将输入的命令进行处理,而后输出
- onEditFinished() 响应输出完时的回车
- onScrollBarValueChanged() 滚动条变动时,我们要调整QLineEdit的位置
- resizeEvent() 窗口变化时,我们也要调整QLineEdit的大小和位置
#ifndef SHELLDEMO_H #define SHELLDEMO_H #include class QLineEdit; class ShellDemo:public QPlainTextEdit { Q_OBJECT public: explicit ShellDemo(QWidget *parent=0); virtual QString runCommand(const QString& cmd); protected: void resizeEvent(QResizeEvent *e); private slots: void onScrollBarValueChanged(); void onEditFinished(); private: void updateEditPosition(); QLineEdit * edit; }; #endif // SHELLDEMO_H
shelldemo.cpp
这个是重点了,你可以忽略其中关于Qt5和V8的代码(已经被宏保住了,不会影响你的编译)。
#include "shelldemo.h" #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif ShellDemo::ShellDemo(QWidget *parent) : QPlainTextEdit(parent) { setReadOnly(true); QFont font = this->font(); font.setPointSize(font.pointSize()+2); this->setFont(font); appendPlainText(">>> "); edit = new QLineEdit(this->viewport()); edit->setStyleSheet("border-style:none; background-color:transparent;"); connect(edit, SIGNAL(returnPressed()), SLOT(onEditFinished())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(onScrollBarValueChanged())); } void ShellDemo::resizeEvent(QResizeEvent *e) { updateEditPosition(); } void ShellDemo::onScrollBarValueChanged() { updateEditPosition(); } QString ShellDemo::runCommand(const QString &cmd) { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) QString output; v8::HandleScope handle_scope; v8::Persistent context = v8::Context::New(); v8::Context::Scope context_scope(context); v8::Handle source = v8::String::New(cmd.utf16()); v8::TryCatch try_catch; v8::Handle script = v8::Script::Compile(source); if (script.IsEmpty()) { output = QString::fromUtf16(*(v8::String::Value(try_catch.Exception()))); } else { v8::Handle result = script->Run(); if (result.IsEmpty()) { output = QString::fromUtf16(*(v8::String::Value(try_catch.Exception()))); } else { output = QString::fromUtf16(*(v8::String::Value(result))); } } context.Dispose(); return output; #else return QString("Result of %1").arg(cmd); #endif } void ShellDemo::onEditFinished() { QString cmd = edit->text(); if (cmd.isEmpty()) { return; } moveCursor(QTextCursor::End); insertPlainText(cmd); edit->hide(); edit->clear(); appendPlainText(runCommand(cmd)); appendPlainText(">>> "); updateEditPosition(); edit->show(); edit->setFocus(); } void ShellDemo::updateEditPosition() { QPlainTextEditPrivate * d = reinterpret_cast(qGetPtrHelper(d_ptr)); QRectF rect = d->control->blockBoundingRect(d->control->document()->lastBlock()); edit->move(rect.topRight().toPoint()); edit->resize(viewport()->size().width(), edit->size().height()); }
简单说一下:
用来更新QLineEdit的位置(这里面的代码?你凑活看吧,其实代码还有些问题)。
基本没做什么。上面设置了大两个点的字体,纯粹是为了舒服一点(不然在我机子上比较难看)。QLineEdit隐藏掉边框,以便和主控件融为一体。
根据输入,产生什么输出,你说了算。我只是为了学习下V8,胡乱加了点代码。
将输入输出显示到主控件中。
main.cpp
很常规的文件,不用多说。
#include #include "shelldemo.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); ShellDemo w; w.setWindowTitle("Dbzhang800\'s Qt5 V8-Shell"); w.show(); return a.exec(); }
qt5v8.pro
- .pro 文件很简单,对于Qt5,我们使用了v8模块
contains(QT_VERSION, ^5\\..*\\..*) {
QT += v8-private gui-private core-private
}
SOURCES += main.cpp \
shelldemo.cpp
HEADERS += \
shelldemo.h \
shellodemo_p.h
后记