diff --git a/CMakeLists.txt b/CMakeLists.txt index f38ef6d..8fd2469 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ common_find_package(LibJpegTurbo REQUIRED) common_find_package(OpenGL) common_find_package(Qt5Concurrent REQUIRED SYSTEM) common_find_package(Qt5Core REQUIRED) +common_find_package(Qt5Gui COMPONENTS Private) # For Qml WebEngineView if(APPLE) common_find_package(Qt5MacExtras) endif() diff --git a/deflect/qt/CMakeLists.txt b/deflect/qt/CMakeLists.txt index 21ed5c3..dfb921d 100644 --- a/deflect/qt/CMakeLists.txt +++ b/deflect/qt/CMakeLists.txt @@ -1,8 +1,9 @@ -# Copyright (c) 2015, EPFL/Blue Brain Project -# Daniel Nachbaur +# Copyright (c) 2015-2016, EPFL/Blue Brain Project +# Daniel Nachbaur +# Raphael Dumusc -set(DEFLECTQT_HEADERS EventReceiver.h QmlStreamerImpl.h) +set(DEFLECTQT_HEADERS EventReceiver.h QmlGestures.h QmlStreamerImpl.h) set(DEFLECTQT_PUBLIC_HEADERS QmlStreamer.h) set(DEFLECTQT_SOURCES EventReceiver.cpp QmlStreamer.cpp QmlStreamerImpl.cpp) set(DEFLECTQT_LINK_LIBRARIES @@ -10,3 +11,8 @@ set(DEFLECTQT_LINK_LIBRARIES set(DEFLECTQT_INCLUDE_NAME deflect/qt) set(DEFLECTQT_NAMESPACE deflectqt) common_library(DeflectQt) + +if(TARGET Qt5::Gui) + target_include_directories(DeflectQt SYSTEM PRIVATE + "$") +endif() diff --git a/deflect/qt/EventReceiver.cpp b/deflect/qt/EventReceiver.cpp index 81aaede..c57a887 100644 --- a/deflect/qt/EventReceiver.cpp +++ b/deflect/qt/EventReceiver.cpp @@ -97,13 +97,18 @@ void EventReceiver::_onEvent( int socket ) emit wheeled( deflectEvent.mouseX, deflectEvent.mouseY, deflectEvent.dy ); break; - - case Event::EVT_CLICK: - case Event::EVT_DOUBLECLICK: case Event::EVT_SWIPE_LEFT: + emit swipeLeft(); + break; case Event::EVT_SWIPE_RIGHT: + emit swipeRight(); + break; case Event::EVT_SWIPE_UP: + emit swipeUp(); + break; case Event::EVT_SWIPE_DOWN: + emit swipeDown(); + break; case Event::EVT_KEY_PRESS: emit keyPress( deflectEvent.key, deflectEvent.modifiers, QString::fromStdString( deflectEvent.text )); @@ -112,6 +117,8 @@ void EventReceiver::_onEvent( int socket ) emit keyRelease( deflectEvent.key, deflectEvent.modifiers, QString::fromStdString( deflectEvent.text )); break; + case Event::EVT_CLICK: + case Event::EVT_DOUBLECLICK: default: break; } diff --git a/deflect/qt/EventReceiver.h b/deflect/qt/EventReceiver.h index 5bb2ff0..cacac77 100644 --- a/deflect/qt/EventReceiver.h +++ b/deflect/qt/EventReceiver.h @@ -67,6 +67,10 @@ class EventReceiver : public QObject void wheeled( double x, double y, double dy ); void keyPress( int key, int modifiers, QString text ); void keyRelease( int key, int modifiers, QString text ); + void swipeLeft(); + void swipeRight(); + void swipeUp(); + void swipeDown(); private slots: void _onEvent( int socket ); diff --git a/deflect/qt/QmlGestures.h b/deflect/qt/QmlGestures.h new file mode 100644 index 0000000..593e722 --- /dev/null +++ b/deflect/qt/QmlGestures.h @@ -0,0 +1,67 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef QMLGESTURES_H +#define QMLGESTURES_H + +#include + +namespace deflect +{ +namespace qt +{ + +/** + * Expose gesture events as a Qml context property object. + */ +class QmlGestures : public QObject +{ + Q_OBJECT + +signals: + void swipeLeft(); + void swipeRight(); + void swipeUp(); + void swipeDown(); +}; + +} +} + +#endif diff --git a/deflect/qt/QmlStreamer.cpp b/deflect/qt/QmlStreamer.cpp index 4bc58ea..e23eaf9 100644 --- a/deflect/qt/QmlStreamer.cpp +++ b/deflect/qt/QmlStreamer.cpp @@ -40,6 +40,8 @@ #include "QmlStreamer.h" #include "QmlStreamerImpl.h" +#include "../Stream.h" + namespace deflect { namespace qt @@ -68,5 +70,10 @@ QQmlEngine* QmlStreamer::getQmlEngine() return _impl->getQmlEngine(); } +bool QmlStreamer::sendData( const QByteArray data ) +{ + return _impl->getStream()->sendData( data.constData(), data.size( )); +} + } } diff --git a/deflect/qt/QmlStreamer.h b/deflect/qt/QmlStreamer.h index 0fb32fc..90b7fad 100644 --- a/deflect/qt/QmlStreamer.h +++ b/deflect/qt/QmlStreamer.h @@ -59,6 +59,14 @@ namespace qt * This class renders the given QML file in an offscreen fashion and streams * on each update on the given Deflect stream. It automatically register also * for Deflect events, which can be directly handled in the QML. + + * Users can make connections to the "deflectgestures" context property to react + * to certain gestures received as Event, currently swipe[Left|Right|Up|Down]. + * + * When using a WebEngineView, users must call QtWebEngine::initialize() in the + * QApplication before creating the streamer. Also, due to a limitiation in Qt, + * the objectName property of any WebEngineView must be set to "webengineview" + * for it to receive keyboard events. */ class QmlStreamer : public QObject { @@ -87,6 +95,14 @@ class QmlStreamer : public QObject /** @return the QML engine. */ DEFLECTQT_API QQmlEngine* getQmlEngine(); + /** + * Send data to the Server. + * + * @param data the data buffer + * @return true if the data could be sent, false otherwise + */ + DEFLECTQT_API bool sendData( QByteArray data ); + signals: /** Emitted when the stream has been closed. */ void streamClosed(); diff --git a/deflect/qt/QmlStreamerImpl.cpp b/deflect/qt/QmlStreamerImpl.cpp index c81f51a..f55461a 100644 --- a/deflect/qt/QmlStreamerImpl.cpp +++ b/deflect/qt/QmlStreamerImpl.cpp @@ -39,6 +39,7 @@ #include "QmlStreamerImpl.h" #include "EventReceiver.h" +#include "QmlGestures.h" #include #include @@ -46,14 +47,21 @@ #include #include #include +#include #include #include #include #include +#if DEFLECT_USE_QT5GUI +#include +#endif + namespace { const std::string DEFAULT_STREAM_ID( "QmlStreamer" ); +const QString GESTURES_CONTEXT_PROPERTY( "deflectgestures" ); +const QString WEBENGINEVIEW_OBJECT_NAME( "webengineview" ); } class RenderControl : public QQuickRenderControl @@ -97,10 +105,17 @@ QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, , _stopRenderingDelayTimer( 0 ) , _stream( nullptr ) , _eventHandler( nullptr ) + , _qmlGestures( new QmlGestures ) , _streaming( false ) , _streamHost( streamHost ) , _streamId( streamId ) { + // Expose stream gestures to qml objects + _qmlEngine->rootContext()->setContextProperty( GESTURES_CONTEXT_PROPERTY, + _qmlGestures ); + + _device.setType( QTouchDevice::TouchScreen ); + setSurfaceType( QSurface::OpenGLSurface ); // Qt Quick may need a depth and stencil buffer @@ -112,6 +127,15 @@ QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, _context->setFormat( format_ ); _context->create(); + // Test if user has setup shared GL contexts (QtWebEngine::initialize). + // If so, setup global share context needed by the Qml WebEngineView. + if( QCoreApplication::testAttribute( Qt::AA_ShareOpenGLContexts )) +#if DEFLECT_USE_QT5GUI + qt_gl_set_global_share_context( _context ); +#else + qWarning() << "DeflectQt was not compiled with WebEngineView support"; +#endif + // Pass _context->format(), not format_. Format does not specify and color // buffer sizes, while the context, that has just been created, reports a // format that has these values filled in. Pass this to the offscreen @@ -253,27 +277,31 @@ void QmlStreamer::Impl::_requestRender() void QmlStreamer::Impl::_onPressed( double x_, double y_ ) { - const QPoint point( x_ * width(), y_ * height( )); - QMouseEvent* e = new QMouseEvent( QEvent::MouseButtonPress, point, - Qt::LeftButton, Qt::LeftButton, - Qt::NoModifier ); + auto touchPoint = _makeTouchPoint( 0, { x_, y_ }); + touchPoint.setState( Qt::TouchPointPressed ); + + auto* e = new QTouchEvent( QEvent::TouchBegin, &_device, Qt::NoModifier, + Qt::TouchPointPressed, { touchPoint } ); QCoreApplication::postEvent( _quickWindow, e ); } void QmlStreamer::Impl::_onMoved( double x_, double y_ ) { - const QPoint point( x_ * width(), y_ * height( )); - QMouseEvent* e = new QMouseEvent( QEvent::MouseMove, point, Qt::LeftButton, - Qt::LeftButton, Qt::NoModifier ); + auto touchPoint = _makeTouchPoint( 0, { x_, y_ }); + touchPoint.setState( Qt::TouchPointMoved ); + + auto* e = new QTouchEvent( QEvent::TouchUpdate, &_device, Qt::NoModifier, + Qt::TouchPointMoved, { touchPoint } ); QCoreApplication::postEvent( _quickWindow, e ); } void QmlStreamer::Impl::_onReleased( double x_, double y_ ) { - const QPoint point( x_ * width(), y_ * height( )); - QMouseEvent* e = new QMouseEvent( QEvent::MouseButtonRelease, point, - Qt::LeftButton, Qt::NoButton, - Qt::NoModifier ); + auto touchPoint = _makeTouchPoint( 0, { x_, y_ }); + touchPoint.setState( Qt::TouchPointReleased ); + + auto* e = new QTouchEvent( QEvent::TouchEnd, &_device, Qt::NoModifier, + Qt::TouchPointReleased, { touchPoint } ); QCoreApplication::postEvent( _quickWindow, e ); } @@ -293,37 +321,54 @@ void QmlStreamer::Impl::_onResized( double x_, double y_ ) void QmlStreamer::Impl::_onKeyPress( int key, int modifiers, QString text ) { - QKeyEvent* keyEvent_ = new QKeyEvent( QEvent::KeyPress, key, - (Qt::KeyboardModifiers)modifiers, - text ); + QKeyEvent keyEvent_( QEvent::KeyPress, key, + (Qt::KeyboardModifiers)modifiers, text ); _send( keyEvent_ ); } void QmlStreamer::Impl::_onKeyRelease( int key, int modifiers, QString text ) { - QKeyEvent* keyEvent_ = new QKeyEvent( QEvent::KeyRelease, key, - (Qt::KeyboardModifiers)modifiers, - text ); + QKeyEvent keyEvent_( QEvent::KeyRelease, key, + (Qt::KeyboardModifiers)modifiers, text ); _send( keyEvent_ ); } -void QmlStreamer::Impl::_send( QKeyEvent* keyEvent_ ) +void QmlStreamer::Impl::_send( QKeyEvent& keyEvent_ ) { // Work around missing key event support in Qt for offscreen windows. - const QList items = - _rootItem->findChildren( QString(), - Qt::FindChildrenRecursively ); - for( QQuickItem* item : items ) + if( _sendToWebengineviewItems( keyEvent_ )) + return; + + const auto items = _rootItem->findChildren(); + for( auto item : items ) { if( item->hasFocus( )) { - _quickWindow->sendEvent( item, keyEvent_ ); - if( keyEvent_->isAccepted()) + _quickWindow->sendEvent( item, &keyEvent_ ); + if( keyEvent_.isAccepted()) break; } } - delete keyEvent_; +} + +bool QmlStreamer::Impl::_sendToWebengineviewItems( QKeyEvent& keyEvent_ ) +{ + // Special handling for WebEngineView in offscreen Qml windows. + + const auto items = + _rootItem->findChildren( WEBENGINEVIEW_OBJECT_NAME ); + for( auto webengineviewItem : items ) + { + if( webengineviewItem->hasFocus( )) + { + for( auto child : webengineviewItem->childItems( )) + QCoreApplication::instance()->sendEvent( child, &keyEvent_ ); + if( keyEvent_.isAccepted( )) + return true; + } + } + return false; } bool QmlStreamer::Impl::_setupRootItem() @@ -417,6 +462,16 @@ bool QmlStreamer::Impl::_setupDeflectStream() connect( _eventHandler, &EventReceiver::keyRelease, this, &QmlStreamer::Impl::_onKeyRelease ); + // Forward gestures to Qml context object + connect( _eventHandler, &EventReceiver::swipeDown, + _qmlGestures, &QmlGestures::swipeDown ); + connect( _eventHandler, &EventReceiver::swipeUp, + _qmlGestures, &QmlGestures::swipeUp ); + connect( _eventHandler, &EventReceiver::swipeLeft, + _qmlGestures, &QmlGestures::swipeLeft ); + connect( _eventHandler, &EventReceiver::swipeRight, + _qmlGestures, &QmlGestures::swipeRight ); + return true; } @@ -434,6 +489,20 @@ void QmlStreamer::Impl::_updateSizes( const QSize& size_ ) } } +QTouchEvent::TouchPoint +QmlStreamer::Impl::_makeTouchPoint( const int id, const QPointF& normPos ) const +{ + const QPoint pos( normPos.x() * width(), normPos.y() * height( )); + + QTouchEvent::TouchPoint touchPoint( id ); + touchPoint.setPressure( 1.0 ); + touchPoint.setPos( pos ); + touchPoint.setScreenPos( pos ); + touchPoint.setNormalizedPos( normPos ); + + return touchPoint; +} + void QmlStreamer::Impl::resizeEvent( QResizeEvent* e ) { _updateSizes( e->size( )); diff --git a/deflect/qt/QmlStreamerImpl.h b/deflect/qt/QmlStreamerImpl.h index 092b2a3..b11bdfe 100644 --- a/deflect/qt/QmlStreamerImpl.h +++ b/deflect/qt/QmlStreamerImpl.h @@ -64,6 +64,7 @@ namespace qt { class EventReceiver; +class QmlGestures; class QmlStreamer::Impl : public QWindow { @@ -77,6 +78,7 @@ class QmlStreamer::Impl : public QWindow QQuickItem* getRootItem() { return _rootItem; } QQmlEngine* getQmlEngine() { return _qmlEngine; } + Stream* getStream() { return _stream; } protected: void resizeEvent( QResizeEvent* e ) final; @@ -104,10 +106,12 @@ private slots: void streamClosed(); private: - void _send( QKeyEvent* keyEvent ); + void _send( QKeyEvent& keyEvent ); + bool _sendToWebengineviewItems( QKeyEvent& keyEvent ); std::string _getDeflectStreamIdentifier() const; bool _setupDeflectStream(); void _updateSizes( const QSize& size ); + QTouchEvent::TouchPoint _makeTouchPoint( int id, const QPointF& pos ) const; QOpenGLContext* _context; QOffscreenSurface* _offscreenSurface; @@ -123,10 +127,13 @@ private slots: Stream* _stream; EventReceiver* _eventHandler; + QmlGestures* _qmlGestures; bool _streaming; const std::string _streamHost; const std::string _streamId; SizeHints _sizeHints; + + QTouchDevice _device; }; } diff --git a/doc/Changelog.md b/doc/Changelog.md index 5870633..033967b 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -5,6 +5,13 @@ Changelog {#Changelog} ### 0.12.0 (git master) +* [123](https://github.com/BlueBrain/Deflect/pull/123): + QmlStreamer is now compatible with Qml WebEngineView items. Users must call + QtWebEngine::initialize() in their QApplication before creating the stream. + To receive keyboard events, the objectName property of a WebEngineView must + be set to "webengineview". + QmlStreamer also exposes the Stream::sendData() function and swipe gestures + are available in Qml from a "deflectgestures" context property. * [119](https://github.com/BlueBrain/Deflect/pull/119): Added deflect::Stream::sendData() to allow sending user-defined information to the deflect::Server.