habrahabr

Как подружить QML с чужим OpenGL контекстом. Часть III: Обработка пользовательского ввода

  • воскресенье, 1 февраля 2015 г. в 02:11:29
http://habrahabr.ru/post/249383/

В данной статье я попытаюсь рассказать о том как передавать события мыши и клавиатуры в QQuickWindow, в случае его использования в связке с QQuickRenderControl. Причиной того, что этому необходимо уделять специальное внимание, является то, что в случае использования QQuickRenderControl, никакого окна на самом деле не создается, соответственно у QQuickWindow нет абсолютно никакой возможности получать какие либо события, и их приходится эмулировать. То же самое касается изменения размеров — об этой операции так же необходимо оповещать в явном виде.

Для тех кто пропустил предыдущие части:

Как инициируютя события в Qt


Отправка события в Qt осуществляется с помощью метода
bool QCoreApplication::sendEvent(QObject* receiver, QEvent* event)
где,
  • receiver — получатель сообщения, в нашем случае это экземпляр QQuickWindow (или его потомка);
  • event — это экземпляр конкретного типа события;

Передача событий мыши


Для корректного функционирования достаточно реализовать 3 события мыши:

QEvent::MouseButtonPress:
    QPointF mousePoint( 150, 201 );
    Qt::MouseButton button = Qt::LeftButton;
    Qt::MouseButton buttons = Qt::LeftButton | Qt::RightButton;
    Qt::KeyboardModifiers modifiers = Qt::AltModifier; 
    QMouseEvent mouseEvent( QEvent::MouseButtonPress, mousePoint, mousePoint, button, buttons, modifiers );
    QCoreApplication::sendEvent( quickWindow, &mouseEvent );

где,
  • mousePoint — текущая позиция мыши в координатах QQuickWindow;
  • button — кнопка мыши вызвавшая данное событие;
  • buttons — все кнопки мыши нажатые в момент генерации события;
  • modifiers — нажатые на клавиатуре клавиши-модификаторы (Ctrl, Alt, Shift и т.д.);

mousePoint используется дважды, поскольку в первый раз передается в координатах QQuckWindow, второй раз в screen координатах. Но поскольку окно на самом не создается, трактуется всегда как окно верхнего уровня, и его позицией мы управляем самостоятельно, то передаем одно и то же значение (как будто окно находится всегда в верхнем левом углу экрана), а при установке позиции окна, просто будем этот факт учитывать.

QEvent::MouseMove:
    QPointF mousePoint( 170, 198 );
    Qt::MouseButton button = Qt::NoButton;
    Qt::MouseButton buttons = Qt::LeftButton | Qt::RightButton;
    Qt::KeyboardModifiers modifiers = Qt::AltModifier; 
    QMouseEvent mouseEvent( QEvent::MouseMove, mousePoint, mousePoint, button, buttons, modifiers );
    QCoreApplication::sendEvent( quickWindow, &mouseEvent );

Поскольку причиной события передвижения мыши является сам факт передвижения мыши, а не какая-либо из кнопок, переменной button присваивается значение Qt::NoButton.

QEvent::MouseButtonRelease:
    QPointF mousePoint( 160, 251 );
    Qt::MouseButton button = Qt::LeftButton;
    Qt::MouseButton buttons = Qt::LeftButton;
    Qt::KeyboardModifiers modifiers = Qt::AltModifier; 
    QMouseEvent mouseEvent( QEvent::MouseButtonRelease, mousePoint, mousePoint, button, buttons, modifiers );
    QCoreApplication::sendEvent( quickWindow, &mouseEvent );

button в данном случае означает кнопку являвшуюся причиной данного события, но в данном случае кнопка была отпущена, соответственно buttons она присутствовать уже не может (иначе Qt начинает обрабатывать это событие неверно).

Передача событий клавиатуры


Аналогично, для корректной обработки событий клавиатуры, достаточно реализовать 2 события:

QEvent::KeyPress:
    Qt::Key qtKey = Qt::Key_Space;
    QKeyEvent keyEvent( QEvent::KeyPress, qtKey, Qt::NoModifier );
    QCoreApplication::sendEvent( quickWindow, &keyEvent );

QEvent::KeyRelease:
    Qt::Key qtKey = Qt::Key_Space;
    QKeyEvent keyEvent( QEvent::KeyRelease, qtKey, Qt::NoModifier );
    QCoreApplication::sendEvent( quickWindow, &keyEvent );

Изменение размеров


Как уже упомяналось выше, offscreen окна в Qt трактутся как окна верхнего уровня, поэтому используем соответствующий метод:
    QSize newSize( 320, 240 );
    quickWindow->setGeometry( 0, 0, newSize.width(), newSize.height() );

Помимо изменения размеров собственно окна, желательно так же изменить размеры FBO, т.к. в противном случае либо получим пикселизацию (при увеличении размера), либо бессмысленное расходование ресурсов (т.к. размер FBO будет больше чем требуется):
    if( context->makeCurrent( offscreenSurface ) ) {
        destroyFbo();
        createFbo();
        context->doneCurrent();
    }

Поскольку изменить размер FBO невозможно, просто удаляем текущий и создаем новый ( см. детали в Первой части ).

На этом все.

Примеры реализации, как обычно, доступены на GitHub
Ну и как и прежде, коментарии, вопросы, здоровая критика — приветствуются.

Продолжение следует...