Захотелось мне как то в Qt скрипте создать в диалоге дополнительные элементы управления, чуть чуть порывшись в документации, понял что все не так уж тривиально. Свои "терзания" описал здесь, может кому ни будь поможет.Все сведения почерпнул из статьи "Making Applications Scriptable".Задался целью, что бы работал такой скрипт:
var myBtn = new QPushButton(mainWindow);
var geometry = myBtn.geometry;
print(geometry);
geometry.x = 50;
//geometry.y = 30;
myBtn.geometry = geometry;
myBtn.geometry.y = 30;
myBtn.text = "My button";
1. Ну запустить скрипт и передать ему указатель на mainWindow оказалось просто:
void registerGuiTypes(QScriptEngine *engine);
QScriptValue evaluateFile(QScriptEngine *engine, const QString &fileName);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QScriptEngine engine;
MainWindow w;
/**Init engine*/
//Тут кое какие манипуляции ...
registerGuiTypes(&engine); //Тут самое интересное, см дальше
/**Put application & mainWindow to engine*/
engine.globalObject().setProperty("application", engine.newQObject(&a));
engine.globalObject().setProperty("mainWindow", engine.newQObject(&w));
QScriptValue result = evaluateFile(&engine, "gui_test.js");
if (result.isError())
{
QMessageBox::critical(0, "Evaluating error", result.toString(), QMessageBox::Yes);
}
w.show();
return a.exec();
}
Функция запуска файла:
/**Запуск скрипта из файла*/
QScriptValue evaluateFile(QScriptEngine *engine, const QString &fileName)
{
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly))
{
return engine->evaluate(QString("throw \"Error open file %1\"").arg(fileName));
}
return engine->evaluate(file.readAll(), fileName);
}
Что бы скрипт умел создавать элементы управления (var myBtn = new QPushButton(mainWindow)), сделал такой макрос:
/*----------------------------------------------------------------------------*/
#define ADD_QWIDGET_WRAPER(type) \
class type ## Wrapper\
{\
public:\
static QScriptValue objConstructor(QScriptContext *context, QScriptEngine *engine)\
{\
QWidget *parent = qobject_cast<QWidget *>(context->argument(0).toQObject());\
QWidget *object = new type(parent);\
return engine->newQObject(object, QScriptEngine::ScriptOwnership);\
}\
};\
do\
{\
QScriptValue ctor = engine->newFunction(type##Wrapper::objConstructor);\
QScriptValue metaObject = engine->newQMetaObject(&type::staticMetaObject, ctor);\
engine->globalObject().setProperty(#type, metaObject);\
} while(0)
/*----------------------------------------------------------------------------*/
и в тело registerGuiTypes добавил:
ADD_QWIDGET_WRAPER(QPushButton);
(не забываем нужные инклуды, напр. #include - об этом не пишу)Скрипт имеет доступ ко всем свойствам наследников QObject и к методам объявленным как слоты. С простыми типами пропертей, как то целые и строки проблем нет, например
myBtn.text = "My button";
Вся сложность заключается в том, как работать со свойствами сложных типов, например свойство geometry с типом QRect.Для определения пользовательских типов в скрипте есть два метода:1. с помощью qScriptRegisterMetaType - регистрируется функции преобразования из скриптового объекта в объект С++ и обратно.2. с помощь регистрации прототипа setDefaultPrototype.Я попробовал оба способа, и пока не совсем понял, какой лучше.Для регистрации типа QRect я использовал определения классов со статическими методами только для удобства определения оберток для нескольких типов. Итак первый способ:
Q_DECLARE_METATYPE(QRect);
Q_DECLARE_METATYPE(QRect*);
class QRectWrapper
{
//Конструктор.
static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine)
{
QRect r;
int count = context->argumentCount();
if(count x", s.x());
obj.setProperty("y", s.y());
obj.setProperty("width", s.width());
obj.setProperty("height", s.height());
obj.setProperty("toString", engine->newFunction(toString));
return obj;
}
//-------------------------
static void fromScriptValue(const QScriptValue &obj, QRect &s)
{
s.setX(obj.property("x").toInt32());
s.setY(obj.property("y").toInt32());
s.setWidth(obj.property("width").toInt32());
s.setHeight(obj.property("height").toInt32());
}
//-------------------------
static QScriptValue toString(QScriptContext *context, QScriptEngine *engine)
{
QRect r;
fromScriptValue(context->thisObject(), r);
return QScriptValue(QString("QRect(%1,%2,%3,%4)").arg(
QString::number(r.x()),
QString::number(r.y()),
QString::number(r.width()),
QString::number(r.height())));
}
//---------------------------
public:
static void registerWrapper(QScriptEngine *engine)
{
//Регистрирую преобразование
qScriptRegisterMetaType(engine, toScriptValue, fromScriptValue);
//регистрирую конструктор
engine->globalObject().setProperty("QRect", engine->newFunction(ctor));
};
};
/*----------------------------------------------------------------------------*/
Метод toString добавил для того, что бы в скрипте можно было сделатьprint(geometry);аналог __str__(self) в питоне.Конструктор регистрирую на всякий случай, что бы в скрипте можно было сделать new QRect()и в тело registerGuiTypes добавил:
QRectWrapper::registerWrapper(engine);
Второй способ:
Q_DECLARE_METATYPE(QRect);
Q_DECLARE_METATYPE(QRect*);
class QRectPrototype
{
//---------------------------
static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine)
{
QRect r;
int count = context->argumentCount();
if(count QRect.prototype.toString: this object is not a QRect");
return QScriptValue(QString("QRect(%1,%2,%3,%4)").arg(
QString::number(r->x()),
QString::number(r->y()),
QString::number(r->width()),
QString::number(r->height())));
}
//---------------------------
static QScriptValue x(QScriptContext *context, QScriptEngine *engine)
{
QRect *r = qscriptvalue_cast<QRect *>(context->thisObject());
if(r QRect.prototype.x: this object is not a QRect");
if(context->argumentCount())
r->setX(context->argument(0).toInt32());
return QScriptValue(r->x());
}
//---------------------------
static QScriptValue y(QScriptContext *context, QScriptEngine *engine)
{
QRect *r = qscriptvalue_cast<QRect *>(context->thisObject());
if(r QRect.prototype.y: this object is not a QRect");
if(context->argumentCount())
r->setY(context->argument(0).toInt32());
return QScriptValue(r->y());
}
//---------------------------
static QScriptValue width(QScriptContext *context, QScriptEngine *engine)
{
QRect *r = qscriptvalue_cast<QRect *>(context->thisObject());
if(r QRect.prototype.width: this object is not a QRect");
if(context->argumentCount())
r->setWidth(context->argument(0).toInt32());
return QScriptValue(r->width());
}
//---------------------------
static QScriptValue height(QScriptContext *context, QScriptEngine *engine)
{
QRect *r = qscriptvalue_cast<QRect *>(context->thisObject());
if(r QRect.prototype.height: this object is not a QRect");
if(context->argumentCount())
r->setHeight(context->argument(0).toInt32());
return QScriptValue(r->height());
}
//---------------------------
public:
static void registerPrototype(QScriptEngine *engine)
QScriptValue::PropertySetter);
engine->setDefaultPrototype(qMetaTypeId<QRect>(), prototype);
//регистрирую конструктор
QScriptValue ct = engine->newFunction(ctor);
ct.setProperty("prototype", prototype);
engine->globalObject().setProperty("QRect", ct);
};
/*----------------------------------------------------------------------------*/
и в тело registerGuiTypes добавил:
QRectPrototype::registerPrototype(engine);
Нужно добавить, что авторы рекомендуют свой прототип делать наследников от классов QScriptable и QObject. Пример - Default Prototypes Example.При обоих подходах не работает строка в скрипте
myBtn.geometry.y = 30;
но я подозреваю, что это баг самого движка: выполняется как то так:
temp = propertyGet(myBtn, geometry)
propertySet(temp, y, 30)