суббота, 17 ноября 2012 г.

Интеграция роликов Adobe Flash/Flex в Qt приложение

Комплект компонентов под общим названием Qtitan Multimedia позволяет разработчикам тесно интегрировать в разрабатываемое Qt приложение такие мультимедийные компоненты, как ролики Adobe Flash, Microsoft Silverlight или документы Adobe Reader.

В прошлый раз мы внедряли в приложение, написанное на Qt, баннер Silverlight. Мы не только отобразили баннер в десктоп-приложении, но и осуществили с ним саму тесную интеграцию:
мы смогли вызвать функцию, определенную в баннере, чтобы передать ей наше имя и,
наш интерактивный баннер смог позвать функцию, определенную в приложении и передать ему результаты голосования.
Подробнее об этом можно прочитать в предыдущем выпуске.

Что будет представлять из себя наш пример на этот раз. Мы построим интерфейс пользователя полностью на Flash, а результаты работы пользователя с интерфейсом будем передавать в Qt приложение. Второй частью нашего примера станет обратная задача - из Qt приложения мы передадим данные во Flash ролик.



Создание примера


Что нам понадобится для создания примера интеграции Flash ролика в Qt приложение:

  1. Нам понадобится редактор Adobe Flash для создания нашего ролика. Мы воспользуемся продуктом Adobe Flash Builder, триальную версию которого можно скачать здесь: http://www.adobe.com/products/flashbuilder/
  2. Для создания Qt приложения нам понадобится Visual Studio C++ 2008 SP1 и собранный ей Qt фреймворк не ниже версии 4.6.х.
  3. Конечно же, компоненты Qtitan Multimedia, берем их здесь: http://www.devmachines.com

Создаем Flash ролик

Adobe Flash Builder предоставляет нам возможность создавать Flash приложения используя технологию Flex. Flex — это большой набор классов (Flex SDK), содержащий многие не вошедшие во Flash компоненты. “Фишкой” Flex является декларативный язык описания интерфейсов MXML, по структуре похожий на XML. Если сравнивать с технологией Microsoft Silverlight, то мы увидим, что MXML схож по своему назначению с языком XAML в Silverlight. С XAML мы работали в прошлый раз, теперь предстоит работа с MXML.

Итак, Adobe Flash Builder скачан и установлен, самое время начать.
Создадим новый проект в Adobe Flash Builder используя меню File -> New -> Flex Project.
В явившемся окне зададим имя проекта: FlashForm и укажем путь для сохранения проекта. На этом настройки проекта можно закончить и нажать кнопку Finish.


В результате мы получим проект, готовый к работе. Слева, в окне Package Explorer, располагается дерево проекта, чуть ниже, в окне Components, располагаются доступные элементы интерфейса, а в центре - рабочая область, на которой, собственно, и будет создаваться Rich Internet Application.
Создавать интерфейс очень просто - перетаскиваем элементы с вкладки Components на рабочую область и выравниваем как нужно.
Набросаем примерно вот такой интерфейс:
Здесь мы применили к нашему приложению стиль Zen.
Компоненты на форме имеют следующие имена (ID):
  • Переключатели (RadioButton): “mr” и “ms”
  • Поля ввода (TextInput): “txtFirstName”, “txtLastName”, “txtEmail”, txtLastName
  • Область текста (TextArea): txtComments
  • Кнопка (Button): “btnSubmit”
Следующим шагом будет написание обработчика кнопки (Click Handler). Выделяем кнопку в дизайнере и во вкладке Properties в поле On Click выбираем пункт Generate Event Handler.
Напишем следующий обработчик нажатия кнопки:
protected function btnSubmit_clickHandler(event:MouseEvent):void
{
  var mrMs:String;
  if (mr.selected == true)
    mrMs = "Mr.";
  if (ms.selected == true)
    mrMs = "Ms.";
  var fn:String = txtFirstName.text;
  var ln:String = txtLastName.text;
  var em:String = txtEmail.text;
  var cm:String = txtComments.text;
  ExternalInterface.call("Submit", mrMs, fn, ln, em, cm);
}
Здесь мы собираем данные со всех полей ввода и передаем их функции call объекта ExternalInterface. Первым параметром функции call идет имя функции JavaScript’a “Submit”, затем передаются аргументы функции. Функцию “Submit” на самом деле будет изображать наше Qt приложение, но для Flash все выглядит как работа с обычными функциями JavaScript.
Класс ExternalInterface используется для связи ролика с контейнером Flash Player’а, например, с веб-страницей, а в нашем случае с плагином Qtitan.

В принципе сейчас мы могли бы приступать к интеграции ролика в Qt приложение, однако, чтобы более не возвращаться к Flash Builder’у, мы сразу напишем реализацию обратной задачи - передачу данных из приложения в ролик.

Откроем исходный код ролика и добавим функцию в секцию CDATA.
private function init():void
{
  ExternalInterface.addCallback("Submit", submit);
}
Здесь мы ставим функцию обратной связи на внешний контейнер, что позволит нам вызывать эту функцию из Qt приложения.
Напишем саму callback-функцию.
private function submit(arg1:String, arg2:String, arg3:String, arg4:String, arg5:String):void
{
  if (arg1 == "Mr.")
     mr.selected = true;
  else
     ms.selected = true;
  txtFirstName.text = arg2;
  txtLastName.text  = arg3;
  txtEmail.text     = arg4;
  txtComments.text  = arg5;
}
Эта функция принимает 5 аргументов и заполняем ими поля ввода Flash-ролика.

Для того, чтобы callback установился при загрузке Flash-ролика, мы должны прописать его в секции xmlns:mx.
xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="init();"

Таким образом, при загрузке ролика Flash Player вызовет функцию init(), что повлечет установку нужного нам callback’a.

На этом работу с Flash можно считать законченной, поэтому мы экспортируем результат нашей работы и перейдем к созданию Qt-приложения и интеграции в него Flash-ролика.
Экспорт из Adobe Flash Builder осуществляется через меню Project -> Export Release Build ...


Создание Qt-приложения

Начнем создание проекта с написания файла проекта:
TEMPLATE = app
DESTDIR = bin
HEADERS        = mainwindow.h
SOURCES        = main.cpp \
               mainwindow.cpp
RESOURCES   = flash.qrc

include($$(QTITANDIR)/src/shared/shared.pri)

Наш ролик с именем FlashForm.swf добавим в файл ресурсов:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
  <file>res/FlashForm.swf</file>
</qresource>
</RCC>

Выполним команду qmake -tp vc, чтобы получить .vcproj файл для Visual Studio.
Откроем проект и откроем файл mainwindow.h. Определим класс MainWindow:
class MainWindow : public QMainWindow
{
  Q_OBJECT
public:
  MainWindow(QWidget *parent = 0);
  static QScriptValue submitFromFlash(QScriptContext *, QScriptEngine *);
protected slots:
  void submit();
  void getUrl(NPluginStreamArgs& args);
protected:
  Qtitan::FlashPlayer * m_flashPlayer;
  QWidget * makeWidget();
};

В этом классе у нас определена статическая функция submitFromFlash для получения данных из Flash ролика. Определен слот submit(), который будет пересылать данные из Qt-приложения во Flash ролик. Слот getUrl нужен для организации взаимодействия с роликом.
Посмотрим на код конструктора класса:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
  setWindowTitle("Flash Qt Exchanger");
  // Создаем Flash Player
  m_flashPlayer = new Qtitan::FlashPlayer();
  m_flashPlayer->setSource(":/res/FlashForm.swf");
  connect(m_flashPlayer, SIGNAL(getUrl(NPluginStreamArgs&)),
         this, SLOT(getUrl(NPluginStreamArgs&)));
  m_flashPlayer->setMinimumSize(450, 300);
  // Регистрируем функцию
  m_flashPlayer->registrScriptableFunction("Submit", SubmitFromFlash);

  // Создаем центральный объект
  QTabWidget * widget  = new QTabWidget(this);
  setCentralWidget(widget);
  // Добавляем FlashForm
  widget->addTab(m_flashPlayer, "Flash App");
  // Добавляем Qt форму
  widget->addTab(makeWidget(), "Qt App");
  // Активируем ролик
  m_flashPlayer->setActive(true);
  // Проверяем результат активации
  if (!m_flashPlayer->isActive())
  {
   // Если ролик неактивен собщаем об этом
   QMessageBox::about(this, tr("Adobe Flash Demo"),
      tr("The <b>Adobe Flash</b> is not installed at your PC."));
  }
}
Здесь мы загружаем Flash ролик аналогично как мы делали это с Silverlight, регистрируем функцию Submit, для получения данных из ролика, и формируем  пользовательский интерфейс.
Пользовательский интерфейс представляет собой QTabWidget - в одной из вкладок располагается Flash-приложение, в другой - Qt-приложение.
Qt-приложение полностью аналогично созданному нами во Flash.
Создает этот интерфейс функция makeWidget() класса MainWindow:
QWidget * MainWindow::makeWidget()
{
  //Создаем центральный виджет
  QWidget * widget   = new QWidget(this);
  QGridLayout * gLayout = new QGridLayout;
  gLayout->setContentsMargins(32, 16, 32, 32);
  widget->setLayout(gLayout);
  //Создаем компоненты
  QLabel * lbl          = new QLabel("<h2>Personal Information</h2>"this);
  QLabel * lblFirstName = new QLabel("First Name"this);
  QLabel * lblLastName = new QLabel("Last Name"this);
  QLabel * lblEmail   = new QLabel("Email"this);
  QLabel * lblComments = new QLabel("Comments"this);
  mr = new QRadioButton("Mr.", widget);
  ms = new QRadioButton("Ms.", widget);
  txtFirstName = new QLineEdit(widget);
  txtLastName = new QLineEdit(widget);
  txtEmail   = new QLineEdit(widget);
  txtComments = new QTextEdit(widget);
  //Создаем управляющую кнопку
  QPushButton * btn = new QPushButton("Submit", widget);
  connect(btn, SIGNAL(clicked()), this, SLOT(submit()));
  //Размещаем их в GridLayout
  gLayout->addWidget(lbl, 0, 0, 1, 2);
  gLayout->addWidget(mr, 1, 0);
  gLayout->addWidget(ms, 1, 1);
  gLayout->addWidget(lblFirstName, 2, 0);
  gLayout->addWidget(txtFirstName, 2, 1);
  gLayout->addWidget(lblLastName, 3, 0);
  gLayout->addWidget(txtLastName, 3, 1);
  gLayout->addWidget(lblEmail, 4, 0);
  gLayout->addWidget(txtEmail, 4, 1);
  gLayout->addWidget(lblComments, 5, 0);
  gLayout->addWidget(txtComments, 5, 1);
  gLayout->addWidget(btn, 6, 1, Qt::AlignRight);
  return widget;
}

Код функции, которая принимает значения из Flash-ролика и устанавливает значения в поля ввода, приведен ниже:
QScriptValue MainWindow::submitFromFlash(QScriptContext* context, QScriptEngine *)
  int count = context->argumentCount();
  if (count == 5)
  {
   QString mrMs   = context->argument(0).toString();
   QString firstName = context->argument(1).toString();
   QString lastName = context->argument(2).toString();
   QString email   = context->argument(3).toString();
   QString cmnts   = context->argument(4).toString();
  
   if (mrMs.contains("Mr"))
       mr->setChecked(true);
   else
       ms->setChecked(true);
   txtFirstName ->setText(firstName);
   txtLastName ->setText(lastName);
   txtEmail   ->setText(email);
   txtComments ->setText(cmnts);
  }

Эта функция определена как статическая, поэтому, чтобы она смогла обратиться к полям ввода класса MainWindow, мы должны сделать элементы управления статическими и вынести за пределы класса:
static QRadioButton * mr;
static QRadioButton * ms;
static QLineEdit * txtFirstName;
static QLineEdit * txtLastName;
static QLineEdit * txtEmail;
static QTextEdit * txtComments;

Ниже приведен код функции, которая передает значения из владки Qt app и передает их Flash- ролику.
void MainWindow::submit()
{
  // Получаем доступ к функциям ролика
  QScriptValue flashObject = m_flashPlayer->pluginScriptValue();
  // Получаем доступ к функции submit
  QScriptValue func = flashObject.property("Submit");
  // Заполняем список аргументов
  QScriptValueList args;
  QString mrMs   = mr->isChecked() ? "Mr." : "Ms.";
  QString firstName = txtFirstName->text();
  QString lastName = txtLastName->text();
  QString email   = txtEmail->text();
  QString cmnts   = txtComments->toPlainText();
  args << mrMs << firstName << lastName << email << cmnts;
  func.call(flashObject, args);
}

Осталось только написать код слота getUrl:
void MainWindow::getUrl(NPluginStreamArgs& args)
{
  QFile* data = new QFile(":/res/FlashForm.swf");
  data->open(QIODevice::ReadOnly);
  args.setIODevice(data);
}

Теперь мы получили передачу данных с вкладки Flash App во вкладку Qt App и наоборот.

Заключение

Что в итоге? Мы увидели, что встроить Flash-приложение в Qt оказалось даже еще проще, чем ролик Silverlight. Со стороны Qt-приложения практически ничего не поменялось, мы только сменили тип объекта с Qtitan::Silverlight на Qtitan::FlashPlayer. Поэтому вы можете разрабатывать интерактивные ролики там где вам удобнее, Microsoft Silverlight или же Adobe Flash - для Qtitan Multimedia разницы нет.

Комментариев нет:

Отправить комментарий