QCad源码分析 第五章

讲解一下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对象。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值