讲解一下Add-ons,即RAction的执行过程
前提条件是MainWindow已将所有的界面Add-ons加载完成,QCad中的Action都会绑定一个脚本,例如“Draw”菜单下的“Line”下的“Line2P”菜单项的执行过程。
首先Line2P在初始化时会绑定脚本,代码如下
//---------------------------Line2PInit.js文件-----------------------------------
function init(basePath) {
//创建一个RGuiAction项,创建后此项会保存在C++类RGuiAction中的static成员列表中
var action = new RGuiAction(qsTranslate("Line2P", "Line from &2 Points"),
RMainWindowQt.getMainWindow());
action.setRequiresDocument(true);
//设置RGuiAction项绑定的脚本
action.setScriptFile(basePath + "/Line2P.js");
//设置图标
action.setIcon(basePath + "/Line2P.svg");
//设置提示信息
action.setStatusTip(qsTranslate("Line2P", "Draw single line or sequence of lines"));
//设置快捷键
action.setDefaultShortcut(new QKeySequence("l,i"));
//设置命令字符,可以通过命令触发该action
action.setDefaultCommands(["line", "ln", "li", "l"]);
//设置分组
action.setGroupSortOrder(6100);
action.setSortOrder(100);
//设置该action会出现在菜单、工具条、线工具面板、线矩阵面板中
action.setWidgetNames(["DrawLineMenu", "LineToolBar", "LineToolsPanel",
"LineMatrixPanel"]);
}
其中action.setScriptFile(basePath + "/Line2P.js");显示此action绑定的脚本是“Line2P.js”
下面是相关的类结构图:
当“Line2P”菜单项被触发时,会调用“triggered”信号并调用相关的槽“slotTrigger”,信号槽的连接过程如下:
//RGuiAction类的构造函数
RGuiAction::RGuiAction(const QString& text, QObject* parent)
: QAction(text, parent),
factory(NULL),
oriText(text),
groupDefault(false),
forceGlobal(false),
requiresDocument(true),
requiresSelection(false),
requiresUndoableTransaction(false),
requiresRedoableTransaction(false),
override(false),
allowInterrupt(false),
noState(false),
toggleable(false),
iconDisabled(false),
enabledOverride(-1) {
//documentInterface(NULL) {
......
//连接信号和槽
connect(this, SIGNAL(triggered()), this, SLOT(slotTrigger()));
......
}
其中slotTrigger的处理过程如下:
bool RGuiAction::slotTrigger(const QString& command) {
//获取主窗口
RMainWindow* mainWindow = RMainWindow::getMainWindow();
//
if (mainWindow != NULL) {
// display main command somewhere, e.g. in command line:
if (command.isNull()) {
//如果不是命令行调用,得到
QString mainCommand = getMainCommand();
if (!mainCommand.isEmpty()) {
//设置命名为当前命令
mainWindow->handleUserCommand(mainCommand);
}
}
else {
//设置命名为当前命令
mainWindow->handleUserCommand(command);
}
}
// uncheck all other actions in this group and check this action:
//遍历action所在的组,将其他action的状态设置为uncheck
if (!group.isEmpty()) {
setChecked(true);
QList<RGuiAction*> actions = actionsByGroup.values(group);
for (int i = 0; i < actions.size(); ++i) {
RGuiAction* action = actions.at(i);
if (action!=this) {
//设置其他action为uncheck
action->setChecked(false);
}
}
}
//查看脚本文件是否存在
if (scriptFile.size() > 0) {
// call action factory of script handler:
if (requiresDocument && !forceGlobal) {
RDocumentInterface* di;
// if (documentInterface!=NULL) {
// di = documentInterface;
// //qDebug() << "got di: " << (unsigned long int)di;
// }
// else {
//获取当前文档
di = RMainWindow::getDocumentInterfaceStatic();
//qDebug() << "getting di statically: " << (unsigned long int)di;
// }
if (di == NULL) {
qWarning() << "This action requires a document to be open: " << scriptFile;
return true;
}
//如果当前action已经触发,则终止这次的action
if (isToggleable() && !isChecked()) {
// if action is toggleable, terminate already running action:
di->terminateCurrentAction();
return true;
}
//获取脚本后缀
QString extension = QFileInfo(scriptFile).suffix();
//根据扩展得到脚本处理类
RScriptHandler* scriptHandler = di->getScriptHandler(extension);
if (scriptHandler == NULL) {
qWarning("RGuiAction::slotTrigger: "
"no script handler found for scriptFile: %s",
(const char*) scriptFile.toUtf8());
return false;
}
//调用脚本
scriptHandler->createActionDocumentLevel(scriptFile, this);
} else {
RScriptHandler::triggerActionApplicationLevel(scriptFile, this);
}
emit postTriggered();
return true;
} else if (factory != NULL) {
// call C++ action factory:
factory(this);
emit postTriggered();
return true;
}
//qWarning("RGuiAction::doAction: factory is NULL");
// A QAction based action might choose to call QAction::trigger if false is
// returned:
emit postTriggered();
return false;
}
其中的 scriptHandler->createActionDocumentLevel(scriptFile, this); 代码段会调用该RGuiAction绑定的“Line2P.js”文件,createActionDocumentLevel的处理过程如下:
void RScriptHandlerEcma::createActionDocumentLevel(const QString &scriptFile,
RGuiAction *guiAction, RDocumentInterface *documentInterface)
{
if (engine->isEvaluating())
{
// qWarning() << "RScriptHandlerEcma::createActionDocumentLevel(): "
// << scriptFile
// << ": Engine is busy. Aborting...";
if (guiAction == NULL || !guiAction->getAllowInterrupt())
{
return;
}
}
if (guiAction == NULL)
{
qWarning() << "RScriptHandlerEcma::createActionDocumentLevel(): "
<< "guiAction is NULL";
}
// if (documentInterface == NULL && guiAction!=NULL) {
// documentInterface = guiAction->getDocumentInterface();
// }
if (documentInterface == NULL)
{
documentInterface = RMainWindow::getDocumentInterfaceStatic();
}
if (documentInterface == NULL)
{
qWarning() << "RScriptHandlerEcma::createActionDocumentLevel(): "
<< scriptFile
<< ": No document interface given or found.";
return;
}
if (!QFileInfo(scriptFile).exists())
{
getScriptEngine().currentContext()->throwError(QString(
"File %1 does not exists.").arg(scriptFile));
return;
}
//不在用的脚本内存就收集起来
engine->collectGarbage();
//执行脚本
doScript(scriptFile);
//获取文件名的基本名称
QString className = QFileInfo(scriptFile).completeBaseName();
//获取全局变量
QScriptValue globalObject = getScriptEngine().globalObject();
//将action导出为脚本对象
globalObject.setProperty("guiAction", engine->toScriptValue(guiAction));
//将documentinterface导出为脚本对象
globalObject.setProperty("documentInterface", engine->toScriptValue(documentInterface));
//将创建的对象加入到documentInterface中【例如new Line();】
eval("documentInterface.setCurrentAction(new " + className + "(guiAction));");
/*
QScriptValue ecmaConstructor = globalObject.property(className);
if (!(ecmaConstructor.isValid() && ecmaConstructor.isFunction())) {
getScriptEngine().currentContext()->throwError(
QString("Class not found or not valid: %1").arg(className));
return;
}
// call constructor of ECMA class with GUI action as argument:
QScriptValueList constructorArgs;
constructorArgs.append(getScriptEngine().toScriptValue(guiAction));
QScriptValue ecmaObject = ecmaConstructor.construct(constructorArgs);
// if (engine->hasUncaughtException()) {
// qWarning() << engine->uncaughtException().toString();
// qWarning() << "Exception Backtrace:";
// qWarning() << engine->uncaughtExceptionBacktrace().join("\n");
// Q_ASSERT(false);
// }
if (!ecmaObject.isValid()) {
getScriptEngine().currentContext()->throwError(QString(
"Constructor %1 not found.").arg(className));
return;
}
REcmaShellActionAdapter* action = qscriptvalue_cast<REcmaShellActionAdapter*>(ecmaObject.prototype());
if (action!=NULL) {
if (documentInterface != NULL) {
// essential to rescue member variables that were set in the constructor:
action->__qtscript_self = ecmaObject;
documentInterface->setCurrentAction(action);
}
else {
qDebug() << "RScriptHandlerEcma::createActionDocumentLevel: documentInterface is NULL";
}
}
else {
qDebug() << "RScriptHandlerEcma::createActionDocumentLevel: action is NULL";
}
*/
}
其中的doScript(scriptFile);将会把“Line2P.js”加入到QScriptEngine当中,然后会在eval("documentInterface.setCurrentAction(new " + className + "(guiAction));");一句中拼接出“new Line2P('guiaction')”.
//将guiaction导出为脚本对象
globalObject.setProperty("guiAction", engine->toScriptValue(guiAction));
//将documentinterface导出为脚本对象
globalObject.setProperty("documentInterface", engine->toScriptValue(documentInterface));
//将创建的对象加入到documentInterface中【例如new Line();】
eval("documentInterface.setCurrentAction(new " + className + "(guiAction));");
创建Line2P对象以后,鼠标的拾取点的操作都是在操作Line2P对象。