Whitelist functionality for tablet apps

This commit is contained in:
Brad Davis 2017-10-25 16:49:23 -07:00
parent ff36cbf45f
commit d162e1cff6
28 changed files with 284 additions and 218 deletions

View file

@ -0,0 +1,18 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Rectangle {
width: 100
height: 100
color: "white"
Rectangle {
width: 10
height: 10
color: "red"
}
Label {
text: OverlayWindowTestString
anchors.centerIn: parent
}
}

View file

@ -22,7 +22,6 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
property var source; property var source;
property var component;
property var dynamicContent; property var dynamicContent;
// Keyboard control properties in case needed by QML content. // Keyboard control properties in case needed by QML content.
@ -35,28 +34,9 @@ Windows.Window {
dynamicContent.destroy(); dynamicContent.destroy();
dynamicContent = null; dynamicContent = null;
} }
component = Qt.createComponent(source); QmlSurface.load(source, contentHolder, function(newObject) {
console.log("Created component " + component + " from source " + source); dynamicContent = newObject;
} });
onComponentChanged: {
console.log("Component changed to " + component)
populate();
}
function populate() {
console.log("Populate called: dynamicContent " + dynamicContent + " component " + component);
if (!dynamicContent && component) {
if (component.status == Component.Error) {
console.log("Error loading component:", component.errorString());
} else if (component.status == Component.Ready) {
console.log("Building dynamic content");
dynamicContent = component.createObject(contentHolder);
} else {
console.log("Component not yet ready, connecting to status change");
component.statusChanged.connect(populate);
}
}
} }
// Handle message traffic from the script that launched us to the loaded QML // Handle message traffic from the script that launched us to the loaded QML

View file

@ -2,6 +2,7 @@ import QtQuick 2.5
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import "."
import "../../styles-uit" import "../../styles-uit"
import "../audio" as HifiAudio import "../audio" as HifiAudio
@ -12,6 +13,31 @@ Item {
property int columnIndex: 0 property int columnIndex: 0
property int count: (flowMain.children.length - 1) property int count: (flowMain.children.length - 1)
Component {
id: buttonComponent
TabletButton { }
}
Component.onCompleted: {
tablet.populateButtons();
}
function createClickedHandler(proxy) {
return function() { proxy.clicked(); }
}
function populateButtons() {
var tabletProxy = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var buttons = tabletProxy.getButtons();
for (var i = 0; i < buttons.length; i++) {
var proxy = buttons[i];
var button = tablet.addButtonProxy(proxy.getProperties());
button.clicked.connect(createClickedHandler(proxy));
proxy.setQmlButton(button);
}
sortButtons();
}
// used to look up a button by its uuid // used to look up a button by its uuid
function findButtonIndex(uuid) { function findButtonIndex(uuid) {
if (!uuid) { if (!uuid) {
@ -47,9 +73,7 @@ Item {
// called by C++ code when a button should be added to the tablet // called by C++ code when a button should be added to the tablet
function addButtonProxy(properties) { function addButtonProxy(properties) {
var component = Qt.createComponent("TabletButton.qml"); var button = buttonComponent.createObject(flowMain);
var button = component.createObject(flowMain);
// copy all properites to button // copy all properites to button
var keys = Object.keys(properties).forEach(function (key) { var keys = Object.keys(properties).forEach(function (key) {
button[key] = properties[key]; button[key] = properties[key];
@ -62,8 +86,6 @@ Item {
button.tabletRoot = parent.parent; button.tabletRoot = parent.parent;
} }
sortButtons();
return button; return button;
} }
@ -83,11 +105,8 @@ Item {
anchors { anchors {
top: parent.top top: parent.top
topMargin: 0
left: parent.left left: parent.left
leftMargin: 0
right: parent.right right: parent.right
rightMargin: 0
} }
gradient: Gradient { gradient: Gradient {

View file

@ -68,18 +68,17 @@ Item {
function loadSource(url) { function loadSource(url) {
tabletApps.clear(); tabletApps.clear();
loader.source = ""; // make sure we load the qml fresh each time. loader.load(url)
loader.source = url; tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
} }
function loadQMLOnTop(url) { function loadQMLOnTop(url) {
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""}); tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
loader.source = ""; loader.load(tabletApps.get(currentApp).appUrl, function(){
loader.source = tabletApps.get(currentApp).appUrl; if (loader.item.hasOwnProperty("gotoPreviousApp")) {
if (loader.item.hasOwnProperty("gotoPreviousApp")) { loader.item.gotoPreviousApp = true;
loader.item.gotoPreviousApp = true; }
} })
} }
function loadWebOnTop(url, injectJavaScriptUrl) { function loadWebOnTop(url, injectJavaScriptUrl) {
@ -92,13 +91,11 @@ Item {
} }
function loadWebBase() { function loadWebBase() {
loader.source = ""; loader.load("hifi/tablet/TabletWebView.qml");
loader.source = "TabletWebView.qml";
} }
function loadTabletWebBase() { function loadTabletWebBase() {
loader.source = ""; loader.load("hifi/tablet/BlocksWebView.qml");
loader.source = "./BlocksWebView.qml";
} }
function returnToPreviousApp() { function returnToPreviousApp() {
@ -110,7 +107,7 @@ Item {
loadSource("TabletWebView.qml"); loadSource("TabletWebView.qml");
loadWebUrl(webUrl, scriptUrl); loadWebUrl(webUrl, scriptUrl);
} else { } else {
loader.source = tabletApps.get(currentApp).appUrl; loader.load(tabletApps.get(currentApp).appUrl);
} }
} }
@ -173,47 +170,72 @@ Item {
} }
} }
Loader { // Hook up callback for clara.io download from the marketplace.
id: loader Connections {
objectName: "loader" id: eventBridgeConnection
asynchronous: false target: eventBridge
onWebEventReceived: {
width: parent.width if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
height: parent.height ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
// Hook up callback for clara.io download from the marketplace.
Connections {
id: eventBridgeConnection
target: eventBridge
onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
}
}
}
onLoaded: {
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (openModal) {
openModal.canceled();
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
} }
} }
} }
Item {
id: loader
objectName: "loader";
anchors.fill: parent;
property string source: "";
property var item: null;
signal loaded;
onWidthChanged: {
if (loader.item) {
loader.item.width = loader.width;
}
}
onHeightChanged: {
if (loader.item) {
loader.item.height = loader.height;
}
}
function load(newSource, callback) {
loader.source = newSource;
loader.item = null;
QmlSurface.load(newSource, loader, function(newItem) {
loader.item = newItem;
loader.item.width = loader.width;
loader.item.height = loader.height;
loader.loaded();
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
if (openModal) {
openModal.canceled();
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
}
if (callback) {
callback();
}
});
console.log("QQQ done calling QmlSurface.load")
}
}
width: 480 width: 480
height: 706 height: 706

View file

@ -2214,6 +2214,16 @@ extern void setupPreferences();
void Application::initializeUi() { void Application::initializeUi() {
// Make sure all QML surfaces share the main thread GL context // Make sure all QML surfaces share the main thread GL context
OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext()); OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "qrc:///qml/OverlayWindowTest.qml" },
[](QQmlContext* context) {
qDebug() << "Whitelist OverlayWindow worked";
context->setContextProperty("OverlayWindowTestString", "TestWorked");
});
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "qrc:///qml/hifi/audio/Audio.qml" },
[](QQmlContext* context) {
qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked";
});
AddressBarDialog::registerType(); AddressBarDialog::registerType();
ErrorDialog::registerType(); ErrorDialog::registerType();
@ -2230,10 +2240,9 @@ void Application::initializeUi() {
auto surfaceContext = offscreenUi->getSurfaceContext(); auto surfaceContext = offscreenUi->getSurfaceContext();
offscreenUi->setProxyWindow(_window->windowHandle()); offscreenUi->setProxyWindow(_window->windowHandle());
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
// support the window management and scripting proxies for VR use // support the window management and scripting proxies for VR use
offscreenUi->createDesktop(QString("qrc:///qml/hifi/Desktop.qml")); offscreenUi->createDesktop(QString("hifi/Desktop.qml"));
// FIXME either expose so that dialogs can set this themselves or // FIXME either expose so that dialogs can set this themselves or
// do better detection in the offscreen UI of what has focus // do better detection in the offscreen UI of what has focus
@ -7194,13 +7203,17 @@ void Application::updateDisplayMode() {
} }
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
// Make the switch atomic from the perspective of other threads // Make the switch atomic from the perspective of other threads
{ {
std::unique_lock<std::mutex> lock(_displayPluginLock); std::unique_lock<std::mutex> lock(_displayPluginLock);
// Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below. bool wasRepositionLocked = false;
bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool(); if (desktop) {
offscreenUi->getDesktop()->setProperty("repositionLocked", true); // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
offscreenUi->getDesktop()->setProperty("repositionLocked", true);
}
if (_displayPlugin) { if (_displayPlugin) {
disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent); disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
@ -7246,7 +7259,6 @@ void Application::updateDisplayMode() {
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin; _displayPlugin = newDisplayPlugin;
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection); connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);
auto desktop = offscreenUi->getDesktop();
if (desktop) { if (desktop) {
desktop->setProperty("repositionLocked", wasRepositionLocked); desktop->setProperty("repositionLocked", wasRepositionLocked);
} }

View file

@ -101,7 +101,7 @@ Menu::Menu() {
auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J); auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J);
connect(action, &QAction::triggered, [] { connect(action, &QAction::triggered, [] {
static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml"); static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml");
static const QUrl tabletUrl("../../hifi/dialogs/TabletRunningScripts.qml"); static const QUrl tabletUrl("hifi/dialogs/TabletRunningScripts.qml");
static const QString name("RunningScripts"); static const QString name("RunningScripts");
qApp->showDialog(widgetUrl, tabletUrl, name); qApp->showDialog(widgetUrl, tabletUrl, name);
}); });
@ -338,7 +338,7 @@ Menu::Menu() {
connect(action, &QAction::triggered, [] { connect(action, &QAction::triggered, [] {
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"); auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>(); auto hmd = DependencyManager::get<HMDScriptingInterface>();
tablet->loadQMLSource("ControllerSettings.qml"); tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml");
if (!hmd->getShouldShowTablet()) { if (!hmd->getShouldShowTablet()) {
hmd->toggleShouldShowTablet(); hmd->toggleShouldShowTablet();

View file

@ -1,4 +1,4 @@
// //
// WalletScriptingInterface.cpp // WalletScriptingInterface.cpp
// interface/src/scripting // interface/src/scripting
// //
@ -23,7 +23,7 @@ void WalletScriptingInterface::refreshWalletStatus() {
wallet->getWalletStatus(); wallet->getWalletStatus();
} }
static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; static const QString CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) { void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href)); QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href));

View file

@ -31,7 +31,7 @@
#include "scripting/HMDScriptingInterface.h" #include "scripting/HMDScriptingInterface.h"
static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml"; static const QVariant TABLET_ADDRESS_DIALOG = "hifi/tablet/TabletAddressDialog.qml";
template<typename T> template<typename T>
void DialogsManager::maybeCreateDialog(QPointer<T>& member) { void DialogsManager::maybeCreateDialog(QPointer<T>& member) {
if (!member) { if (!member) {
@ -91,7 +91,7 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
ConnectionFailureDialog::hide(); ConnectionFailureDialog::hide();
} }
} else { } else {
static const QUrl url("../../dialogs/TabletConnectionFailureDialog.qml"); static const QUrl url("dialogs/TabletConnectionFailureDialog.qml");
auto hmd = DependencyManager::get<HMDScriptingInterface>(); auto hmd = DependencyManager::get<HMDScriptingInterface>();
if (visible) { if (visible) {
tablet->initialScreen(url); tablet->initialScreen(url);

View file

@ -46,7 +46,7 @@ void LoginDialog::showWithSelection()
if (tablet->getToolbarMode()) { if (tablet->getToolbarMode()) {
LoginDialog::show(); LoginDialog::show();
} else { } else {
static const QUrl url("../../dialogs/TabletLoginDialog.qml"); static const QUrl url("dialogs/TabletLoginDialog.qml");
tablet->initialScreen(url); tablet->initialScreen(url);
if (!hmd->getShouldShowTablet()) { if (!hmd->getShouldShowTablet()) {
hmd->openTablet(); hmd->openTablet();

View file

@ -264,7 +264,7 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI
} }
} }
static const QString INSPECTION_CERTIFICATE_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
void ContextOverlayInterface::openInspectionCertificate() { void ContextOverlayInterface::openInspectionCertificate() {
// lets open the tablet to the inspection certificate QML // lets open the tablet to the inspection certificate QML
if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {

View file

@ -30,6 +30,8 @@
using namespace render; using namespace render;
using namespace render::entities; using namespace render::entities;
static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml";
const float METERS_TO_INCHES = 39.3701f; const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount{ 0 }; static uint32_t _currentWebCount{ 0 };
// Don't allow more than 100 concurrent web views // Don't allow more than 100 concurrent web views
@ -218,6 +220,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
}; };
{ {
// FIXME use the surface cache instead of explicit creation
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter); _webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
_webSurface->create(); _webSurface->create();
} }
@ -289,7 +292,6 @@ void WebEntityRenderer::loadSourceURL() {
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
_lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) {
_contentType = htmlContent; _contentType = htmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/"));
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) {
@ -298,12 +300,11 @@ void WebEntityRenderer::loadSourceURL() {
_webSurface->setMaxFps(DEFAULT_MAX_FPS); _webSurface->setMaxFps(DEFAULT_MAX_FPS);
} }
_webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
item->setProperty("url", _lastSourceUrl); item->setProperty("url", _lastSourceUrl);
}); });
} else { } else {
_contentType = qmlContent; _contentType = qmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath()));
_webSurface->load(_lastSourceUrl); _webSurface->load(_lastSourceUrl);
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();

View file

@ -62,7 +62,7 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
QUrl url { properties[SOURCE_PROPERTY].toString() }; QUrl url { properties[SOURCE_PROPERTY].toString() };
if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" && if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" &&
url.scheme() != "atp") { url.scheme() != "atp" && url.scheme() != "qrc") {
properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString(); properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString();
} }

View file

@ -47,6 +47,7 @@
#include "types/HFWebEngineProfile.h" #include "types/HFWebEngineProfile.h"
#include "types/SoundEffect.h" #include "types/SoundEffect.h"
#include "TabletScriptingInterface.h"
#include "Logging.h" #include "Logging.h"
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
@ -98,7 +99,7 @@ void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list
} }
QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {}; QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {};
struct TextureSet { struct TextureSet {
// The number of surfaces with this size // The number of surfaces with this size
@ -586,10 +587,11 @@ void OffscreenQmlSurface::create() {
auto qmlEngine = acquireEngine(_quickWindow); auto qmlEngine = acquireEngine(_quickWindow);
_qmlContext = new QQmlContext(qmlEngine->rootContext()); _qmlContext = new QQmlContext(qmlEngine->rootContext());
_qmlContext->setBaseUrl(QUrl{ "qrc:///qml/" });
_qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlContext->setContextProperty("eventBridge", this); _qmlContext->setContextProperty("eventBridge", this);
_qmlContext->setContextProperty("webEntity", this); _qmlContext->setContextProperty("webEntity", this);
_qmlContext->setContextProperty("QmlSurface", this);
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated // Find a way to flag older scripts using this mechanism and wanr that this is deprecated
@ -684,55 +686,69 @@ QQuickItem* OffscreenQmlSurface::getRootItem() {
return _rootItem; return _rootItem;
} }
void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, bool forceNewContext) {
_qmlContext->setBaseUrl(baseUrl); // Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
forceNewContext |= !callbacks.empty();
QQmlContext* targetContext = _qmlContext;
if (_rootItem && forceNewContext) {
targetContext = new QQmlContext(targetContext);
}
for (const auto& callback : callbacks) {
callback(targetContext);
}
return targetContext;
} }
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) { void OffscreenQmlSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) {
loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) {
QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem));
});
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback) {
loadInternal(qmlSource, createNewContext, nullptr, onQmlLoadedCallback);
}
void OffscreenQmlSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback) {
qCDebug(uiLogging) << "QQQ" << __FUNCTION__ << qmlSource;
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Called load on a non-surface thread"; qCWarning(uiLogging) << "Called load on a non-surface thread";
} }
// Synchronous loading may take a while; restart the deadlock timer // Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
// Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
createNewContext |= !callbacks.empty();
callbacks.push_back(onQmlLoadedCallback);
QQmlContext* targetContext = _qmlContext;
if (_rootItem && createNewContext) {
targetContext = new QQmlContext(targetContext);
}
// FIXME eliminate loading of relative file paths for QML
QUrl finalQmlSource = qmlSource; QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = _qmlContext->resolvedUrl(qmlSource); finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
qCDebug(uiLogging) << "QQQ" << __FUNCTION__ << "resolved to " << finalQmlSource;
} }
auto targetContext = contextForUrl(finalQmlSource, createNewContext);
auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) { if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) { connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) {
finishQmlLoad(qmlComponent, targetContext, callbacks); finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
}); });
return; return;
} }
finishQmlLoad(qmlComponent, targetContext, callbacks); finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
} }
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) {
load(qmlSource, true, onQmlLoadedCallback); load(qmlSource, true, onQmlLoadedCallback);
} }
void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) {
load(qmlSource, false, onQmlLoadedCallback); load(qmlSource, false, onQmlLoadedCallback);
} }
void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) { void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback) {
return load(QUrl(qmlSourceFile), onQmlLoadedCallback); return load(QUrl(qmlSourceFile), onQmlLoadedCallback);
} }
@ -740,7 +756,8 @@ void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache(); _qmlContext->engine()->clearComponentCache();
} }
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks) {
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) {
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) { if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) { for (const auto& error : qmlComponent->errors()) {
@ -762,6 +779,22 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
return; return;
} }
if (!newObject) {
if (!_rootItem) {
qFatal("Could not load object as root item");
return;
}
qCWarning(uiLogging) << "Unable to load QML item";
return;
}
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
}
qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
// All quick items should be focusable // All quick items should be focusable
@ -772,37 +805,26 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
newItem->setFlag(QQuickItem::ItemIsFocusScope, true); newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
} }
// Make sure we will call callback for this codepath // Make sure we will call callback for this codepath
// Call this before qmlComponent->completeCreate() otherwise ghost window appears // Call this before qmlComponent->completeCreate() otherwise ghost window appears
if (newItem && _rootItem) { // If we already have a root, just set a couple of flags and the ancestry
for (const auto& callback : callbacks) { if (_rootItem) {
callback(qmlContext, newObject); callback(qmlContext, newItem);
}
}
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>(); if (!parent) {
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { parent = _rootItem;
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper }
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated // Allow child windows to be destroyed from JS
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(parent);
newItem->setParentItem(parent);
} }
qmlComponent->completeCreate(); qmlComponent->completeCreate();
qmlComponent->deleteLater(); qmlComponent->deleteLater();
// If we already have a root, just set a couple of flags and the ancestry if (_rootItem) {
if (newItem && _rootItem) {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(_rootItem);
if (newItem) {
newItem->setParentItem(_rootItem);
}
return;
}
if (!newItem) {
qFatal("Could not load object as root item");
return; return;
} }
@ -813,10 +835,16 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
_rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize()); _rootItem->setSize(_quickWindow->renderTargetSize());
// Call this callback after rootitem is set, otherwise VrMenu wont work if (_rootItem->objectName() == "tabletRoot") {
for (const auto& callback : callbacks) { _qmlContext->setContextProperty("tabletRoot", QVariant::fromValue(_rootItem));
callback(qmlContext, newObject); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", this);
QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
_qmlContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
} }
// Call this callback after rootitem is set, otherwise VrMenu wont work
callback(qmlContext, newItem);
} }
void OffscreenQmlSurface::updateQuick() { void OffscreenQmlSurface::updateQuick() {

View file

@ -30,12 +30,14 @@ class QQmlContext;
class QQmlComponent; class QQmlComponent;
class QQuickWindow; class QQuickWindow;
class QQuickItem; class QQuickItem;
class QJSValue;
// GPU resources are typically buffered for one copy being used by the renderer, // GPU resources are typically buffered for one copy being used by the renderer,
// one copy in flight, and one copy being used by the receiver // one copy in flight, and one copy being used by the receiver
#define GPU_RESOURCE_BUFFER_SIZE 3 #define GPU_RESOURCE_BUFFER_SIZE 3
using QmlContextCallback = std::function<void(QQmlContext*, QObject*)>; using QmlContextCallback = std::function<void(QQmlContext*)>;
using QmlContextObjectCallback = std::function<void(QQmlContext*, QQuickItem*)>;
class OffscreenQmlSurface : public QObject { class OffscreenQmlSurface : public QObject {
Q_OBJECT Q_OBJECT
@ -43,7 +45,7 @@ class OffscreenQmlSurface : public QObject {
public: public:
static void setSharedContext(QOpenGLContext* context); static void setSharedContext(QOpenGLContext* context);
static QmlContextCallback DEFAULT_CONTEXT_CALLBACK; static QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK;
static void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback);
static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); };
@ -56,10 +58,15 @@ public:
void resize(const QSize& size, bool forceResize = false); void resize(const QSize& size, bool forceResize = false);
QSize size() const; QSize size() const;
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); // Usable from QML code as QmlSurface.load(url, parent, function(newItem){ ... })
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); // For C++ use
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
void clearCache(); void clearCache();
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
// Optional values for event handling // Optional values for event handling
@ -73,7 +80,6 @@ public:
void resume(); void resume();
bool isPaused() const; bool isPaused() const;
void setBaseUrl(const QUrl& baseUrl);
QQuickItem* getRootItem(); QQuickItem* getRootItem();
QQuickWindow* getWindow(); QQuickWindow* getWindow();
QObject* getEventHandler(); QObject* getEventHandler();
@ -124,13 +130,13 @@ protected:
private: private:
static QOpenGLContext* getSharedContext(); static QOpenGLContext* getSharedContext();
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks); QQmlContext* contextForUrl(const QUrl& url, bool forceNewContext = false);
void loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo();
bool allowNewFrame(uint8_t fps); bool allowNewFrame(uint8_t fps);
void render(); void render();
void cleanup(); void cleanup();
QJsonObject getGLContextData();
private slots: private slots:
void updateQuick(); void updateQuick();

View file

@ -45,7 +45,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP
QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) { QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) {
auto surface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface()); auto surface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface());
surface->create(); surface->create();
surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
surface->load(rootSource); surface->load(rootSource);
surface->resize(QSize(100, 100)); surface->resize(QSize(100, 100));
return surface; return surface;

View file

@ -210,9 +210,9 @@ QObject* TabletScriptingInterface::getFlags() {
// TabletProxy // TabletProxy
// //
static const char* TABLET_SOURCE_URL = "Tablet.qml"; static const char* TABLET_SOURCE_URL = "hifi/tablet/Tablet.qml";
static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; static const char* WEB_VIEW_SOURCE_URL = "hifi/tablet/TabletWebView.qml";
static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml";
class TabletRootWindow : public QmlWindowClass { class TabletRootWindow : public QmlWindowClass {
virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; } virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; }
@ -232,6 +232,15 @@ TabletProxy::~TabletProxy() {
disconnect(this, &TabletProxy::tabletShownChanged, this, &TabletProxy::onTabletShown); disconnect(this, &TabletProxy::tabletShownChanged, this, &TabletProxy::onTabletShown);
} }
QVariant TabletProxy::getButtons() {
Q_ASSERT(QThread::currentThread() == qApp->thread());
QVariantList result;
for (const auto& button : _tabletButtonProxies) {
result.push_back(QVariant::fromValue(button.data()));
}
return result;
}
void TabletProxy::setToolbarMode(bool toolbarMode) { void TabletProxy::setToolbarMode(bool toolbarMode) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setToolbarMode", Q_ARG(bool, toolbarMode)); QMetaObject::invokeMethod(this, "setToolbarMode", Q_ARG(bool, toolbarMode));
@ -247,7 +256,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (toolbarMode) { if (toolbarMode) {
removeButtonsFromHomeScreen();
addButtonsToToolbar(); addButtonsToToolbar();
// create new desktop window // create new desktop window
@ -267,7 +275,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
removeButtonsFromToolbar(); removeButtonsFromToolbar();
if (_currentPathLoaded == TABLET_SOURCE_URL) { if (_currentPathLoaded == TABLET_SOURCE_URL) {
addButtonsToHomeScreen(); // Tablet QML now pulls buttons from Tablet proxy
} else { } else {
loadHomeScreen(true); loadHomeScreen(true);
} }
@ -284,18 +292,20 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
} }
} }
#if 0
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
Q_ASSERT(QThread::currentThread() == qApp->thread()); Q_ASSERT(QThread::currentThread() == qApp->thread());
if (buttonProxy == NULL){ if (buttonProxy == NULL){
qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL"; qCCritical(uiLogging) << __FUNCTION__ << "buttonProxy is NULL";
return; return;
} }
QVariant resultVar; QVariant resultVar;
qCDebug(uiLogging) << "QQQ" << __FUNCTION__ << "adding button " << buttonProxy;
bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection, bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties())); Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties()));
if (!hasResult) { if (!hasResult) {
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result"; qCWarning(uiLogging) << __FUNCTION__ << " has no result";
return; return;
} }
@ -307,6 +317,8 @@ static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy*
QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot())); QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot()));
buttonProxy->setQmlButton(qobject_cast<QQuickItem*>(qmlButton)); buttonProxy->setQmlButton(qobject_cast<QQuickItem*>(qmlButton));
} }
#endif
static QString getUsername() { static QString getUsername() {
QString username = "Unknown user"; QString username = "Unknown user";
@ -362,7 +374,7 @@ void TabletProxy::onTabletShown() {
static_cast<TabletScriptingInterface*>(parent())->playSound(TabletScriptingInterface::TabletOpen); static_cast<TabletScriptingInterface*>(parent())->playSound(TabletScriptingInterface::TabletOpen);
if (_showRunningScripts) { if (_showRunningScripts) {
_showRunningScripts = false; _showRunningScripts = false;
pushOntoStack("../../hifi/dialogs/TabletRunningScripts.qml"); pushOntoStack("hifi/dialogs/TabletRunningScripts.qml");
} }
} }
} }
@ -396,9 +408,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
}); });
if (_toolbarMode) { if (_toolbarMode) {
// if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet.
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
} }
@ -427,7 +436,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true))); QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true)));
} }
} else { } else {
removeButtonsFromHomeScreen();
_state = State::Uninitialized; _state = State::Uninitialized;
emit screenChanged(QVariant("Closed"), QVariant("")); emit screenChanged(QVariant("Closed"), QVariant(""));
_currentPathLoaded = ""; _currentPathLoaded = "";
@ -456,7 +464,6 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) {
} }
if (root) { if (root) {
removeButtonsFromHomeScreen();
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
QObject* menu = offscreenUi->getRootMenu(); QObject* menu = offscreenUi->getRootMenu();
QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu)));
@ -530,7 +537,6 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) {
} }
if (root) { if (root) {
removeButtonsFromHomeScreen(); //works only in Tablet
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
_state = State::QML; _state = State::QML;
if (path != _currentPathLoaded) { if (path != _currentPathLoaded) {
@ -612,8 +618,6 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) { if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) {
if (!_toolbarMode && _qmlTabletRoot) { if (!_toolbarMode && _qmlTabletRoot) {
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
} else if (_toolbarMode && _desktopWindow) { } else if (_toolbarMode && _desktopWindow) {
@ -674,7 +678,6 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
} }
if (root) { if (root) {
removeButtonsFromHomeScreen();
if (loadOtherBase) { if (loadOtherBase) {
QMetaObject::invokeMethod(root, "loadTabletWebBase"); QMetaObject::invokeMethod(root, "loadTabletWebBase");
} else { } else {
@ -701,12 +704,8 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) {
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap())); auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
_tabletButtonProxies.push_back(tabletButtonProxy); _tabletButtonProxies.push_back(tabletButtonProxy);
if (!_toolbarMode && _qmlTabletRoot) { if (!_toolbarMode && _qmlTabletRoot) {
auto tablet = getQmlTablet(); // Tablet now pulls buttons from the tablet proxy
if (tablet) { // FIXME emit a signal so that the tablet can refresh buttons if they change
addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data());
} else {
qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml";
}
} else if (_toolbarMode) { } else if (_toolbarMode) {
auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy(); auto toolbarProxy = DependencyManager::get<TabletScriptingInterface>()->getSystemToolbarProxy();
if (toolbarProxy) { if (toolbarProxy) {
@ -791,31 +790,11 @@ void TabletProxy::sendToQml(const QVariant& msg) {
} }
} }
void TabletProxy::addButtonsToHomeScreen() {
auto tablet = getQmlTablet();
if (!tablet || _toolbarMode) {
return;
}
for (auto& buttonProxy : _tabletButtonProxies) {
addButtonProxyToQmlTablet(tablet, buttonProxy.data());
}
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()));
}
OffscreenQmlSurface* TabletProxy::getTabletSurface() { OffscreenQmlSurface* TabletProxy::getTabletSurface() {
return _qmlOffscreenSurface; return _qmlOffscreenSurface;
} }
void TabletProxy::removeButtonsFromHomeScreen() {
auto tablet = getQmlTablet();
for (auto& buttonProxy : _tabletButtonProxies) {
if (tablet) {
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
}
buttonProxy->setQmlButton(nullptr);
}
}
void TabletProxy::desktopWindowClosed() { void TabletProxy::desktopWindowClosed() {
gotoHomeScreen(); gotoHomeScreen();

View file

@ -196,6 +196,8 @@ public:
Q_INVOKABLE bool isPathLoaded(const QVariant& path); Q_INVOKABLE bool isPathLoaded(const QVariant& path);
Q_INVOKABLE QVariant getButtons();
QQuickItem* getTabletRoot() const { return _qmlTabletRoot; } QQuickItem* getTabletRoot() const { return _qmlTabletRoot; }
OffscreenQmlSurface* getTabletSurface(); OffscreenQmlSurface* getTabletSurface();
@ -237,12 +239,10 @@ signals:
void tabletShownChanged(); void tabletShownChanged();
protected slots: protected slots:
void addButtonsToHomeScreen();
void desktopWindowClosed(); void desktopWindowClosed();
void emitWebEvent(const QVariant& msg); void emitWebEvent(const QVariant& msg);
void onTabletShown(); void onTabletShown();
protected: protected:
void removeButtonsFromHomeScreen();
void loadHomeScreen(bool forceOntoHomeScreen); void loadHomeScreen(bool forceOntoHomeScreen);
void addButtonsToToolbar(); void addButtonsToToolbar();
void removeButtonsFromToolbar(); void removeButtonsFromToolbar();
@ -277,7 +277,9 @@ public:
TabletButtonProxy(const QVariantMap& properties); TabletButtonProxy(const QVariantMap& properties);
~TabletButtonProxy(); ~TabletButtonProxy();
void setQmlButton(QQuickItem* qmlButton);
Q_INVOKABLE void setQmlButton(QQuickItem* qmlButton);
void setToolbarButtonProxy(QObject* toolbarButtonProxy); void setToolbarButtonProxy(QObject* toolbarButtonProxy);
QUuid getUuid() const { return _uuid; } QUuid getUuid() const { return _uuid; }

View file

@ -19,7 +19,7 @@
tablet.gotoHomeScreen(); tablet.gotoHomeScreen();
onRecordingScreen = false; onRecordingScreen = false;
} else { } else {
tablet.loadQMLSource("InputRecorder.qml"); tablet.loadQMLSource("hifi/tablet/InputRecorder.qml");
onRecordingScreen = true; onRecordingScreen = true;
} }
} }

View file

@ -1,7 +1,7 @@
print("Launching web window"); print("Launching web window");
qmlWindow = new OverlayWindow({ qmlWindow = new OverlayWindow({
title: 'Test Qml', title: 'Test Qml',
source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml", source: "qrc:///qml/OverlayWindowTest.qml",
height: 240, height: 240,
width: 320, width: 320,
toolWindow: false, toolWindow: false,

View file

@ -15,7 +15,7 @@
var TABLET_BUTTON_NAME = "AUDIO"; var TABLET_BUTTON_NAME = "AUDIO";
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
var AUDIO_QML_SOURCE = "../audio/Audio.qml"; var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml";
var MUTE_ICONS = { var MUTE_ICONS = {
icon: "icons/tablet-icons/mic-mute-i.svg", icon: "icons/tablet-icons/mic-mute-i.svg",

View file

@ -26,8 +26,8 @@
// Relevant Variables: // Relevant Variables:
// -WALLET_QML_SOURCE: The path to the Wallet QML // -WALLET_QML_SOURCE: The path to the Wallet QML
// -onWalletScreen: true/false depending on whether we're looking at the app. // -onWalletScreen: true/false depending on whether we're looking at the app.
var WALLET_QML_SOURCE = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
var onWalletScreen = false; var onWalletScreen = false;
function onButtonClicked() { function onButtonClicked() {
if (!tablet) { if (!tablet) {

View file

@ -656,7 +656,7 @@ var toolBar = (function () {
selectionDisplay.triggerMapping.disable(); selectionDisplay.triggerMapping.disable();
tablet.landscape = false; tablet.landscape = false;
} else { } else {
tablet.loadQMLSource("Edit.qml", true); tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
UserActivityLogger.enabledEdit(); UserActivityLogger.enabledEdit();
entityListTool.setVisible(true); entityListTool.setVisible(true);
gridTool.setVisible(true); gridTool.setVisible(true);

View file

@ -18,7 +18,7 @@
var buttonName = "Settings"; var buttonName = "Settings";
var toolBar = null; var toolBar = null;
var tablet = null; var tablet = null;
var settings = "TabletGeneralPreferences.qml" var settings = "hifi/tablet/TabletGeneralPreferences.qml"
function onClicked(){ function onClicked(){
if (tablet) { if (tablet) {
tablet.loadQMLSource(settings); tablet.loadQMLSource(settings);

View file

@ -346,7 +346,7 @@
Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled);
break; break;
case 'purchases_openGoTo': case 'purchases_openGoTo':
tablet.loadQMLSource("TabletAddressDialog.qml"); tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml");
break; break;
case 'purchases_itemCertificateClicked': case 'purchases_itemCertificateClicked':
setCertificateInfo("", message.itemCertificateId); setCertificateInfo("", message.itemCertificateId);

View file

@ -40,7 +40,7 @@ var HOVER_TEXTURES = {
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
var PAL_QML_SOURCE = "../Pal.qml"; var PAL_QML_SOURCE = "hifi/Pal.qml";
var conserveResources = true; var conserveResources = true;
Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllers.js");

View file

@ -24,7 +24,7 @@
print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
} }
var gotoQmlSource = "TabletAddressDialog.qml"; var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml";
var buttonName = "GOTO"; var buttonName = "GOTO";
var onGotoScreen = false; var onGotoScreen = false;
var shouldActivateButton = false; var shouldActivateButton = false;

View file

@ -24,7 +24,7 @@
if (onSkyboxChangerScreen) { if (onSkyboxChangerScreen) {
tablet.gotoHomeScreen(); tablet.gotoHomeScreen();
} else { } else {
tablet.loadQMLSource("../SkyboxChanger.qml"); tablet.loadQMLSource("hifi/SkyboxChanger.qml");
} }
} }

View file

@ -390,7 +390,7 @@
// Relevant Variables: // Relevant Variables:
// -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML // -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML
// -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app. // -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app.
var SPECTATOR_CAMERA_QML_SOURCE = Script.resourcesPath() + "qml/hifi/SpectatorCamera.qml"; var SPECTATOR_CAMERA_QML_SOURCE = "hifi/SpectatorCamera.qml";
var onSpectatorCameraScreen = false; var onSpectatorCameraScreen = false;
function onTabletButtonClicked() { function onTabletButtonClicked() {
if (!tablet) { if (!tablet) {