В Qt предусмотрен мощный механизм расширения функциональности приложений с помощью JavaScript-подобного языка сценариев, поддержка которого реализована в модуле Qt Script.
Рассмотрим пример использования этой технологии, в котором задействуем следующие возможности Qt Script:
У нас должно получиться следующее:
Мы обеспечим доступ скрипта к полям ввода/вывода (QLineEdit
). Для редактирования скрипта предусмотрим текстовое поле QTextEdit
. При этом в ресурсах приложения мы поместим три заранее подготовленных простых скрипта, о которых скоро поговорим.
Обратите внимание , что для работы с Qt Script в файле проекта необходимо подключить соответствующую зависимость в дополнение к остальным:
QT += core gui script
Файл mainwidget.h
:
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QtScript/QScriptEngine>
namespace Ui {
class MainWidget;
}
class MainWidget : public QWidget {
Q_OBJECT
public:
explicit MainWidget( QWidget* parent = 0 );
~MainWidget();
private slots:
void loadScript();
void runScript();
private:
Ui::MainWidget* ui;
QScriptEngine m_engine;
};
#endif // MAINWIDGET_H
В основе Qt Script лежит класс QScriptEngine
, который и занимается интерпретацией кода скрипта. Именно его мы определили в качестве одного из полей нашего класса.
Файл mainwidget.cpp
:
#include "mainwidget.h"
#include "ui_mainwidget.h"
#include <QFile>
MainWidget::MainWidget( QWidget* parent ) :
QWidget( parent ),
ui( new Ui::MainWidget ) {
ui->setupUi( this );
connect( ui->bnRun, SIGNAL( clicked( bool ) ), SLOT( runScript() ) );
foreach( QRadioButton* rb, findChildren< QRadioButton* >() ) {
connect( rb, SIGNAL( toggled( bool ) ), SLOT( loadScript() ) );
}
QScriptValue inputVal = m_engine.newQObject( ui->edInput );
m_engine.globalObject().setProperty( "input", inputVal );
QScriptValue outputVal = m_engine.newQObject( ui->edOutput );
m_engine.globalObject().setProperty( "output", outputVal );
loadScript();
}
MainWidget::~MainWidget() {
delete ui;
}
void MainWidget::loadScript() {
QString fileName = "";
if( ui->rbUpperDemo->isChecked() ) {
fileName = "upper_demo.js";
} else if( ui->rbConnectDemo->isChecked() ) {
fileName = "connect_demo.js";
} else if( ui->rbConnectFuncDemo->isChecked() ) {
fileName = "connect_func_demo.js";
}
QFile f( ":/" + fileName );
if( f.open( QIODevice::ReadOnly ) ) {
ui->txtScript->setText( f.readAll() );
}
}
void MainWidget::runScript() {
ui->edInput->disconnect();
ui->edOutput->disconnect();
ui->edOutput->clear();
QScriptValue result = m_engine.evaluate( ui->txtScript->toPlainText() );
if( result.isError() ) {
ui->edOutput->setText( result.toString() );
}
}
Особый интерес представляет функция-член:
void MainWidget::runScript() {
ui->edInput->disconnect();
ui->edOutput->disconnect();
ui->edOutput->clear();
QScriptValue result = m_engine.evaluate( ui->txtScript->toPlainText() );
if( result.isError() ) {
ui->edOutput->setText( result.toString() );
}
}
Сначала мы подчищаем следы запуска прошлых скриптов (отключаем сигналы и очищаем содержимое поля вывода). Затем следует вызов скрипта с помощью QScriptEngine::evaluate()
. В случае синтаксических ошибок мы выводим текст сообщения о проблеме в поле вывода.
Также важно заметить, что скрипт не имеет прямого доступа к элементам Qt-приложения. Поэтому мы должны передать ему все, что нужно, самостоятельно. Что мы и делаем для полей ввода/вывода в конструкторе класса:
QScriptValue inputVal = m_engine.newQObject( ui->edInput );
m_engine.globalObject().setProperty( "input", inputVal );
QScriptValue outputVal = m_engine.newQObject( ui->edOutput );
m_engine.globalObject().setProperty( "output", outputVal );
Теперь внутри скрипта к полю ввода можно обратиться по имени input
, а к полю вывода по имени output
.
Замечание: мы несколько превышаем потребности скрипта и даем ему больше, чем нужно. Например, скрипт сможет изменять содержимое поля input
, хотя должен уметь только читать его. Но пока что не будем обращать на это внимания.
Первый скрипт upper_demo.js
копирует текст из input
в output
, преобразуя его к верхнему регистру:
output.text = input.text.toUpperCase();
Второй скрипт connect_demo.js
выполняет соединение сигнала textChanged()
поля input
со слотом setText()
поля output
:
input.textChanged.connect( output.setText );
Запуск этого скрипта равносилен выполнению обычной привязки сигналов-слотов в Qt:
connect( ui->edInput, SIGNAL( textChanged( QString ) ), ui->edOutput, SLOT( setText( QString ) ) );
Последний скрипт connect_func_demo.js
:
function onTextChanged( text ) {
output.text = text.toUpperCase();
}
input.textChanged.connect( onTextChanged );
В нем мы определяем свою функцию onTextChanged()
, которая принимает параметр text
. В теле функции мы делаем то же самое, что и ранее в скрипте upper_demo.js
.
Само подключение нашей функции в качестве слота аналогично тому, что мы делали в connect_demo.js
.
Скачать пример использования Qt Script
Anonymous:
Вы, случайно, не знаете алгоритм разбора SVGZ файлов? По документации это просто архив, но, совершенно непонятно, как достать все SVG файлы из него по одиночке.
Здравствуйте. Если можете, то пришлите сюда ссылку на пример конкретного SVGZ-файла, с которым хотите работать
Проще посмотреть в своем дистрибутиве. Допустим, Ancient_Egyptians.svgz Это - из пакета kde-apps/libkdegames. Просто группа карт. Хотелось бы разобрать, но не знаю как. Можно, конечно, изучить, как их оттуда извлекают сами игры, типа KPatience, но, вдруг, есть готовое описание или пример.
Anonymous:
Проще посмотреть в своем дистрибутиве. Допустим, Ancient_Egyptians.svgz Это - из пакета kde-apps/libkdegames. Просто группа карт. Хотелось бы разобрать, но не знаю как. Можно, конечно, изучить, как их оттуда извлекают сами игры, типа KPatience, но, вдруг, есть готовое описание или пример.
Вероятно, они извлекаются именно так, как это сделано в следующем коде (на Java): https://github.com/b0n541/jskat-cardextractor/blob/master/src/main/java/org/jskat/extract/CardExtractor.java.
Суть заключается в вырезании нужных областей (с картами) из большой SVG-картинки, которые затем сохраняются на диск в каталог tmp/ в формате PNG. Хотя их вполне можно держать и в памяти.
Очень нужная мне статья, прямо в 10 (хочу алгоритмы регулирования реализовать в виде плагинов).
Не понятны несколько моментов:
1 поля отключаем от сигнала:
ui->edInput->disconnect();
ui->edOutput->disconnect();
, но мы их и не подключали…
2 результирующее поле заполняется только при ошибке,
if( result.isError() ) {
ui->edOutput->setText( result.toString() );
}
или что-то не понимаю?
С 1-ым вопросом, вроде, разобрался: подключение идет по 2-ой радио-кнопке
Подключение идет и по 3-ей радио-кнопке:-)
Остается понять, как результат оказывается в поле edOutput в случае первого сценария?
Anonymous:
Подключение идет и по 3-ей радио-кнопке:-)
Остается понять, как результат оказывается в поле edOutput в случае первого сценария?
Здравствуйте. Для ответа на этот вопрос достаточно внимательно посмотреть на содержимое сценария upper_demo.js:
output.text = input.text.toUpperCase();
Этот фрагмент равносилен следующему коду на C++:
output->setText( input->text().toUpperCase() );
Результат QScriptValue result содержит информацию об успешности интерпретации скрипта, но оторван от операций, выполняемых в нем непосредственно.
Да-да, я понял.
Говорят, что QtScript перевели в разряд deprecated и следует переходить на QJSEngine.
Какие будут рекомендации по этому поводу?
Anonymous
Вы, случайно, не знаете алгоритм разбора SVGZ файлов? По документации это просто архив, но, совершенно непонятно, как достать все SVG файлы из него по одиночке.