2018-11-12 15:46:41 +00:00
|
|
|
// QQuickDrawable.cxx - OSG Drawable using a QQuickRenderControl to draw
|
|
|
|
//
|
|
|
|
// Copyright (C) 2019 James Turner <james@flightgear.org>
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
#include "config.h"
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2020-06-06 10:57:18 +00:00
|
|
|
#include <simgear/compiler.h>
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
#include "QQuickDrawable.hxx"
|
|
|
|
|
|
|
|
#include <QQmlComponent>
|
|
|
|
#include <QQmlContext>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <QQmlEngine>
|
|
|
|
#include <QQuickRenderControl>
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2020-06-06 10:57:18 +00:00
|
|
|
#include <QTimer>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <QCoreApplication>
|
2017-06-06 15:21:16 +00:00
|
|
|
#include <QOpenGLContext>
|
|
|
|
#include <QOpenGLFunctions>
|
|
|
|
#include <QQuickItem>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <QQuickWindow>
|
|
|
|
#include <QSurfaceFormat>
|
|
|
|
#include <QThread>
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2020-06-06 10:57:18 +00:00
|
|
|
|
|
|
|
// private Qt headers, needed to make glue work between Qt and OSG
|
|
|
|
// graphics window unfortunately.
|
|
|
|
#include <private/qopenglcontext_p.h>
|
|
|
|
|
|
|
|
#if defined(SG_MAC)
|
|
|
|
# include "fake_qguiapp_p.h"
|
|
|
|
#else
|
|
|
|
# include <private/qguiapplication_p.h>
|
|
|
|
#endif
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
#include <osg/GraphicsContext>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <osgGA/GUIEventAdapter>
|
|
|
|
#include <osgGA/GUIEventHandler>
|
2017-06-06 15:21:16 +00:00
|
|
|
#include <osgViewer/GraphicsWindow>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <osgViewer/Viewer>
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2019-09-16 08:37:01 +00:00
|
|
|
#include <GUI/DialogStateController.hxx>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <GUI/FGQQWindowManager.hxx>
|
2017-06-06 15:21:16 +00:00
|
|
|
#include <GUI/FGQmlInstance.hxx>
|
|
|
|
#include <GUI/FGQmlPropertyNode.hxx>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <Main/fg_props.hxx>
|
|
|
|
#include <Main/globals.hxx>
|
2019-09-16 08:37:01 +00:00
|
|
|
#include <Viewer/OSGQtAdaption.hxx>
|
2018-11-12 15:46:41 +00:00
|
|
|
#include <simgear/structure/commands.hxx>
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
#if defined(HAVE_PUI)
|
|
|
|
#include <plib/pu.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using namespace osgGA;
|
|
|
|
|
|
|
|
struct QtKey {
|
|
|
|
QtKey(int _o, int _q, QString _s = {}) : osg(_o), qt(_q), s(_s) {}
|
|
|
|
|
|
|
|
int osg;
|
|
|
|
int qt;
|
|
|
|
QString s;
|
2017-06-06 15:21:16 +00:00
|
|
|
};
|
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
const std::initializer_list<QtKey> keymapInit = {
|
|
|
|
// contains all the key mappings except 0..9 and A..Z which are generated
|
|
|
|
// programatically
|
|
|
|
|
|
|
|
{GUIEventAdapter::KEY_Space, Qt::Key_Space, " "},
|
|
|
|
{GUIEventAdapter::KEY_Escape, Qt::Key_Escape, "\x1B"},
|
|
|
|
{GUIEventAdapter::KEY_Return, Qt::Key_Return, "\r"},
|
|
|
|
{GUIEventAdapter::KEY_Tab, Qt::Key_Tab, "\t"},
|
|
|
|
{GUIEventAdapter::KEY_BackSpace, Qt::Key_Backspace, "\x08"},
|
|
|
|
{GUIEventAdapter::KEY_Delete, Qt::Key_Delete, "\x7f"},
|
|
|
|
|
|
|
|
{GUIEventAdapter::KEY_Period, Qt::Key_Period, "."},
|
|
|
|
{GUIEventAdapter::KEY_Comma, Qt::Key_Comma, ","},
|
|
|
|
{GUIEventAdapter::KEY_Colon, Qt::Key_Colon, ":"},
|
|
|
|
{GUIEventAdapter::KEY_Quote, Qt::Key_QuoteLeft, "'"},
|
|
|
|
{GUIEventAdapter::KEY_Quotedbl, Qt::Key_QuoteDbl, "\""},
|
|
|
|
{GUIEventAdapter::KEY_Underscore, Qt::Key_Underscore, "_"},
|
|
|
|
{GUIEventAdapter::KEY_Plus, Qt::Key_Plus, "+"},
|
|
|
|
{GUIEventAdapter::KEY_Minus, Qt::Key_Minus, "-"},
|
|
|
|
{GUIEventAdapter::KEY_Asterisk, Qt::Key_Asterisk, "*"},
|
|
|
|
{GUIEventAdapter::KEY_Equals, Qt::Key_Equal, "="},
|
|
|
|
{GUIEventAdapter::KEY_Slash, Qt::Key_Slash, "/"},
|
|
|
|
|
|
|
|
{GUIEventAdapter::KEY_Left, Qt::Key_Left},
|
|
|
|
{GUIEventAdapter::KEY_Right, Qt::Key_Right},
|
|
|
|
{GUIEventAdapter::KEY_Up, Qt::Key_Up},
|
|
|
|
{GUIEventAdapter::KEY_Down, Qt::Key_Down},
|
|
|
|
|
|
|
|
{GUIEventAdapter::KEY_Shift_L, Qt::Key_Shift},
|
|
|
|
{GUIEventAdapter::KEY_Shift_R, Qt::Key_Shift},
|
|
|
|
{GUIEventAdapter::KEY_Control_L, Qt::Key_Control},
|
|
|
|
{GUIEventAdapter::KEY_Control_R, Qt::Key_Control},
|
|
|
|
{GUIEventAdapter::KEY_Meta_L, Qt::Key_Meta},
|
|
|
|
{GUIEventAdapter::KEY_Meta_R, Qt::Key_Meta},
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<QtKey> global_keymap;
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
class CustomRenderControl : public QQuickRenderControl
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CustomRenderControl(QWindow* win)
|
2018-11-12 15:46:41 +00:00
|
|
|
: _qWindow(win)
|
2017-06-06 15:21:16 +00:00
|
|
|
{
|
2018-11-12 15:46:41 +00:00
|
|
|
// Q_ASSERT(win);
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
QWindow* renderWindow(QPoint* offset) override
|
|
|
|
{
|
|
|
|
if (offset) {
|
2018-11-12 15:46:41 +00:00
|
|
|
*offset = QPoint(0, 0);
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
|
|
|
return _qWindow;
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
private:
|
|
|
|
QWindow* _qWindow = nullptr;
|
|
|
|
};
|
|
|
|
|
|
|
|
class QQuickDrawablePrivate : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
2020-06-06 10:57:18 +00:00
|
|
|
QQuickDrawablePrivate() :
|
|
|
|
renderControlInited(false)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
~QQuickDrawablePrivate()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
CustomRenderControl* renderControl = nullptr;
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
QQmlComponent* qmlComponent = nullptr;
|
|
|
|
QQmlEngine* qmlEngine = nullptr;
|
|
|
|
bool syncRequired = true;
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
QQuickItem* rootItem = nullptr;
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
// this window is neither created()-ed nor shown but is needed by
|
|
|
|
// QQuickRenderControl for historical reasons, and gives us a place to
|
|
|
|
// inject events from the OSG side.
|
|
|
|
QQuickWindow* quickWindow = nullptr;
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
// window representing the OSG window, needed
|
|
|
|
// for making our adpoted context current
|
|
|
|
QWindow* foreignOSGWindow = nullptr;
|
2017-06-06 15:21:16 +00:00
|
|
|
QOpenGLContext* qtContext = nullptr;
|
2020-06-06 10:57:18 +00:00
|
|
|
osg::GraphicsContext* osgContext = nullptr;
|
|
|
|
|
|
|
|
std::atomic_bool renderControlInited;
|
|
|
|
std::atomic_bool syncPending;
|
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
void frameEvent()
|
|
|
|
{
|
|
|
|
if (syncRequired) {
|
|
|
|
renderControl->polishItems();
|
|
|
|
syncRequired = false;
|
2020-06-06 10:57:18 +00:00
|
|
|
syncPending = true;
|
|
|
|
|
|
|
|
osgContext->add(flightgear::makeGraphicsOp("Sync QQ2 Render control", [this](osg::GraphicsContext*) {
|
|
|
|
QOpenGLContextPrivate::setCurrentContext(qtContext);
|
|
|
|
renderControl->sync();
|
|
|
|
syncPending = false;
|
|
|
|
}));
|
2018-11-12 15:46:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
public slots:
|
2017-06-06 15:21:16 +00:00
|
|
|
void onComponentLoaded()
|
|
|
|
{
|
|
|
|
if (qmlComponent->isError()) {
|
|
|
|
QList<QQmlError> errorList = qmlComponent->errors();
|
2018-11-12 15:46:41 +00:00
|
|
|
Q_FOREACH (const QQmlError& error, errorList) {
|
2017-06-06 15:21:16 +00:00
|
|
|
qWarning() << error.url() << error.line() << error;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
QObject* rootObject = qmlComponent->create();
|
2017-06-06 15:21:16 +00:00
|
|
|
if (qmlComponent->isError()) {
|
|
|
|
QList<QQmlError> errorList = qmlComponent->errors();
|
2018-11-12 15:46:41 +00:00
|
|
|
Q_FOREACH (const QQmlError& error, errorList) {
|
2017-06-06 15:21:16 +00:00
|
|
|
qWarning() << error.url() << error.line() << error;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
rootItem = qobject_cast<QQuickItem*>(rootObject);
|
2017-06-06 15:21:16 +00:00
|
|
|
if (!rootItem) {
|
|
|
|
qWarning() << Q_FUNC_INFO << "root object not a QQuickItem" << rootObject;
|
|
|
|
delete rootObject;
|
|
|
|
return;
|
|
|
|
}
|
2020-06-06 10:57:18 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
// The root item is ready. Associate it with the window.
|
|
|
|
rootItem->setParentItem(quickWindow->contentItem());
|
|
|
|
syncRequired = true;
|
2018-11-12 15:46:41 +00:00
|
|
|
rootItem->setWidth(quickWindow->width());
|
|
|
|
rootItem->setHeight(quickWindow->height());
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
2020-06-06 10:57:18 +00:00
|
|
|
|
|
|
|
void initRenderControl()
|
|
|
|
{
|
|
|
|
qtContext = flightgear::qtContextFromOSG(osgContext);
|
|
|
|
|
|
|
|
#if QT_VERSION < 0x050600
|
|
|
|
SG_LOG(SG_GUI, SG_ALERT, "Qt < 5.6 was used to build FlightGear, multi-threading of QtQuick is not safe");
|
|
|
|
#else
|
|
|
|
renderControl->prepareThread(QThread::currentThread());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
QOpenGLContextPrivate::setCurrentContext(qtContext);
|
|
|
|
QOpenGLContextPrivate::get(qtContext)->surface = foreignOSGWindow;
|
|
|
|
renderControl->initialize(qtContext);
|
|
|
|
|
|
|
|
renderControlInited = true;
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
void onSceneChanged()
|
|
|
|
{
|
|
|
|
syncRequired = true;
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
void onRenderRequested()
|
|
|
|
{
|
|
|
|
qWarning() << Q_FUNC_INFO;
|
|
|
|
}
|
2020-06-06 10:57:18 +00:00
|
|
|
|
|
|
|
void onWindowActiveFocusItemChanged()
|
|
|
|
{
|
|
|
|
if (quickWindow->activeFocusItem())
|
|
|
|
qInfo() << Q_FUNC_INFO << "Active focus item is now:" << quickWindow->activeFocusItem();
|
|
|
|
else
|
|
|
|
qInfo() << Q_FUNC_INFO << "Active focus cleared";
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
};
|
|
|
|
|
2020-06-06 10:57:18 +00:00
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
static QObject* fgqmlinstance_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
|
|
|
|
{
|
|
|
|
Q_UNUSED(engine)
|
|
|
|
Q_UNUSED(scriptEngine)
|
|
|
|
|
|
|
|
FGQmlInstance* instance = new FGQmlInstance;
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
static QObject* fgqq_windowManager_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
|
|
|
|
{
|
|
|
|
Q_UNUSED(scriptEngine)
|
|
|
|
|
|
|
|
FGQQWindowManager* instance = new FGQQWindowManager(engine);
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
class ReloadCommand : public SGCommandMgr::Command
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ReloadCommand(QQuickDrawable* qq) : _drawable(qq)
|
2017-06-06 15:21:16 +00:00
|
|
|
{
|
2018-11-12 15:46:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(const SGPropertyNode* aNode, SGPropertyNode* root) override
|
|
|
|
{
|
|
|
|
SG_UNUSED(aNode);
|
|
|
|
SG_UNUSED(root);
|
2020-06-06 10:57:18 +00:00
|
|
|
|
|
|
|
QTimer::singleShot(0, [this]() {
|
|
|
|
std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path");
|
|
|
|
_drawable->reload(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath)));
|
|
|
|
});
|
2018-11-12 15:46:41 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
QQuickDrawable* _drawable;
|
|
|
|
};
|
|
|
|
|
|
|
|
class QuickEventHandler : public osgGA::GUIEventHandler
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
QuickEventHandler(QQuickDrawablePrivate* p) : _drawable(p)
|
|
|
|
{
|
|
|
|
populateKeymap();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool handle(const GUIEventAdapter& ea, GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*) override
|
|
|
|
{
|
|
|
|
Q_UNUSED(aa);
|
|
|
|
|
|
|
|
if (ea.getHandled()) return false;
|
|
|
|
|
|
|
|
// frame event, ho hum ...
|
|
|
|
if (ea.getEventType() == GUIEventAdapter::FRAME) {
|
|
|
|
_drawable->frameEvent();
|
2017-06-06 15:21:16 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
// Qt expects increasing downward mouse coords
|
|
|
|
const float fixedY = (ea.getMouseYOrientation() == GUIEventAdapter::Y_INCREASING_UPWARDS) ? ea.getWindowHeight() - ea.getY() : ea.getY();
|
|
|
|
const double pixelRatio = _drawable->foreignOSGWindow->devicePixelRatio();
|
|
|
|
|
|
|
|
QPointF pointInWindow{ea.getX() / pixelRatio, fixedY / pixelRatio};
|
|
|
|
QPointF screenPt = pointInWindow +
|
|
|
|
QPointF{ea.getWindowX() / pixelRatio, ea.getWindowY() / pixelRatio};
|
|
|
|
|
|
|
|
// const int scaledX = static_cast<int>(ea.getX() / static_pixelRatio);
|
|
|
|
// const int scaledY = static_cast<int>(fixedY / static_pixelRatio);
|
|
|
|
|
|
|
|
switch (ea.getEventType()) {
|
|
|
|
case (GUIEventAdapter::DRAG):
|
|
|
|
case (GUIEventAdapter::MOVE): {
|
|
|
|
QMouseEvent m(QEvent::MouseMove, pointInWindow, pointInWindow, screenPt,
|
|
|
|
Qt::NoButton,
|
|
|
|
osgButtonMaskToQt(ea), osgModifiersToQt(ea));
|
|
|
|
QCoreApplication::sendEvent(_drawable->quickWindow, &m);
|
|
|
|
return m.isAccepted();
|
|
|
|
}
|
|
|
|
|
|
|
|
case (GUIEventAdapter::PUSH):
|
|
|
|
case (GUIEventAdapter::RELEASE): {
|
|
|
|
const bool isUp = (ea.getEventType() == GUIEventAdapter::RELEASE);
|
|
|
|
QMouseEvent m(isUp ? QEvent::MouseButtonRelease : QEvent::MouseButtonPress,
|
|
|
|
pointInWindow, pointInWindow, screenPt,
|
|
|
|
osgButtonToQt(ea),
|
|
|
|
osgButtonMaskToQt(ea), osgModifiersToQt(ea));
|
|
|
|
QCoreApplication::sendEvent(_drawable->quickWindow, &m);
|
|
|
|
|
|
|
|
if (!isUp) {
|
|
|
|
if (m.isAccepted()) {
|
|
|
|
// deactivate PUI
|
|
|
|
auto active = puActiveWidget();
|
|
|
|
if (active) {
|
|
|
|
active->invokeDownCallback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// on mouse downs which we don't accept, take focus back
|
|
|
|
// qInfo() << "Clearing QQ focus";
|
|
|
|
// auto focused = _drawable->quickWindow->activeFocusItem();
|
|
|
|
// if (focused) {
|
|
|
|
// focused->setFocus(false, Qt::MouseFocusReason);
|
|
|
|
// }
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
return m.isAccepted();
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
case (GUIEventAdapter::KEYDOWN):
|
|
|
|
case (GUIEventAdapter::KEYUP): {
|
2020-06-06 10:57:18 +00:00
|
|
|
if (!_drawable->quickWindow->activeFocusItem()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
const bool isKeyRelease = (ea.getEventType() == GUIEventAdapter::KEYUP);
|
|
|
|
const auto& key = osgKeyToQt(ea.getKey());
|
|
|
|
QString s = key.s;
|
|
|
|
|
|
|
|
QKeyEvent k(isKeyRelease ? QEvent::KeyRelease : QEvent::KeyPress,
|
|
|
|
key.qt, osgModifiersToQt(ea), s);
|
|
|
|
QCoreApplication::sendEvent(_drawable->quickWindow, &k);
|
|
|
|
return k.isAccepted();
|
|
|
|
}
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
default:
|
2018-11-12 15:46:41 +00:00
|
|
|
return false;
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
private:
|
|
|
|
Qt::MouseButtons osgButtonMaskToQt(const osgGA::GUIEventAdapter& ea) const
|
|
|
|
{
|
|
|
|
const int mask = ea.getButtonMask();
|
|
|
|
Qt::MouseButtons result = Qt::NoButton;
|
|
|
|
if (mask & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
|
|
|
|
result |= Qt::LeftButton;
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
if (mask & osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON)
|
|
|
|
result |= Qt::MiddleButton;
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
if (mask & osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON)
|
|
|
|
result |= Qt::RightButton;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::MouseButton osgButtonToQt(const osgGA::GUIEventAdapter& ea) const
|
|
|
|
{
|
|
|
|
switch (ea.getButton()) {
|
|
|
|
case osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON: return Qt::LeftButton;
|
|
|
|
case osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON: return Qt::MiddleButton;
|
|
|
|
case osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON: return Qt::RightButton;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2017-06-06 15:21:16 +00:00
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
return Qt::NoButton;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::KeyboardModifiers osgModifiersToQt(const osgGA::GUIEventAdapter& ea) const
|
|
|
|
{
|
|
|
|
Qt::KeyboardModifiers result = Qt::NoModifier;
|
|
|
|
const int mask = ea.getModKeyMask();
|
|
|
|
if (mask & osgGA::GUIEventAdapter::MODKEY_ALT) result |= Qt::AltModifier;
|
|
|
|
if (mask & osgGA::GUIEventAdapter::MODKEY_CTRL) result |= Qt::ControlModifier;
|
|
|
|
if (mask & osgGA::GUIEventAdapter::MODKEY_META) result |= Qt::MetaModifier;
|
|
|
|
if (mask & osgGA::GUIEventAdapter::MODKEY_SHIFT) result |= Qt::ShiftModifier;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QtKey osgKeyToQt(int code) const
|
|
|
|
{
|
|
|
|
auto it = std::lower_bound(global_keymap.begin(), global_keymap.end(),
|
|
|
|
QtKey{code, 0, {}},
|
|
|
|
[](const QtKey& a, const QtKey& b) { return a.osg < b.osg; });
|
|
|
|
if ((it == global_keymap.end()) || (it->osg != code)) {
|
|
|
|
qWarning() << "no mapping defined for OSG key:" << code;
|
|
|
|
return {0, 0, ""};
|
|
|
|
}
|
|
|
|
|
|
|
|
return *it;
|
|
|
|
}
|
|
|
|
|
|
|
|
void populateKeymap()
|
|
|
|
{
|
|
|
|
if (!global_keymap.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// regular keymappsing for A..Z and 0..9
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
|
|
|
global_keymap.emplace_back(GUIEventAdapter::KEY_0 + i, Qt::Key_0 + i, QString::number(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < 26; ++i) {
|
|
|
|
global_keymap.emplace_back(GUIEventAdapter::KEY_A + i, Qt::Key_A + i, QChar::fromLatin1('a' + i));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < 26; ++i) {
|
|
|
|
global_keymap.emplace_back('A' + i, Qt::Key_A + i, QChar::fromLatin1('A' + i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// custom key mappsing
|
|
|
|
global_keymap.insert(global_keymap.end(), keymapInit);
|
|
|
|
|
|
|
|
// sort by OSG code for fast lookups
|
|
|
|
std::sort(global_keymap.begin(), global_keymap.end(),
|
|
|
|
[](const QtKey& a, const QtKey& b) { return a.osg < b.osg; });
|
|
|
|
}
|
|
|
|
|
|
|
|
QQuickDrawablePrivate* _drawable;
|
|
|
|
};
|
|
|
|
|
|
|
|
QQuickDrawable::QQuickDrawable() : d(new QQuickDrawablePrivate)
|
2017-06-06 15:21:16 +00:00
|
|
|
{
|
|
|
|
setUseDisplayList(false);
|
|
|
|
setDataVariance(Object::DYNAMIC);
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
osg::StateSet* stateSet = getOrCreateStateSet();
|
|
|
|
stateSet->setRenderBinDetails(1001, "RenderBin");
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
QSurfaceFormat format;
|
|
|
|
format.setRenderableType(QSurfaceFormat::OpenGL);
|
|
|
|
QSurfaceFormat::setDefaultFormat(format);
|
2017-06-06 15:21:16 +00:00
|
|
|
|
|
|
|
static bool doneQmlRegistration = false;
|
|
|
|
if (!doneQmlRegistration) {
|
|
|
|
doneQmlRegistration = true;
|
|
|
|
|
|
|
|
// singleton system object
|
2018-11-12 15:46:41 +00:00
|
|
|
qmlRegisterSingletonType<FGQmlInstance>("FlightGear", 1, 0, "System", fgqmlinstance_provider);
|
|
|
|
qmlRegisterSingletonType<FGQmlInstance>("FlightGear", 1, 0, "WindowManager", fgqq_windowManager_provider);
|
2017-06-06 15:21:16 +00:00
|
|
|
|
|
|
|
// QML types
|
2018-11-12 15:46:41 +00:00
|
|
|
qmlRegisterType<FGQmlPropertyNode>("FlightGear", 1, 0, "Property");
|
2019-09-16 08:37:01 +00:00
|
|
|
qmlRegisterType<DialogStateController>("FlightGear", 1, 0, "DialogStateController");
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
2020-06-06 10:57:18 +00:00
|
|
|
|
|
|
|
globals->get_commands()->addCommandObject("reload-quick-gui", new ReloadCommand(this));
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
|
|
|
|
2020-06-05 19:49:05 +00:00
|
|
|
QQuickDrawable::~QQuickDrawable()
|
|
|
|
{
|
|
|
|
delete d->qmlEngine;
|
|
|
|
delete d->renderControl;
|
|
|
|
}
|
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
void QQuickDrawable::setup(osgViewer::GraphicsWindow *gw, osgViewer::Viewer *viewer)
|
2017-06-06 15:21:16 +00:00
|
|
|
{
|
|
|
|
osg::GraphicsContext* gc = gw;
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2020-06-06 10:57:18 +00:00
|
|
|
// none of this stuff needs the context current, so we can do it
|
|
|
|
// all safely on the main thread
|
|
|
|
|
2018-11-12 15:46:41 +00:00
|
|
|
d->foreignOSGWindow = flightgear::qtWindowFromOSG(gw);
|
|
|
|
// d->foreignOSGWindow->setFormat(format);
|
|
|
|
d->foreignOSGWindow->setSurfaceType(QSurface::OpenGLSurface);
|
2020-06-06 10:57:18 +00:00
|
|
|
|
|
|
|
// QWindow::requestActive would do QPA::makeKey, but on macOS this
|
|
|
|
// is a no-op for foreign windows. So we're going to manually set
|
|
|
|
// the focus window!
|
|
|
|
QGuiApplicationPrivate::focus_window = d->foreignOSGWindow;
|
|
|
|
|
|
|
|
d->osgContext = gc;
|
2018-11-12 15:46:41 +00:00
|
|
|
d->renderControl = new CustomRenderControl(d->foreignOSGWindow);
|
2017-06-06 15:21:16 +00:00
|
|
|
d->quickWindow = new QQuickWindow(d->renderControl);
|
|
|
|
d->quickWindow->setClearBeforeRendering(false);
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
d->qmlEngine = new QQmlEngine;
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
SGPath rootQMLPath = SGPath::fromUtf8(fgGetString("/sim/gui/qml-root-path"));
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "Root QML dir:" << rootQMLPath.dir());
|
|
|
|
d->qmlEngine->addImportPath(QString::fromStdString(rootQMLPath.dir()));
|
|
|
|
// d->qmlEngine->addImportPath(QStringLiteral("qrc:///"));
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
if (!d->qmlEngine->incubationController())
|
|
|
|
d->qmlEngine->setIncubationController(d->quickWindow->incubationController());
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2020-06-06 10:57:18 +00:00
|
|
|
// QObject::connect(d->quickWindow, &QQuickWindow::activeFocusItemChanged,
|
|
|
|
// d.get(), &QQuickDrawablePrivate::onWindowActiveFocusItemChanged);
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
QObject::connect(d->renderControl, &QQuickRenderControl::sceneChanged,
|
2020-06-05 19:49:05 +00:00
|
|
|
d.get(), &QQuickDrawablePrivate::onSceneChanged);
|
2017-06-06 15:21:16 +00:00
|
|
|
QObject::connect(d->renderControl, &QQuickRenderControl::renderRequested,
|
2020-06-05 19:49:05 +00:00
|
|
|
d.get(), &QQuickDrawablePrivate::onRenderRequested);
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
|
2020-06-05 19:49:05 +00:00
|
|
|
viewer->getEventHandlers().push_front(new QuickEventHandler(d.get()));
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QQuickDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
|
|
|
|
{
|
2020-06-06 10:57:18 +00:00
|
|
|
if (!d->renderControlInited) {
|
|
|
|
d->initRenderControl();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (QOpenGLContext::currentContext() != d->qtContext) {
|
|
|
|
QOpenGLContextPrivate::setCurrentContext(d->qtContext);
|
|
|
|
}
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
QOpenGLFunctions* glFuncs = d->qtContext->functions();
|
|
|
|
// prepare any state QQ2 needs
|
|
|
|
d->quickWindow->resetOpenGLState();
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
// and reset these manually
|
|
|
|
glFuncs->glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
|
|
glFuncs->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
|
|
glFuncs->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
|
|
|
glFuncs->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
d->renderControl->render();
|
2018-11-12 15:46:41 +00:00
|
|
|
|
|
|
|
// otherwise the PUI camera gets confused
|
|
|
|
d->quickWindow->resetOpenGLState();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QQuickDrawable::reload(QUrl url)
|
|
|
|
{
|
|
|
|
d->qmlEngine->clearComponentCache();
|
|
|
|
setSource(url);
|
2017-06-06 15:21:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QQuickDrawable::setSource(QUrl url)
|
|
|
|
{
|
2018-11-12 15:46:41 +00:00
|
|
|
if (d->rootItem)
|
|
|
|
delete d->rootItem;
|
|
|
|
if (d->qmlComponent)
|
|
|
|
delete d->qmlComponent;
|
|
|
|
d->rootItem = nullptr;
|
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
d->qmlComponent = new QQmlComponent(d->qmlEngine, url);
|
|
|
|
if (d->qmlComponent->isLoading()) {
|
|
|
|
QObject::connect(d->qmlComponent, &QQmlComponent::statusChanged,
|
2020-06-05 19:49:05 +00:00
|
|
|
d.get(), &QQuickDrawablePrivate::onComponentLoaded);
|
2017-06-06 15:21:16 +00:00
|
|
|
} else {
|
|
|
|
d->onComponentLoaded();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QQuickDrawable::resize(int width, int height)
|
|
|
|
{
|
|
|
|
// we need to unscale from physical pixels back to logical, otherwise we end up double-scaled.
|
2018-11-12 15:46:41 +00:00
|
|
|
const float currentPixelRatio = static_cast<float>(d->foreignOSGWindow->devicePixelRatio());
|
2017-06-06 15:21:16 +00:00
|
|
|
const int logicalWidth = static_cast<int>(width / currentPixelRatio);
|
|
|
|
const int logicalHeight = static_cast<int>(height / currentPixelRatio);
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2017-06-06 15:21:16 +00:00
|
|
|
if (d->rootItem) {
|
|
|
|
d->rootItem->setWidth(logicalWidth);
|
|
|
|
d->rootItem->setHeight(logicalHeight);
|
|
|
|
}
|
2018-11-12 15:46:41 +00:00
|
|
|
|
2020-06-06 10:57:18 +00:00
|
|
|
// SG_LOG(SG_GUI, SG_INFO, "Resize:, lw=" << logicalWidth << ", lh=" << logicalHeight);
|
2017-06-06 15:21:16 +00:00
|
|
|
d->quickWindow->setGeometry(0, 0, logicalWidth, logicalHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "QQuickDrawable.moc"
|