Previous Entry Поделиться Next Entry
QT scripting + GUI
coder
olegy wrote in ru_qt4
Захотелось мне как то в 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 >= 1)
      r.setX(context->argument(0).toInt32());
    if(count >= 2)
      r.setY(context->argument(1).toInt32());
    if(count >= 3)
      r.setWidth(context->argument(2).toInt32());
    if(count >= 4)
      r.setHeight(context->argument(3).toInt32());
    return toScriptValue(engine, r);
  }
  //---------------------------
  static QScriptValue toScriptValue(QScriptEngine *engine, const QRect &s)
  {
    QScriptValue obj = engine->newObject();
    obj.setProperty("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 == 1)
    {
      QRect *src = qscriptvalue_cast<QRect *>(context->argument(0).toObject());
      if(src != NULL)
        return engine->newVariant(QVariant(*src));
    }
    if(count >= 1)
      r.setX(context->argument(0).toInt32());
    if(count >= 2)
      r.setY(context->argument(1).toInt32());
    if(count >= 3)
      r.setWidth(context->argument(2).toInt32());
    if(count >= 4)
      r.setHeight(context->argument(3).toInt32());
    return engine->newVariant(QVariant(r));
  }
  //---------------------------
  static QScriptValue toString(QScriptContext *context, QScriptEngine *engine)
  {
    QRect *r = qscriptvalue_cast<QRect *>(context->thisObject());
    if(r == NULL)
      return context->throwError(QScriptContext::TypeError, "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 == NULL)
      return context->throwError(QScriptContext::TypeError, "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 == NULL)
      return context->throwError(QScriptContext::TypeError, "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 == NULL)
      return context->throwError(QScriptContext::TypeError, "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 == NULL)
      return context->throwError(QScriptContext::TypeError, "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 prototype = engine->newObject();
    prototype.setProperty("toString", engine->newFunction(toString));
    prototype.setProperty("x", engine->newFunction(x), QScriptValue::PropertyGetter|QScriptValue::PropertySetter);
    prototype.setProperty("y", engine->newFunction(y), QScriptValue::PropertyGetter|QScriptValue::PropertySetter);
    prototype.setProperty("width", engine->newFunction(width), QScriptValue::PropertyGetter|QScriptValue::PropertySetter);
    prototype.setProperty("height", engine->newFunction(height), QScriptValue::PropertyGetter|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)

Метки:

?

Log in