From 61e558e5687c0d1bb6b8eab727a0733b141222c4 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 8 Feb 2017 09:53:25 -0700 Subject: [PATCH 01/42] Entity Edit Filters for zones Part 1 - just put them in the zones entities. Next I'll hook them up and actually filter... --- .../entities/src/EntityItemProperties.cpp | 14 +++++++++++-- libraries/entities/src/EntityItemProperties.h | 2 ++ libraries/entities/src/EntityPropertyFlags.h | 2 ++ libraries/entities/src/ZoneEntityItem.cpp | 20 ++++++++++++++++++- libraries/entities/src/ZoneEntityItem.h | 5 +++++ scripts/system/html/entityProperties.html | 5 +++++ scripts/system/html/js/entityProperties.js | 5 ++++- 7 files changed, 49 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 21018d8afa..edfabf84e9 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -332,6 +332,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); + CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL); CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly); CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); @@ -509,6 +510,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FILTER_URL, filterURL); } // Web only @@ -751,6 +753,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); + COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly); COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); @@ -879,6 +882,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(flyingAllowed); COPY_PROPERTY_IF_CHANGED(ghostingAllowed); + COPY_PROPERTY_IF_CHANGED(filterURL); COPY_PROPERTY_IF_CHANGED(clientOnly); COPY_PROPERTY_IF_CHANGED(owningAvatarID); @@ -1063,6 +1067,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); + ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString); ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t); @@ -1311,6 +1316,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1605,6 +1611,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FILTER_URL, QString, setFilterURL); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1798,7 +1805,7 @@ void EntityItemProperties::markAllChanged() { _parentIDChanged = true; _parentJointIndexChanged = true; - + _jointRotationsSetChanged = true; _jointRotationsChanged = true; _jointTranslationsSetChanged = true; @@ -1808,6 +1815,7 @@ void EntityItemProperties::markAllChanged() { _flyingAllowedChanged = true; _ghostingAllowedChanged = true; + _filterURLChanged = true; _clientOnlyChanged = true; _owningAvatarIDChanged = true; @@ -2150,7 +2158,9 @@ QList EntityItemProperties::listChangedProperties() { if (ghostingAllowedChanged()) { out += "ghostingAllowed"; } - + if (filterURLChanged()) { + out += "filterURL"; + } if (dpiChanged()) { out += "dpi"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 1961feaf1d..419740e4ea 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -215,6 +215,7 @@ public: DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED); DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED); + DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL); DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false); DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); @@ -458,6 +459,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, FlyingAllowed, flyingAllowed, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index b77d3cc077..b3cfc143c2 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -185,6 +185,8 @@ enum EntityPropertyList { PROP_SERVER_SCRIPTS, + PROP_FILTER_URL, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 3e21497d63..7de3f6b68f 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -28,7 +28,7 @@ const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX; const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = ""; const bool ZoneEntityItem::DEFAULT_FLYING_ALLOWED = true; const bool ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED = true; - +const QString ZoneEntityItem::DEFAULT_FILTER_URL = ""; EntityItemPointer ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity { new ZoneEntityItem(entityID) }; @@ -61,6 +61,7 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(filterURL, getFilterURL); return properties; } @@ -79,6 +80,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties); @@ -128,6 +130,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); + READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL); return bytesRead; } @@ -147,6 +150,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_FLYING_ALLOWED; requestedProperties += PROP_GHOSTING_ALLOWED; + requestedProperties += PROP_FILTER_URL; return requestedProperties; } @@ -177,6 +181,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); } void ZoneEntityItem::debugDump() const { @@ -215,3 +220,16 @@ bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return _zonesArePickable; } + +bool ZoneEntityItem::containsPoint(glm::vec3& position) { + // use _shapeType shortly + // for now bounding box just so I can get end-to-end working + bool success; + AABox box = getAABox(success); + if (success) { + return box.contains(position); + } + // just return false if no AABox + return success; +} + diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 3084d71f46..07520001f7 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -74,6 +74,9 @@ public: void setFlyingAllowed(bool value) { _flyingAllowed = value; } bool getGhostingAllowed() const { return _ghostingAllowed; } void setGhostingAllowed(bool value) { _ghostingAllowed = value; } + QString getFilterURL() const { return _filterURL; } + void setFilterURL(const QString url) { _filterURL = url; } + bool containsPoint(glm::vec3& position); virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -87,6 +90,7 @@ public: static const QString DEFAULT_COMPOUND_SHAPE_URL; static const bool DEFAULT_FLYING_ALLOWED; static const bool DEFAULT_GHOSTING_ALLOWED; + static const QString DEFAULT_FILTER_URL; protected: KeyLightPropertyGroup _keyLightProperties; @@ -101,6 +105,7 @@ protected: bool _flyingAllowed { DEFAULT_FLYING_ALLOWED }; bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; + QString _filterURL { DEFAULT_FILTER_URL }; static bool _drawZoneBoundaries; static bool _zonesArePickable; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 1fca14c2bc..d28416eb18 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -449,6 +449,11 @@ +
+
+ + +
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 6b3bdaa0a4..98784d8e96 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -697,6 +697,7 @@ function loaded() { var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed"); var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); + var elZoneFilterURL = document.getElementById("property-zone-filter-url"); var elPolyVoxSections = document.querySelectorAll(".poly-vox-section"); allSections.push(elPolyVoxSections); @@ -1034,6 +1035,7 @@ function loaded() { elZoneFlyingAllowed.checked = properties.flyingAllowed; elZoneGhostingAllowed.checked = properties.ghostingAllowed; + elZoneFilterURL.value = properties.filterURL; showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); } else if (properties.type == "PolyVox") { @@ -1385,7 +1387,8 @@ function loaded() { elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); - + elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); From 4821180dd3a6dc6805eb6aefbb01a323cbdad027 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 8 Feb 2017 10:48:26 -0700 Subject: [PATCH 02/42] Just add a pointer to the filter class but do nothing with it --- libraries/entities/src/EntityTree.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 5dad282d3b..7d7948844e 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -24,6 +24,7 @@ typedef std::shared_ptr EntityTreePointer; #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" +#include "EntityEditFilters.h" class Model; using ModelPointer = std::shared_ptr; @@ -370,6 +371,7 @@ protected: std::function _entityEditFilterHadUncaughtExceptions; QStringList _entityScriptSourceWhitelist; + EntityEditFilters* _entityEditFilters; }; #endif // hifi_EntityTree_h From c08adc9faa2c67a4a40ab9a261e2efbfa69b9c29 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 8 Feb 2017 10:51:28 -0700 Subject: [PATCH 03/42] and the new filter class --- libraries/entities/src/EntityEditFilters.cpp | 138 +++++++++++++++++++ libraries/entities/src/EntityEditFilters.h | 46 +++++++ 2 files changed, 184 insertions(+) create mode 100644 libraries/entities/src/EntityEditFilters.cpp create mode 100644 libraries/entities/src/EntityEditFilters.h diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp new file mode 100644 index 0000000000..a74ba091b5 --- /dev/null +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -0,0 +1,138 @@ +// +// EntityEditFilters.cpp +// libraries/entities/src +// +// Created by David Kelly on 2/7/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include + +#include + +#include "EntityEditFilters.h" + +QScriptValue EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd) { + qDebug() << "in EntityEditFilters"; + return QScriptValue(); +} + +void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) { + QScriptEngine* engine = _filterScriptEngineMap.value(entityID); + if (engine) { + _filterScriptEngineMap.remove(entityID); + delete engine; + } + FilterFunctionPair* pair = _filterFunctionMap.value(entityID); + if (pair) { + _filterFunctionMap.remove(entityID); + delete pair; + } +} + +void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterURL) { + QUrl scriptURL(filterURL); + + // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) + if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { + qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; + scriptRequestFinished(entityID); + return; + } + // first remove any existing info for this entity + removeEntityFilter(entityID); + + auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL); + if (!scriptRequest) { + qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString(); + scriptRequestFinished(entityID); + return; + } + // Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own. + connect(scriptRequest, &ResourceRequest::finished, this, [this, &entityID]{ EntityEditFilters::scriptRequestFinished(entityID);} ); + // FIXME: handle atp rquests setup here. See Agent::requestScript() + qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString()); + scriptRequest->send(); + qDebug() << "script request sent"; + +} + +// Copied from ScriptEngine.cpp. We should make this a class method for reuse. +// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point. +static bool hasCorrectSyntax(const QScriptProgram& program) { + const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { + const auto error = syntaxCheck.errorMessage(); + const auto line = QString::number(syntaxCheck.errorLineNumber()); + const auto column = QString::number(syntaxCheck.errorColumnNumber()); + const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); + qCritical() << qPrintable(message); + return false; + } + return true; +} +static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) { + if (engine.hasUncaughtException()) { + const auto backtrace = engine.uncaughtExceptionBacktrace(); + const auto exception = engine.uncaughtException().toString(); + const auto line = QString::number(engine.uncaughtExceptionLineNumber()); + engine.clearExceptions(); + + static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; + auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line); + if (!backtrace.empty()) { + static const auto lineSeparator = "\n "; + message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); + } + qCritical() << qPrintable(message); + return true; + } + return false; +} + +void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) { + qDebug() << "script request completed"; + auto scriptRequest = qobject_cast(sender()); + const QString urlString = scriptRequest->getUrl().toString(); + if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) { + auto scriptContents = scriptRequest->getData(); + qInfo() << "Downloaded script:" << scriptContents; + QScriptProgram program(scriptContents, urlString); + if (hasCorrectSyntax(program)) { + // create a QScriptEngine for this script + QScriptEngine* engine = new QScriptEngine(); + engine->evaluate(scriptContents); + if (!hadUncaughtExceptions(*engine, urlString)) { + // put the engine in the engine map (so we don't leak them, etc...) + _filterScriptEngineMap.insert(entityID, engine); + + // define the uncaughtException function + FilterFunctionPair* pair = new FilterFunctionPair(); + QScriptEngine& engineRef = *engine; + pair->second = [this, &engineRef, &urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; + + // now get the filter function + auto global = engine->globalObject(); + pair->first = global.property("filter"); + if (!pair->first.isFunction()) { + qDebug() << "Filter function specified but not found. Ignoring filter"; + return; + } + _filterFunctionMap.insert(entityID, pair); + qDebug() << "script request filter processed"; + return; + } + } + } else if (scriptRequest) { + qCritical() << "Failed to download script at" << urlString; + // See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure. + qCritical() << "ResourceRequest error was" << scriptRequest->getResult(); + } else { + qCritical() << "Failed to create script request."; + } + +} diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h new file mode 100644 index 0000000000..678b3ad0d1 --- /dev/null +++ b/libraries/entities/src/EntityEditFilters.h @@ -0,0 +1,46 @@ +// +// EntityEditFilters.h +// libraries/entities/src +// +// Created by David Kelly on 2/7/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_EntityEditFilters_h +#define hifi_EntityEditFilters_h + +#include +#include +#include +#include +#include + +#include + +#include "EntityItemID.h" +#include "EntityItemProperties.h" + + +typedef QPair> FilterFunctionPair; + +class EntityEditFilters : public QObject { + Q_OBJECT +public: + EntityEditFilters() {}; + + void addEntityFilter(EntityItemID& entityID, QString filterURL); + void removeEntityFilter(EntityItemID& entityID); + + QScriptValue filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd); + +private slots: + void scriptRequestFinished(EntityItemID& entityID); + +private: + QMap _filterFunctionMap; + QMap _filterScriptEngineMap; +}; + +#endif //hifi_EntityEditFilters_h From ff7c9d354694e6b74dd9d10d82e7b4b3c85368c1 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 8 Feb 2017 15:36:16 -0700 Subject: [PATCH 04/42] Working like before Single entity script running properly. Now, need to add the zone filters and execute them. --- .../src/entities/EntityServer.cpp | 105 ++++-------------- assignment-client/src/entities/EntityServer.h | 7 +- libraries/entities/src/EntityEditFilters.cpp | 69 ++++++++++-- libraries/entities/src/EntityEditFilters.h | 16 ++- libraries/entities/src/EntityTree.cpp | 44 +++----- libraries/entities/src/EntityTree.h | 9 +- 6 files changed, 112 insertions(+), 138 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 02dc552dae..9af9618b92 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "EntityServer.h" #include "EntityServerConsts.h" @@ -292,97 +293,33 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio } else { tree->setEntityScriptSourceWhitelist(""); } + + _entityEditFilters = new EntityEditFilters(); - if (readOptionString("entityEditFilter", settingsSectionObject, _entityEditFilter) && !_entityEditFilter.isEmpty()) { - // Tell the tree that we have a filter, so that it doesn't accept edits until we have a filter function set up. - std::static_pointer_cast(_tree)->setHasEntityFilter(true); - // Now fetch script from file asynchronously. - QUrl scriptURL(_entityEditFilter); - - // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) - if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { - qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; - scriptRequestFinished(); - return; - } - auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL); - if (!scriptRequest) { - qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString(); - scriptRequestFinished(); - return; - } - // Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own. - connect(scriptRequest, &ResourceRequest::finished, this, &EntityServer::scriptRequestFinished); - // FIXME: handle atp rquests setup here. See Agent::requestScript() - qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString()); - scriptRequest->send(); - qDebug() << "script request sent"; + std::static_pointer_cast(tree)->setEntityEditFilters(_entityEditFilters); + QString filterURL; + if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) { + // connect the filterAdded signal, and block edits until you hear back + connect(_entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); + + _entityEditFilters->rejectAll(true); + _entityEditFilters->addFilter(EntityItemID(), filterURL); } } -// Copied from ScriptEngine.cpp. We should make this a class method for reuse. -// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point. -static bool hasCorrectSyntax(const QScriptProgram& program) { - const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); - if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { - const auto error = syntaxCheck.errorMessage(); - const auto line = QString::number(syntaxCheck.errorLineNumber()); - const auto column = QString::number(syntaxCheck.errorColumnNumber()); - const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); - qCritical() << qPrintable(message); - return false; +void EntityServer::entityFilterAdded(EntityItemID id, bool success) { + if(id.isInvalidID()) { + // this is the domain-wide entity filter, which we want to stop + // the world for + if (success) { + _entityEditFilters->rejectAll(false); + } else { + qDebug() << "entity edit filter unsuccessfully added, stopping..."; + stop(); + } } - return true; } -static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) { - if (engine.hasUncaughtException()) { - const auto backtrace = engine.uncaughtExceptionBacktrace(); - const auto exception = engine.uncaughtException().toString(); - const auto line = QString::number(engine.uncaughtExceptionLineNumber()); - engine.clearExceptions(); - static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; - auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line); - if (!backtrace.empty()) { - static const auto lineSeparator = "\n "; - message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); - } - qCritical() << qPrintable(message); - return true; - } - return false; -} -void EntityServer::scriptRequestFinished() { - qDebug() << "script request completed"; - auto scriptRequest = qobject_cast(sender()); - const QString urlString = scriptRequest->getUrl().toString(); - if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) { - auto scriptContents = scriptRequest->getData(); - qInfo() << "Downloaded script:" << scriptContents; - QScriptProgram program(scriptContents, urlString); - if (hasCorrectSyntax(program)) { - _entityEditFilterEngine.evaluate(scriptContents); - if (!hadUncaughtExceptions(_entityEditFilterEngine, urlString)) { - std::static_pointer_cast(_tree)->initEntityEditFilterEngine(&_entityEditFilterEngine, [this]() { - return hadUncaughtExceptions(_entityEditFilterEngine, _entityEditFilter); - }); - scriptRequest->deleteLater(); - qDebug() << "script request filter processed"; - return; - } - } - } else if (scriptRequest) { - qCritical() << "Failed to download script at" << urlString; - // See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure. - qCritical() << "ResourceRequest error was" << scriptRequest->getResult(); - } else { - qCritical() << "Failed to create script request."; - } - // Hard stop of the assignment client on failure. We don't want anyone to think they have a filter in place when they don't. - // Alas, only indications will be the above logging with assignment client restarting repeatedly, and clients will not see any entities. - qDebug() << "script request failure causing stop"; - stop(); -} void EntityServer::nodeAdded(SharedNodePointer node) { EntityTreePointer tree = std::static_pointer_cast(_tree); diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index f142145d5f..0edbc45ce1 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -63,13 +63,13 @@ public slots: virtual void nodeAdded(SharedNodePointer node) override; virtual void nodeKilled(SharedNodePointer node) override; void pruneDeletedEntities(); + void entityFilterAdded(EntityItemID id, bool success); protected: virtual OctreePointer createTree() override; private slots: void handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode); - void scriptRequestFinished(); private: SimpleEntitySimulationPointer _entitySimulation; @@ -77,9 +77,8 @@ private: QReadWriteLock _viewerSendingStatsLock; QMap> _viewerSendingStats; - - QString _entityEditFilter{}; - QScriptEngine _entityEditFilterEngine{}; + + EntityEditFilters* _entityEditFilters{}; }; #endif // hifi_EntityServer_h diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index a74ba091b5..2afcd93b63 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -16,12 +16,55 @@ #include "EntityEditFilters.h" -QScriptValue EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd) { - qDebug() << "in EntityEditFilters"; - return QScriptValue(); +bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType) { + qCDebug(entities) << "in EntityEditFilters"; + + // allows us to start entity server and reject all edits until filter is setup + if (_rejectAll) { + return false; + } + + bool accepted = true; + + qCDebug(entities) << "_filterFunctionMap.size:" << _filterFunctionMap.size(); + //first off lets call the filter (if any) that is domain-wide (EntityItemID()) + FilterFunctionPair* pair = _filterFunctionMap.value(EntityItemID()); + QScriptEngine* engine = _filterScriptEngineMap.value(EntityItemID()); + qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine; + if (pair != nullptr && engine != nullptr) { + + qCDebug(entities) << "attempting to call filter"; + auto oldProperties = propertiesIn.getDesiredProperties(); + auto specifiedProperties = propertiesIn.getChangedProperties(); + propertiesIn.setDesiredProperties(specifiedProperties); + QScriptValue inputValues = propertiesIn.copyToScriptValue(engine, false, true, true); + propertiesIn.setDesiredProperties(oldProperties); + + auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. + QScriptValueList args; + args << inputValues; + args << filterType; + + QScriptValue result = pair->first.call(_nullObjectForFilter, args); + if (pair->second()) { + result = QScriptValue(); + } + + accepted = result.isObject(); + + if (accepted) { + qCDebug(entities) << "filter result accepted"; + // call rest of them soon + propertiesOut.copyFromScriptValue(result, false); + // Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON. + auto out = QJsonValue::fromVariant(result.toVariant()); + wasChanged = in != out; + } + } + return accepted; } -void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) { +void EntityEditFilters::removeFilter(EntityItemID& entityID) { QScriptEngine* engine = _filterScriptEngineMap.value(entityID); if (engine) { _filterScriptEngineMap.remove(entityID); @@ -34,7 +77,7 @@ void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) { } } -void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterURL) { +void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) { QUrl scriptURL(filterURL); // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) @@ -44,7 +87,7 @@ void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterUR return; } // first remove any existing info for this entity - removeEntityFilter(entityID); + removeFilter(entityID); auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL); if (!scriptRequest) { @@ -53,11 +96,11 @@ void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterUR return; } // Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own. - connect(scriptRequest, &ResourceRequest::finished, this, [this, &entityID]{ EntityEditFilters::scriptRequestFinished(entityID);} ); + connect(scriptRequest, &ResourceRequest::finished, this, [this, entityID]{ EntityEditFilters::scriptRequestFinished(entityID);} ); // FIXME: handle atp rquests setup here. See Agent::requestScript() qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString()); scriptRequest->send(); - qDebug() << "script request sent"; + qDebug() << "script request sent for entity " << entityID; } @@ -94,8 +137,8 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName return false; } -void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) { - qDebug() << "script request completed"; +void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { + qDebug() << "script request completed for entity " << entityID; auto scriptRequest = qobject_cast(sender()); const QString urlString = scriptRequest->getUrl().toString(); if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) { @@ -123,7 +166,9 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) { return; } _filterFunctionMap.insert(entityID, pair); - qDebug() << "script request filter processed"; + qDebug() << "script request filter processed for entity id " << entityID; + + emit filterAdded(entityID, true); return; } } @@ -134,5 +179,5 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) { } else { qCritical() << "Failed to create script request."; } - + emit filterAdded(entityID, false); } diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 678b3ad0d1..ad6722819b 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -21,7 +21,7 @@ #include "EntityItemID.h" #include "EntityItemProperties.h" - +#include "EntityTree.h" typedef QPair> FilterFunctionPair; @@ -30,15 +30,21 @@ class EntityEditFilters : public QObject { public: EntityEditFilters() {}; - void addEntityFilter(EntityItemID& entityID, QString filterURL); - void removeEntityFilter(EntityItemID& entityID); + void addFilter(EntityItemID& entityID, QString filterURL); + void removeFilter(EntityItemID& entityID); - QScriptValue filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd); + bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType); + void rejectAll(bool state) {_rejectAll = state; } + +signals: + void filterAdded(EntityItemID id, bool success); private slots: - void scriptRequestFinished(EntityItemID& entityID); + void scriptRequestFinished(EntityItemID entityID); private: + bool _rejectAll {false}; + QScriptValue _nullObjectForFilter{}; QMap _filterFunctionMap; QMap _filterScriptEngineMap; }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4e92b2a572..5e2fc0f1f9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -24,6 +24,7 @@ #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" #include "LogHandler.h" +#include "EntityEditFilters.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour @@ -61,6 +62,9 @@ EntityTree::EntityTree(bool shouldReaverage) : EntityTree::~EntityTree() { eraseAllOctreeElements(false); + if (_entityEditFilters) { + delete _entityEditFilters; + } } void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { @@ -940,8 +944,8 @@ void EntityTree::initEntityEditFilterEngine(QScriptEngine* engine, std::function _hasEntityEditFilter = true; } -bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) { - if (!_entityEditFilterEngine) { +bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) { + if (!_entityEditFilterEngine && !_entityEditFilters) { propertiesOut = propertiesIn; wasChanged = false; // not changed if (_hasEntityEditFilter) { @@ -950,28 +954,9 @@ bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItem } return true; // allowed } - auto oldProperties = propertiesIn.getDesiredProperties(); - auto specifiedProperties = propertiesIn.getChangedProperties(); - propertiesIn.setDesiredProperties(specifiedProperties); - QScriptValue inputValues = propertiesIn.copyToScriptValue(_entityEditFilterEngine, false, true, true); - propertiesIn.setDesiredProperties(oldProperties); - - auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. - QScriptValueList args; - args << inputValues; - args << filterType; - - QScriptValue result = _entityEditFilterFunction.call(_nullObjectForFilter, args); - if (_entityEditFilterHadUncaughtExceptions()) { - result = QScriptValue(); - } - - bool accepted = result.isObject(); // filters should return null or false to completely reject edit or add - if (accepted) { - propertiesOut.copyFromScriptValue(result, false); - // Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON. - auto out = QJsonValue::fromVariant(result.toVariant()); - wasChanged = in != out; + bool accepted = true; + if (_entityEditFilters) { + accepted = _entityEditFilters->filter(existingEntity->getPosition(), propertiesIn, propertiesOut, wasChanged, filterType); } return accepted; @@ -1076,11 +1061,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c // an existing entity... handle appropriately if (validEditPacket) { + // search for the entity by EntityItemID + startLookup = usecTimestampNow(); + EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID); + endLookup = usecTimestampNow(); + startFilter = usecTimestampNow(); bool wasChanged = false; // Having (un)lock rights bypasses the filter, unless it's a physics result. FilterType filterType = isPhysics ? FilterType::Physics : (isAdd ? FilterType::Add : FilterType::Edit); - bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(properties, properties, wasChanged, filterType); + bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(existingEntity, properties, properties, wasChanged, filterType); if (!allowed) { auto timestamp = properties.getLastEdited(); properties = EntityItemProperties(); @@ -1093,10 +1083,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } endFilter = usecTimestampNow(); - // search for the entity by EntityItemID - startLookup = usecTimestampNow(); - EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID); - endLookup = usecTimestampNow(); if (existingEntity && !isAdd) { if (suppressDisallowedScript) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 7d7948844e..34a52ce1b2 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -24,8 +24,8 @@ typedef std::shared_ptr EntityTreePointer; #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" -#include "EntityEditFilters.h" +class EntityEditFilters; class Model; using ModelPointer = std::shared_ptr; using ModelWeakPointer = std::weak_ptr; @@ -274,7 +274,8 @@ public: void initEntityEditFilterEngine(QScriptEngine* engine, std::function entityEditFilterHadUncaughtExceptions); void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; } - + + void setEntityEditFilters(EntityEditFilters* entityEditFilters) { _entityEditFilters = entityEditFilters; } static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; public slots: @@ -363,7 +364,7 @@ protected: float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME }; - bool filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType); + bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType); bool _hasEntityEditFilter{ false }; QScriptEngine* _entityEditFilterEngine{}; QScriptValue _entityEditFilterFunction{}; @@ -371,7 +372,7 @@ protected: std::function _entityEditFilterHadUncaughtExceptions; QStringList _entityScriptSourceWhitelist; - EntityEditFilters* _entityEditFilters; + EntityEditFilters* _entityEditFilters{}; }; #endif // hifi_EntityTree_h From 8d666854c79eb15b71e284e52b5abd62b3232d71 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 9 Feb 2017 15:39:38 -0700 Subject: [PATCH 05/42] working it seems But, AABox for zones isn't very helpful (box is _small). Time to use the shape of the zone. --- .../src/entities/EntityServer.cpp | 15 ++- assignment-client/src/entities/EntityServer.h | 2 - libraries/entities/src/EntityEditFilters.cpp | 122 +++++++++++++----- libraries/entities/src/EntityEditFilters.h | 6 + libraries/entities/src/EntityTree.cpp | 8 +- libraries/entities/src/EntityTree.h | 4 +- libraries/entities/src/ZoneEntityItem.cpp | 15 +++ libraries/entities/src/ZoneEntityItem.h | 2 +- 8 files changed, 125 insertions(+), 49 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 9af9618b92..c3045f599c 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -294,16 +294,15 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio tree->setEntityScriptSourceWhitelist(""); } - _entityEditFilters = new EntityEditFilters(); - - std::static_pointer_cast(tree)->setEntityEditFilters(_entityEditFilters); + auto entityEditFilters = tree->createEntityEditFilters(); + QString filterURL; if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) { // connect the filterAdded signal, and block edits until you hear back - connect(_entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); + connect(entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); - _entityEditFilters->rejectAll(true); - _entityEditFilters->addFilter(EntityItemID(), filterURL); + entityEditFilters->rejectAll(true); + entityEditFilters->addFilter(EntityItemID(), filterURL); } } @@ -311,8 +310,10 @@ void EntityServer::entityFilterAdded(EntityItemID id, bool success) { if(id.isInvalidID()) { // this is the domain-wide entity filter, which we want to stop // the world for + auto entityEditFilters = qobject_cast(sender()); if (success) { - _entityEditFilters->rejectAll(false); + qDebug() << "entity edit filter for " << id << "added successfully"; + entityEditFilters->rejectAll(false); } else { qDebug() << "entity edit filter unsuccessfully added, stopping..."; stop(); diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 0edbc45ce1..325435fe7e 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -77,8 +77,6 @@ private: QReadWriteLock _viewerSendingStatsLock; QMap> _viewerSendingStats; - - EntityEditFilters* _entityEditFilters{}; }; #endif // hifi_EntityServer_h diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 2afcd93b63..dccb49d4f5 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -13,58 +13,99 @@ #include #include - #include "EntityEditFilters.h" +QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { + QList zones; + _lock.lockForRead(); + qCDebug(entities) << "looking at " << _filterFunctionMap.size() << "possible zones"; + for (auto it = _filterFunctionMap.begin(); it != _filterFunctionMap.end(); it++) { + auto id = it.key(); + if (!id.isInvalidID()) { + // for now, look it up in the tree (soon we need to cache or similar?) + EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); + auto zone = std::dynamic_pointer_cast(itemPtr); + if (zone && zone->containsPoint(position)) { + zones.append(id); + } + } else { + zones.append(id); + } + } + _lock.unlock(); + return zones; +} + bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType) { qCDebug(entities) << "in EntityEditFilters"; // allows us to start entity server and reject all edits until filter is setup if (_rejectAll) { + qCDebug(entities) << "rejecting all edits"; return false; } - bool accepted = true; + // get the ids of all the zones (plus the global entity edit filter) that the position + // lies within + auto zoneIDs = getZonesByPosition(position); - qCDebug(entities) << "_filterFunctionMap.size:" << _filterFunctionMap.size(); - //first off lets call the filter (if any) that is domain-wide (EntityItemID()) - FilterFunctionPair* pair = _filterFunctionMap.value(EntityItemID()); - QScriptEngine* engine = _filterScriptEngineMap.value(EntityItemID()); - qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine; - if (pair != nullptr && engine != nullptr) { + // temp debugging -- remove! + qCDebug(entities) << "zones:"; + for (auto zoneID : zoneIDs) { + qCDebug(entities) << zoneID << ","; + } + + auto oldProperties = propertiesIn.getDesiredProperties(); + auto specifiedProperties = propertiesIn.getChangedProperties(); + propertiesIn.setDesiredProperties(specifiedProperties); + for (auto it = zoneIDs.begin(); it != zoneIDs.end(); it++) { + qCDebug(entities) << "applying filter for zone" << *it; - qCDebug(entities) << "attempting to call filter"; - auto oldProperties = propertiesIn.getDesiredProperties(); - auto specifiedProperties = propertiesIn.getChangedProperties(); - propertiesIn.setDesiredProperties(specifiedProperties); - QScriptValue inputValues = propertiesIn.copyToScriptValue(engine, false, true, true); - propertiesIn.setDesiredProperties(oldProperties); + // get the filter pair, etc... + _lock.lockForRead(); + FilterFunctionPair* pair = _filterFunctionMap.value(*it); + QScriptEngine* engine = _filterScriptEngineMap.value(*it); + _lock.unlock(); + + qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine; + if (pair != nullptr && engine != nullptr) { - auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. - QScriptValueList args; - args << inputValues; - args << filterType; + QScriptValue inputValues = propertiesIn.copyToScriptValue(engine, false, true, true); + propertiesIn.setDesiredProperties(oldProperties); - QScriptValue result = pair->first.call(_nullObjectForFilter, args); - if (pair->second()) { - result = QScriptValue(); - } - - accepted = result.isObject(); - - if (accepted) { - qCDebug(entities) << "filter result accepted"; - // call rest of them soon - propertiesOut.copyFromScriptValue(result, false); - // Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON. - auto out = QJsonValue::fromVariant(result.toVariant()); - wasChanged = in != out; + auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. + QScriptValueList args; + args << inputValues; + args << filterType; + + QScriptValue result = pair->first.call(_nullObjectForFilter, args); + if (pair->second()) { + result = QScriptValue(); + } + + + if (result.isObject()){ + qCDebug(entities) << "filter result accepted"; + // make propertiesIn reflect the changes, for next filter... + propertiesIn.copyFromScriptValue(result, false); + + // and update propertiesOut too. #TODO: this could be more efficient... + propertiesOut.copyFromScriptValue(result, false); + // Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON. + auto out = QJsonValue::fromVariant(result.toVariant()); + wasChanged |= (in != out); + } else { + // an edit was rejected, so we stop here and return false + qCDebug(entities) << "Edit rejected by " << *it; + } } } - return accepted; + // if we made it here, + return true; } void EntityEditFilters::removeFilter(EntityItemID& entityID) { + QWriteLocker writeLock(&_lock); QScriptEngine* engine = _filterScriptEngineMap.value(entityID); if (engine) { _filterScriptEngineMap.remove(entityID); @@ -78,6 +119,7 @@ void EntityEditFilters::removeFilter(EntityItemID& entityID) { } void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) { + QUrl scriptURL(filterURL); // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) @@ -151,8 +193,10 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { engine->evaluate(scriptContents); if (!hadUncaughtExceptions(*engine, urlString)) { // put the engine in the engine map (so we don't leak them, etc...) + _lock.lockForWrite(); _filterScriptEngineMap.insert(entityID, engine); - + _lock.unlock(); + // define the uncaughtException function FilterFunctionPair* pair = new FilterFunctionPair(); QScriptEngine& engineRef = *engine; @@ -160,12 +204,20 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { // now get the filter function auto global = engine->globalObject(); + auto entitiesObject = engine->newObject(); + entitiesObject.setProperty("ADD_FILTER_TYPE", EntityTree::FilterType::Add); + entitiesObject.setProperty("EDIT_FILTER_TYPE", EntityTree::FilterType::Edit); + entitiesObject.setProperty("PHYSICS_FILTER_TYPE", EntityTree::FilterType::Physics); + global.setProperty("Entities", entitiesObject); pair->first = global.property("filter"); if (!pair->first.isFunction()) { qDebug() << "Filter function specified but not found. Ignoring filter"; return; } - _filterFunctionMap.insert(entityID, pair); + _lock.lockForWrite(); + _filterFunctionMap.insert(entityID, pair); + _lock.unlock(); + qDebug() << "script request filter processed for entity id " << entityID; emit filterAdded(entityID, true); diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index ad6722819b..b4d31a54e8 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -29,6 +29,7 @@ class EntityEditFilters : public QObject { Q_OBJECT public: EntityEditFilters() {}; + EntityEditFilters(EntityTreePointer tree ): _tree(tree) {}; void addFilter(EntityItemID& entityID, QString filterURL); void removeFilter(EntityItemID& entityID); @@ -43,8 +44,13 @@ private slots: void scriptRequestFinished(EntityItemID entityID); private: + QList getZonesByPosition(glm::vec3& position); + + EntityTreePointer _tree {}; bool _rejectAll {false}; QScriptValue _nullObjectForFilter{}; + + QReadWriteLock _lock; QMap _filterFunctionMap; QMap _filterScriptEngineMap; }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5e2fc0f1f9..6a2a52f958 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -62,9 +62,6 @@ EntityTree::EntityTree(bool shouldReaverage) : EntityTree::~EntityTree() { eraseAllOctreeElements(false); - if (_entityEditFilters) { - delete _entityEditFilters; - } } void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { @@ -1751,3 +1748,8 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { } return entity->getJointNames(); } + +EntityEditFilters* EntityTree::createEntityEditFilters() { + _entityEditFilters = new EntityEditFilters(getThisPointer()); + return _entityEditFilters; +} diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 34a52ce1b2..03e0f30b0b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -275,7 +275,9 @@ public: void initEntityEditFilterEngine(QScriptEngine* engine, std::function entityEditFilterHadUncaughtExceptions); void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; } - void setEntityEditFilters(EntityEditFilters* entityEditFilters) { _entityEditFilters = entityEditFilters; } + EntityEditFilters* createEntityEditFilters(); + EntityEditFilters* getEntityEditFilters() { return _entityEditFilters; } + static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; public slots: diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 7de3f6b68f..61b3800b50 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -19,6 +19,9 @@ #include "EntityTree.h" #include "EntityTreeElement.h" #include "ZoneEntityItem.h" +#include "EntityEditFilters.h" + +#include bool ZoneEntityItem::_zonesArePickable = false; bool ZoneEntityItem::_drawZoneBoundaries = false; @@ -221,12 +224,24 @@ bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return _zonesArePickable; } +void ZoneEntityItem::setFilterURL(QString url) { + _filterURL = url; + if (getTree()) { + auto entityEditFilters = getTree()->getEntityEditFilters(); + if (entityEditFilters) { + qCDebug(entities) << "adding filter " << url << "for zone" << getEntityItemID(); + entityEditFilters->addFilter(getEntityItemID(), url); + } + } +} + bool ZoneEntityItem::containsPoint(glm::vec3& position) { // use _shapeType shortly // for now bounding box just so I can get end-to-end working bool success; AABox box = getAABox(success); if (success) { + qCDebug(entities) << "box: " << box; return box.contains(position); } // just return false if no AABox diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 07520001f7..f804ac8dbb 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -75,7 +75,7 @@ public: bool getGhostingAllowed() const { return _ghostingAllowed; } void setGhostingAllowed(bool value) { _ghostingAllowed = value; } QString getFilterURL() const { return _filterURL; } - void setFilterURL(const QString url) { _filterURL = url; } + void setFilterURL(const QString url); bool containsPoint(glm::vec3& position); virtual bool supportsDetailedRayIntersection() const override { return true; } From 415d71956f975b0ccd4eef41283814f46c095460 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 9 Feb 2017 19:59:55 -0700 Subject: [PATCH 06/42] minor logging crap --- libraries/entities/src/EntityEditFilters.cpp | 2 +- libraries/entities/src/ZoneEntityItem.cpp | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index dccb49d4f5..8c4041fd35 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -18,7 +18,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { QList zones; _lock.lockForRead(); - qCDebug(entities) << "looking at " << _filterFunctionMap.size() << "possible zones"; + qCDebug(entities) << "looking at " << _filterFunctionMap.size() << "possible zones, at " << position; for (auto it = _filterFunctionMap.begin(); it != _filterFunctionMap.end(); it++) { auto id = it.key(); if (!id.isInvalidID()) { diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 61b3800b50..58b57f357d 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -239,12 +239,7 @@ bool ZoneEntityItem::containsPoint(glm::vec3& position) { // use _shapeType shortly // for now bounding box just so I can get end-to-end working bool success; - AABox box = getAABox(success); - if (success) { - qCDebug(entities) << "box: " << box; - return box.contains(position); - } - // just return false if no AABox - return success; + bool result = getAABox(success).contains(position); + return result && success; } From cf780b3b73146a72346254331d1c7809cfbdd7ff Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 10 Feb 2017 11:42:56 -0700 Subject: [PATCH 07/42] fixed persist issue, working much better --- .../src/entities/EntityServer.cpp | 5 +-- libraries/entities/src/EntityEditFilters.cpp | 14 +++++--- libraries/entities/src/EntityEditFilters.h | 2 +- libraries/entities/src/EntityItemID.cpp | 1 + libraries/entities/src/EntityTree.cpp | 34 ++----------------- libraries/entities/src/EntityTree.h | 12 ------- libraries/entities/src/ZoneEntityItem.cpp | 10 +++--- 7 files changed, 22 insertions(+), 56 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index c3045f599c..845136b0a2 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -72,6 +72,7 @@ OctreePointer EntityServer::createTree() { DependencyManager::registerInheritance(); DependencyManager::set(tree); + DependencyManager::set(std::static_pointer_cast(tree)); return tree; } @@ -294,12 +295,12 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio tree->setEntityScriptSourceWhitelist(""); } - auto entityEditFilters = tree->createEntityEditFilters(); + auto entityEditFilters = DependencyManager::get(); QString filterURL; if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) { // connect the filterAdded signal, and block edits until you hear back - connect(entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); + connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); entityEditFilters->rejectAll(true); entityEditFilters->addFilter(EntityItemID(), filterURL); diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 8c4041fd35..385fb91c81 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -55,9 +55,6 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper qCDebug(entities) << zoneID << ","; } - auto oldProperties = propertiesIn.getDesiredProperties(); - auto specifiedProperties = propertiesIn.getChangedProperties(); - propertiesIn.setDesiredProperties(specifiedProperties); for (auto it = zoneIDs.begin(); it != zoneIDs.end(); it++) { qCDebug(entities) << "applying filter for zone" << *it; @@ -70,6 +67,9 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine; if (pair != nullptr && engine != nullptr) { + auto oldProperties = propertiesIn.getDesiredProperties(); + auto specifiedProperties = propertiesIn.getChangedProperties(); + propertiesIn.setDesiredProperties(specifiedProperties); QScriptValue inputValues = propertiesIn.copyToScriptValue(engine, false, true, true); propertiesIn.setDesiredProperties(oldProperties); @@ -122,12 +122,19 @@ void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) { QUrl scriptURL(filterURL); + // setting it to an empty string is same as removing + if (filterURL.size() == 0) { + removeFilter(entityID); + return; + } + // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; scriptRequestFinished(entityID); return; } + // first remove any existing info for this entity removeFilter(entityID); @@ -143,7 +150,6 @@ void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) { qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString()); scriptRequest->send(); qDebug() << "script request sent for entity " << entityID; - } // Copied from ScriptEngine.cpp. We should make this a class method for reuse. diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index b4d31a54e8..36962838d8 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -25,7 +25,7 @@ typedef QPair> FilterFunctionPair; -class EntityEditFilters : public QObject { +class EntityEditFilters : public QObject, public Dependency { Q_OBJECT public: EntityEditFilters() {}; diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index 1462a4ef88..5f07019db4 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -19,6 +19,7 @@ #include "RegisteredMetaTypes.h" #include "EntityItemID.h" +int entityItemIDTypeID = qRegisterMetaType(); EntityItemID::EntityItemID() : QUuid() { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6a2a52f958..4958932fe3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -924,36 +924,12 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList entityEditFilterHadUncaughtExceptions) { - _entityEditFilterEngine = engine; - _entityEditFilterHadUncaughtExceptions = entityEditFilterHadUncaughtExceptions; - auto global = _entityEditFilterEngine->globalObject(); - _entityEditFilterFunction = global.property("filter"); - if (!_entityEditFilterFunction.isFunction()) { - qCDebug(entities) << "Filter function specified but not found. Will reject all edits."; - _entityEditFilterEngine = nullptr; // So that we don't try to call it. See filterProperties. - } - auto entitiesObject = _entityEditFilterEngine->newObject(); - entitiesObject.setProperty("ADD_FILTER_TYPE", FilterType::Add); - entitiesObject.setProperty("EDIT_FILTER_TYPE", FilterType::Edit); - entitiesObject.setProperty("PHYSICS_FILTER_TYPE", FilterType::Physics); - global.setProperty("Entities", entitiesObject); - _hasEntityEditFilter = true; -} bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) { - if (!_entityEditFilterEngine && !_entityEditFilters) { - propertiesOut = propertiesIn; - wasChanged = false; // not changed - if (_hasEntityEditFilter) { - qCDebug(entities) << "Rejecting properties because filter has not been set."; - return false; - } - return true; // allowed - } bool accepted = true; - if (_entityEditFilters) { - accepted = _entityEditFilters->filter(existingEntity->getPosition(), propertiesIn, propertiesOut, wasChanged, filterType); + auto entityEditFilters = DependencyManager::get(); + if (entityEditFilters) { + accepted = entityEditFilters->filter(existingEntity->getPosition(), propertiesIn, propertiesOut, wasChanged, filterType); } return accepted; @@ -1749,7 +1725,3 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { return entity->getJointNames(); } -EntityEditFilters* EntityTree::createEntityEditFilters() { - _entityEditFilters = new EntityEditFilters(getThisPointer()); - return _entityEditFilters; -} diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 03e0f30b0b..63f7bbfd66 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -272,12 +272,6 @@ public: void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID); - void initEntityEditFilterEngine(QScriptEngine* engine, std::function entityEditFilterHadUncaughtExceptions); - void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; } - - EntityEditFilters* createEntityEditFilters(); - EntityEditFilters* getEntityEditFilters() { return _entityEditFilters; } - static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; public slots: @@ -368,13 +362,7 @@ protected: bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType); bool _hasEntityEditFilter{ false }; - QScriptEngine* _entityEditFilterEngine{}; - QScriptValue _entityEditFilterFunction{}; - QScriptValue _nullObjectForFilter{}; - std::function _entityEditFilterHadUncaughtExceptions; - QStringList _entityScriptSourceWhitelist; - EntityEditFilters* _entityEditFilters{}; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 58b57f357d..9273298d3e 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -226,12 +226,10 @@ bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const void ZoneEntityItem::setFilterURL(QString url) { _filterURL = url; - if (getTree()) { - auto entityEditFilters = getTree()->getEntityEditFilters(); - if (entityEditFilters) { - qCDebug(entities) << "adding filter " << url << "for zone" << getEntityItemID(); - entityEditFilters->addFilter(getEntityItemID(), url); - } + if (DependencyManager::isSet()) { + auto entityEditFilters = DependencyManager::get(); + qCDebug(entities) << "adding filter " << url << "for zone" << getEntityItemID(); + entityEditFilters->addFilter(getEntityItemID(), url); } } From 7ec27cab13f5b51fae640210c543e7a4db16a55e Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 10 Feb 2017 12:47:48 -0700 Subject: [PATCH 08/42] Use EntityItem.contains Cuz it works - seemed like it was broken but nope. Also, trim off the filters for zones that no longer are in the tree. --- libraries/entities/src/EntityEditFilters.cpp | 17 ++++++++++++++--- libraries/entities/src/ZoneEntityItem.cpp | 8 -------- libraries/entities/src/ZoneEntityItem.h | 1 - 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 385fb91c81..e914248118 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -17,6 +17,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { QList zones; + QList missingZones; _lock.lockForRead(); qCDebug(entities) << "looking at " << _filterFunctionMap.size() << "possible zones, at " << position; for (auto it = _filterFunctionMap.begin(); it != _filterFunctionMap.end(); it++) { @@ -25,14 +26,23 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { // for now, look it up in the tree (soon we need to cache or similar?) EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); auto zone = std::dynamic_pointer_cast(itemPtr); - if (zone && zone->containsPoint(position)) { + if (!zone) { + missingZones.append(id); + } else if (zone->contains(position)) { zones.append(id); } } else { + // the null id is the global filter we put in the domain server's + // advanced entity server settings zones.append(id); } } _lock.unlock(); + // TODO: maybe do this later (not block filter) + // now remove filters for missing zones + for (auto id : missingZones) { + removeFilter(id); + } return zones; } @@ -89,14 +99,15 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // make propertiesIn reflect the changes, for next filter... propertiesIn.copyFromScriptValue(result, false); - // and update propertiesOut too. #TODO: this could be more efficient... + // and update propertiesOut too. TODO: this could be more efficient... propertiesOut.copyFromScriptValue(result, false); // Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON. auto out = QJsonValue::fromVariant(result.toVariant()); wasChanged |= (in != out); } else { // an edit was rejected, so we stop here and return false - qCDebug(entities) << "Edit rejected by " << *it; + qCDebug(entities) << "Edit rejected by filter " << *it ; + return false; } } } diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 9273298d3e..7158bc2588 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -233,11 +233,3 @@ void ZoneEntityItem::setFilterURL(QString url) { } } -bool ZoneEntityItem::containsPoint(glm::vec3& position) { - // use _shapeType shortly - // for now bounding box just so I can get end-to-end working - bool success; - bool result = getAABox(success).contains(position); - return result && success; -} - diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index f804ac8dbb..2bef95e452 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -76,7 +76,6 @@ public: void setGhostingAllowed(bool value) { _ghostingAllowed = value; } QString getFilterURL() const { return _filterURL; } void setFilterURL(const QString url); - bool containsPoint(glm::vec3& position); virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, From 1c1b0d1332ca1ae2e22757e5a6d220050bd4421b Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 10 Feb 2017 13:16:52 -0700 Subject: [PATCH 09/42] oops, debugging left in --- libraries/entities/src/ZoneEntityItem.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 7158bc2588..37b3be99a3 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -21,8 +21,6 @@ #include "ZoneEntityItem.h" #include "EntityEditFilters.h" -#include - bool ZoneEntityItem::_zonesArePickable = false; bool ZoneEntityItem::_drawZoneBoundaries = false; From 2996298e7903b3d98da9d9c93282e08240a48262 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 10 Feb 2017 14:57:09 -0700 Subject: [PATCH 10/42] First bit of cleanup consolidate to one map, some minor other cleaning. More coming. --- .../src/entities/EntityServer.cpp | 1 - libraries/entities/src/EntityEditFilters.cpp | 71 +++++++------------ libraries/entities/src/EntityEditFilters.h | 13 ++-- .../entities/src/EntityItemProperties.cpp | 2 +- 4 files changed, 37 insertions(+), 50 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 845136b0a2..b71242753d 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -322,7 +322,6 @@ void EntityServer::entityFilterAdded(EntityItemID id, bool success) { } } - void EntityServer::nodeAdded(SharedNodePointer node) { EntityTreePointer tree = std::static_pointer_cast(_tree); tree->knowAvatarID(node->getUUID()); diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index e914248118..4a84763a02 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -19,15 +19,17 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { QList zones; QList missingZones; _lock.lockForRead(); - qCDebug(entities) << "looking at " << _filterFunctionMap.size() << "possible zones, at " << position; - for (auto it = _filterFunctionMap.begin(); it != _filterFunctionMap.end(); it++) { - auto id = it.key(); + auto zoneIDs = _filterDataMap.keys(); + _lock.unlock(); + qCDebug(entities) << "looking at " << zoneIDs.size() << "possible zones, at " << position; + for (auto id : zoneIDs) { if (!id.isInvalidID()) { // for now, look it up in the tree (soon we need to cache or similar?) EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); auto zone = std::dynamic_pointer_cast(itemPtr); if (!zone) { - missingZones.append(id); + // TODO: maybe remove later? + removeFilter(id); } else if (zone->contains(position)) { zones.append(id); } @@ -37,12 +39,6 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { zones.append(id); } } - _lock.unlock(); - // TODO: maybe do this later (not block filter) - // now remove filters for missing zones - for (auto id : missingZones) { - removeFilter(id); - } return zones; } @@ -59,28 +55,20 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // lies within auto zoneIDs = getZonesByPosition(position); - // temp debugging -- remove! - qCDebug(entities) << "zones:"; - for (auto zoneID : zoneIDs) { - qCDebug(entities) << zoneID << ","; - } - - for (auto it = zoneIDs.begin(); it != zoneIDs.end(); it++) { - qCDebug(entities) << "applying filter for zone" << *it; + for (auto id : zoneIDs) { + qCDebug(entities) << "applying filter for zone" << id; // get the filter pair, etc... _lock.lockForRead(); - FilterFunctionPair* pair = _filterFunctionMap.value(*it); - QScriptEngine* engine = _filterScriptEngineMap.value(*it); + FilterData filterData = _filterDataMap.value(id); _lock.unlock(); - qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine; - if (pair != nullptr && engine != nullptr) { + if (filterData.valid()) { auto oldProperties = propertiesIn.getDesiredProperties(); auto specifiedProperties = propertiesIn.getChangedProperties(); propertiesIn.setDesiredProperties(specifiedProperties); - QScriptValue inputValues = propertiesIn.copyToScriptValue(engine, false, true, true); + QScriptValue inputValues = propertiesIn.copyToScriptValue(filterData.engine, false, true, true); propertiesIn.setDesiredProperties(oldProperties); auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. @@ -88,8 +76,8 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper args << inputValues; args << filterType; - QScriptValue result = pair->first.call(_nullObjectForFilter, args); - if (pair->second()) { + QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args); + if (filterData.uncaughtExceptions()) { result = QScriptValue(); } @@ -106,7 +94,7 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper wasChanged |= (in != out); } else { // an edit was rejected, so we stop here and return false - qCDebug(entities) << "Edit rejected by filter " << *it ; + qCDebug(entities) << "Edit rejected by filter " << id ; return false; } } @@ -117,16 +105,11 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper void EntityEditFilters::removeFilter(EntityItemID& entityID) { QWriteLocker writeLock(&_lock); - QScriptEngine* engine = _filterScriptEngineMap.value(entityID); - if (engine) { - _filterScriptEngineMap.remove(entityID); - delete engine; - } - FilterFunctionPair* pair = _filterFunctionMap.value(entityID); - if (pair) { - _filterFunctionMap.remove(entityID); - delete pair; + FilterData filterData = _filterDataMap.value(entityID); + if (filterData.valid()) { + delete filterData.engine; } + _filterDataMap.remove(entityID); } void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) { @@ -210,14 +193,12 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { engine->evaluate(scriptContents); if (!hadUncaughtExceptions(*engine, urlString)) { // put the engine in the engine map (so we don't leak them, etc...) - _lock.lockForWrite(); - _filterScriptEngineMap.insert(entityID, engine); - _lock.unlock(); - + FilterData filterData; + filterData.engine = engine; + // define the uncaughtException function - FilterFunctionPair* pair = new FilterFunctionPair(); QScriptEngine& engineRef = *engine; - pair->second = [this, &engineRef, &urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; + filterData.uncaughtExceptions = [this, &engineRef, &urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; // now get the filter function auto global = engine->globalObject(); @@ -226,13 +207,15 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { entitiesObject.setProperty("EDIT_FILTER_TYPE", EntityTree::FilterType::Edit); entitiesObject.setProperty("PHYSICS_FILTER_TYPE", EntityTree::FilterType::Physics); global.setProperty("Entities", entitiesObject); - pair->first = global.property("filter"); - if (!pair->first.isFunction()) { + filterData.filterFn = global.property("filter"); + if (!filterData.filterFn.isFunction()) { qDebug() << "Filter function specified but not found. Ignoring filter"; + delete engine; return; } + _lock.lockForWrite(); - _filterFunctionMap.insert(entityID, pair); + _filterDataMap.insert(entityID, filterData); _lock.unlock(); qDebug() << "script request filter processed for entity id " << entityID; diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 36962838d8..282d00187a 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -23,11 +23,17 @@ #include "EntityItemProperties.h" #include "EntityTree.h" -typedef QPair> FilterFunctionPair; - class EntityEditFilters : public QObject, public Dependency { Q_OBJECT public: + struct FilterData { + QScriptValue filterFn; + std::function uncaughtExceptions; + QScriptEngine* engine; + + bool valid() { return (engine != nullptr && filterFn.isFunction() && uncaughtExceptions); } + }; + EntityEditFilters() {}; EntityEditFilters(EntityTreePointer tree ): _tree(tree) {}; @@ -51,8 +57,7 @@ private: QScriptValue _nullObjectForFilter{}; QReadWriteLock _lock; - QMap _filterFunctionMap; - QMap _filterScriptEngineMap; + QMap _filterDataMap; }; #endif //hifi_EntityEditFilters_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index edfabf84e9..ea81df3801 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1805,7 +1805,7 @@ void EntityItemProperties::markAllChanged() { _parentIDChanged = true; _parentJointIndexChanged = true; - + _jointRotationsSetChanged = true; _jointRotationsChanged = true; _jointTranslationsSetChanged = true; From 9891d7b7d2528ce45f8bf3c98ad7c5b494c686bd Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 10 Feb 2017 15:24:58 -0700 Subject: [PATCH 11/42] build issue on linux/mac --- libraries/entities/src/EntityEditFilters.cpp | 4 ++-- libraries/entities/src/EntityEditFilters.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 4a84763a02..ddd9ea006a 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -103,7 +103,7 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper return true; } -void EntityEditFilters::removeFilter(EntityItemID& entityID) { +void EntityEditFilters::removeFilter(EntityItemID entityID) { QWriteLocker writeLock(&_lock); FilterData filterData = _filterDataMap.value(entityID); if (filterData.valid()) { @@ -112,7 +112,7 @@ void EntityEditFilters::removeFilter(EntityItemID& entityID) { _filterDataMap.remove(entityID); } -void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) { +void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { QUrl scriptURL(filterURL); diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 282d00187a..8ad82621dd 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -37,8 +37,8 @@ public: EntityEditFilters() {}; EntityEditFilters(EntityTreePointer tree ): _tree(tree) {}; - void addFilter(EntityItemID& entityID, QString filterURL); - void removeFilter(EntityItemID& entityID); + void addFilter(EntityItemID entityID, QString filterURL); + void removeFilter(EntityItemID entityID); bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType); void rejectAll(bool state) {_rejectAll = state; } From 8a7a3926c563392adc28f074e5993105d2a726a4 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 10 Feb 2017 15:57:27 -0700 Subject: [PATCH 12/42] rest of issues w/linux and mac --- libraries/entities/src/EntityTree.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4958932fe3..e1edc6bb20 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -929,7 +929,8 @@ bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemP bool accepted = true; auto entityEditFilters = DependencyManager::get(); if (entityEditFilters) { - accepted = entityEditFilters->filter(existingEntity->getPosition(), propertiesIn, propertiesOut, wasChanged, filterType); + auto position = existingEntity->getPosition(); + accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType); } return accepted; From 590cf6d79870b863c0ff3a41e560c48c0f237695 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 13 Feb 2017 12:20:47 -0700 Subject: [PATCH 13/42] whitespace --- assignment-client/src/entities/EntityServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index b71242753d..7fbbefa596 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -308,7 +308,7 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio } void EntityServer::entityFilterAdded(EntityItemID id, bool success) { - if(id.isInvalidID()) { + if (id.isInvalidID()) { // this is the domain-wide entity filter, which we want to stop // the world for auto entityEditFilters = qobject_cast(sender()); From a1371a4f0605a13a897afdc4d2de5e01fdaffa07 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 13 Feb 2017 13:20:31 -0800 Subject: [PATCH 14/42] Fix possible race --- libraries/networking/src/udt/SendQueue.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 31d61bde5d..c14ae0a39c 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -478,6 +478,9 @@ bool SendQueue::maybeResendPacket() { Packet::ObfuscationLevel level = (Packet::ObfuscationLevel)(entry.first < 2 ? 0 : (entry.first - 2) % 4); + auto wireSize = resendPacket.getWireSize(); + auto sequenceNumber = it->first; + if (level != Packet::NoObfuscation) { #ifdef UDT_CONNECTION_DEBUG QString debugString = "Obfuscating packet %1 with level %2"; @@ -512,7 +515,7 @@ bool SendQueue::maybeResendPacket() { sentLocker.unlock(); } - emit packetRetransmitted(resendPacket.getWireSize(), it->first, p_high_resolution_clock::now()); + emit packetRetransmitted(wireSize, sequenceNumber, p_high_resolution_clock::now()); // Signal that we did resend a packet return true; From 156e365eff17d3d28feedc0128387739ccc30858 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 13 Feb 2017 15:02:31 -0700 Subject: [PATCH 15/42] update version since new property was added, things will appear broken if this version is used with old version (server or client). --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index b13b21ba3b..545f394490 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return VERSION_ENTITIES_PHYSICS_PACKET; + return VERSION_ENTITIES_ZONE_FILTERS; case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::JsonFilter); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 0b2c04b031..38af6078a7 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -204,6 +204,7 @@ const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65; const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66; const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67; +const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68; enum class EntityQueryPacketVersion: PacketVersion { JsonFilter = 18 From 3b344ca01a3e12e17282b2bc1787f742349314d0 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 13 Feb 2017 18:25:00 -0700 Subject: [PATCH 16/42] inform gatekeeper immediately when kicking someone --- domain-server/CMakeLists.txt | 2 +- .../src/DomainServerSettingsManager.cpp | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 746e599d4e..2ce537a5a0 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -20,7 +20,7 @@ endif () symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources") # link the shared hifi libraries -link_hifi_libraries(embedded-webserver networking shared) +link_hifi_libraries(embedded-webserver networking shared avatars) # find OpenSSL find_package(OpenSSL REQUIRED) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 31d6845972..e1cdb96462 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -29,7 +29,7 @@ #include #include #include - +#include //for KillAvatarReason #include "DomainServerNodeData.h" const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; @@ -711,6 +711,13 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerwrite(nodeUUID.toRfc4122()); + packet->writePrimitive(KillAvatarReason::NoReason); + + // send to avatar mixer, it sends the kill to everyone else + limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer); if (newPermissions) { qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID()) @@ -718,9 +725,12 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer Date: Tue, 14 Feb 2017 13:05:12 -0700 Subject: [PATCH 17/42] Filter failure mode updated The decision here is that all failed filters (syntax errors, 404s, bad urls etc...) lock out all edits for those without lock rights. If it is the domain-wide one, then that applies to entire domain. If a zone filter, then that applies to all edits in that zone. Also - zone filters don't apply to the zone itself. Other zone filters whose zones lie within that zone _do_ apply, in addition to the global one. --- .../src/entities/EntityServer.cpp | 8 +---- libraries/entities/src/EntityEditFilters.cpp | 34 ++++++++++++------- libraries/entities/src/EntityEditFilters.h | 10 +++--- libraries/entities/src/EntityTree.cpp | 4 +-- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 7fbbefa596..425bea2c38 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -302,22 +302,16 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio // connect the filterAdded signal, and block edits until you hear back connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); - entityEditFilters->rejectAll(true); entityEditFilters->addFilter(EntityItemID(), filterURL); } } void EntityServer::entityFilterAdded(EntityItemID id, bool success) { if (id.isInvalidID()) { - // this is the domain-wide entity filter, which we want to stop - // the world for - auto entityEditFilters = qobject_cast(sender()); if (success) { qDebug() << "entity edit filter for " << id << "added successfully"; - entityEditFilters->rejectAll(false); } else { - qDebug() << "entity edit filter unsuccessfully added, stopping..."; - stop(); + qDebug() << "entity edit filter unsuccessfully added, all edits will be rejected for those without lock rights."; } } } diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index ddd9ea006a..cda7f1bc59 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -42,20 +42,18 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { return zones; } -bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType) { +bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, + EntityTree::FilterType filterType, EntityItemID& itemID) { qCDebug(entities) << "in EntityEditFilters"; - // allows us to start entity server and reject all edits until filter is setup - if (_rejectAll) { - qCDebug(entities) << "rejecting all edits"; - return false; - } - // get the ids of all the zones (plus the global entity edit filter) that the position // lies within auto zoneIDs = getZonesByPosition(position); - for (auto id : zoneIDs) { + if (id == itemID) { + qCDebug(entities) << "zone filter not applied to its own edit"; + continue; + } qCDebug(entities) << "applying filter for zone" << id; // get the filter pair, etc... @@ -64,7 +62,10 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper _lock.unlock(); if (filterData.valid()) { - + if (filterData.rejectAll) { + qCDebug(entities) << "rejecting all edits"; + return false; + } auto oldProperties = propertiesIn.getDesiredProperties(); auto specifiedProperties = propertiesIn.getChangedProperties(); propertiesIn.setDesiredProperties(specifiedProperties); @@ -81,7 +82,6 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper result = QScriptValue(); } - if (result.isObject()){ qCDebug(entities) << "filter result accepted"; // make propertiesIn reflect the changes, for next filter... @@ -131,7 +131,15 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { // first remove any existing info for this entity removeFilter(entityID); + + // reject all edits until we load the script + FilterData filterData; + filterData.rejectAll = true; + _lock.lockForWrite(); + _filterDataMap.insert(entityID, filterData); + _lock.unlock(); + auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL); if (!scriptRequest) { qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString(); @@ -195,6 +203,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { // put the engine in the engine map (so we don't leak them, etc...) FilterData filterData; filterData.engine = engine; + filterData.rejectAll = false; // define the uncaughtException function QScriptEngine& engineRef = *engine; @@ -209,10 +218,11 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { global.setProperty("Entities", entitiesObject); filterData.filterFn = global.property("filter"); if (!filterData.filterFn.isFunction()) { - qDebug() << "Filter function specified but not found. Ignoring filter"; + qDebug() << "Filter function specified but not found. Will reject all edits for those without lock rights."; delete engine; - return; + filterData.rejectAll=true; } + _lock.lockForWrite(); _filterDataMap.insert(entityID, filterData); diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 8ad82621dd..6aeb347603 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -30,8 +30,10 @@ public: QScriptValue filterFn; std::function uncaughtExceptions; QScriptEngine* engine; - - bool valid() { return (engine != nullptr && filterFn.isFunction() && uncaughtExceptions); } + bool rejectAll; + + FilterData(): engine(nullptr), rejectAll(false) {}; + bool valid() { return (rejectAll || (engine != nullptr && filterFn.isFunction() && uncaughtExceptions)); } }; EntityEditFilters() {}; @@ -40,8 +42,8 @@ public: void addFilter(EntityItemID entityID, QString filterURL); void removeFilter(EntityItemID entityID); - bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType); - void rejectAll(bool state) {_rejectAll = state; } + bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, + EntityTree::FilterType filterType, EntityItemID& entityID); signals: void filterAdded(EntityItemID id, bool success); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e1edc6bb20..cd12b703f8 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -928,9 +928,9 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList(); - if (entityEditFilters) { + if (entityEditFilters && existingEntity) { auto position = existingEntity->getPosition(); - accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType); + accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, existingEntity->getEntityItemID()); } return accepted; From 17acd8fa4fdfc458bc97c5139808cb9f37c07af3 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 14 Feb 2017 13:30:43 -0700 Subject: [PATCH 18/42] I should just build on linux exclusively --- libraries/entities/src/EntityTree.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index cd12b703f8..04f185ce97 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -930,7 +930,8 @@ bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemP auto entityEditFilters = DependencyManager::get(); if (entityEditFilters && existingEntity) { auto position = existingEntity->getPosition(); - accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, existingEntity->getEntityItemID()); + auto entityID = existingEntity->getEntityItemID(); + accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID); } return accepted; From 08d30608c7e237d25b6f95b5864c35a22e2ee529 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 14 Feb 2017 14:28:20 -0700 Subject: [PATCH 19/42] remove debug logging --- libraries/entities/src/EntityEditFilters.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index cda7f1bc59..d58b996878 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -21,7 +21,6 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { _lock.lockForRead(); auto zoneIDs = _filterDataMap.keys(); _lock.unlock(); - qCDebug(entities) << "looking at " << zoneIDs.size() << "possible zones, at " << position; for (auto id : zoneIDs) { if (!id.isInvalidID()) { // for now, look it up in the tree (soon we need to cache or similar?) @@ -44,17 +43,14 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID) { - qCDebug(entities) << "in EntityEditFilters"; // get the ids of all the zones (plus the global entity edit filter) that the position // lies within auto zoneIDs = getZonesByPosition(position); for (auto id : zoneIDs) { if (id == itemID) { - qCDebug(entities) << "zone filter not applied to its own edit"; continue; } - qCDebug(entities) << "applying filter for zone" << id; // get the filter pair, etc... _lock.lockForRead(); @@ -63,7 +59,6 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper if (filterData.valid()) { if (filterData.rejectAll) { - qCDebug(entities) << "rejecting all edits"; return false; } auto oldProperties = propertiesIn.getDesiredProperties(); @@ -83,7 +78,6 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper } if (result.isObject()){ - qCDebug(entities) << "filter result accepted"; // make propertiesIn reflect the changes, for next filter... propertiesIn.copyFromScriptValue(result, false); From f8055a027ec14095de24e3637e1d3c96750f6010 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 10 Feb 2017 15:06:55 -0800 Subject: [PATCH 20/42] Refactor of pal.js to dynamically switch between toolbar and tablet. --- scripts/system/pal.js | 258 ++++++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 112 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 2e07a2d431..3e52eec4dd 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -1,6 +1,7 @@ "use strict"; -/*jslint vars: true, plusplus: true, forin: true*/ -/*globals Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, OverlayWindow, Toolbars, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, OverlayWindow, Toolbars, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // pal.js // @@ -13,21 +14,24 @@ (function() { // BEGIN LOCAL_SCOPE -// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed +// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed // something, will revisit as this is sorta horrible. -const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") +var UNSELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") }; -const SELECTED_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") +var SELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") }; -const HOVER_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") +var HOVER_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") }; -const UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; -const SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; -const HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; +var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; +var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now var conserveResources = true; @@ -87,24 +91,24 @@ ExtendedOverlay.prototype.hover = function (hovering) { } else { lastHoveringId = 0; } - } + } this.editOverlay({color: color(this.selected, hovering, this.audioLevel)}); if (this.model) { this.model.editOverlay({textures: textures(this.selected, hovering)}); } if (hovering) { // un-hover the last hovering overlay - if (lastHoveringId && lastHoveringId != this.key) { + if (lastHoveringId && lastHoveringId !== this.key) { ExtendedOverlay.get(lastHoveringId).hover(false); } lastHoveringId = this.key; } -} +}; ExtendedOverlay.prototype.select = function (selected) { if (this.selected === selected) { return; } - + UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key); this.editOverlay({color: color(selected, this.hovering, this.audioLevel)}); @@ -204,6 +208,7 @@ var pal = new OverlayWindow({ visible: false }); function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var data; switch (message.method) { case 'selected': selectedIds = message.params; @@ -250,7 +255,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } break; case 'displayNameUpdate': - if (MyAvatar.displayName != message.params) { + if (MyAvatar.displayName !== message.params) { MyAvatar.displayName = message.params; UserActivityLogger.palAction("display_name_change", ""); } @@ -261,9 +266,9 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } function sendToQml(message) { - if (Settings.getValue("HUDUIEnabled")) { + if (currentUIMode === "toolbar") { pal.sendToQml(message); - } else { + } else if (currentUIMode === "tablet") { tablet.sendToQml(message); } } @@ -273,7 +278,7 @@ function sendToQml(message) { // function addAvatarNode(id) { var selected = ExtendedOverlay.isSelected(id); - return new ExtendedOverlay(id, "sphere", { + return new ExtendedOverlay(id, "sphere", { drawInFront: true, solid: true, alpha: 0.8, @@ -339,7 +344,7 @@ function updateOverlays() { if (!id) { return; // don't update ourself } - + var overlay = ExtendedOverlay.get(id); if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. print('Adding non-PAL avatar node', id); @@ -349,7 +354,7 @@ function updateOverlays() { var target = avatar.position; var distance = Vec3.distance(target, eye); var offset = 0.2; - + // base offset on 1/2 distance from hips to head if we can var headIndex = avatar.getJointIndex("Head"); if (headIndex > 0) { @@ -358,7 +363,7 @@ function updateOverlays() { // get diff between target and eye (a vector pointing to the eye from avatar position) var diff = Vec3.subtract(target, eye); - + // move a bit in front, towards the camera target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); @@ -369,12 +374,12 @@ function updateOverlays() { overlay.editOverlay({ color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel), position: target, - dimensions: 0.032 * distance + dimensions: 0.032 * distance }); if (overlay.model) { overlay.model.ping = pingPong; overlay.model.editOverlay({ - position: target, + position: target, scale: 0.2 * distance, // constant apparent size rotation: Camera.orientation }); @@ -393,7 +398,9 @@ function removeOverlays() { selectedIds = []; lastHoveringId = 0; HighlightedEntity.clearOverlays(); - ExtendedOverlay.some(function (overlay) { overlay.deleteOverlay(); }); + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); } // @@ -423,12 +430,13 @@ function handleMouseMove(pickRay) { // given the pickRay, just do the hover logi // handy global to keep track of which hand is the mouse (if any) var currentHandPressed = 0; -const TRIGGER_CLICK_THRESHOLD = 0.85; -const TRIGGER_PRESS_THRESHOLD = 0.05; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; if (HMD.active) { - if (currentHandPressed != 0) { + if (currentHandPressed !== 0) { pickRay = controllerComputePickRay(currentHandPressed); } else { // nothing should hover, so @@ -441,18 +449,18 @@ function handleMouseMoveEvent(event) { // find out which overlay (if any) is ove handleMouseMove(pickRay); } function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one + // The idea is if you press one trigger, it is the one // we will consider the mouse. Even if the other is pressed, // we ignore it until this one is no longer pressed. - isPressed = value > TRIGGER_PRESS_THRESHOLD; - if (currentHandPressed == 0) { + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { currentHandPressed = isPressed ? hand : 0; return; } - if (currentHandPressed == hand) { + if (currentHandPressed === hand) { currentHandPressed = isPressed ? hand : 0; return; - } + } // otherwise, the other hand is still triggered // so do nothing. } @@ -478,7 +486,7 @@ function makeClickHandler(hand) { function makePressHandler(hand) { return function (value) { handleTriggerPressed(hand, value); - } + }; } triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); @@ -491,25 +499,56 @@ var button; var buttonName = "PEOPLE"; var tablet = null; var toolBar = null; -if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolBar.addButton({ - objectName: buttonName, - imageURL: Script.resolvePath("assets/images/tools/people.svg"), - visible: true, - alpha: 0.9 - }); - pal.fromQml.connect(fromQml); -} else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - text: buttonName, - icon: "icons/tablet-icons/people-i.svg", - sortOrder: 7 - }); - tablet.fromQml.connect(fromQml); + +var currentUIMode; + +function onTabletScreenChanged(type, url) { + if (type !== "QML" || url !== "../Pal.qml") { + off(); + } } +// @param mode {string} "tablet" or "toolbar" +function startup(mode) { + if (mode === "toolbar") { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/people.svg"), + visible: true, + alpha: 0.9 + }); + pal.fromQml.connect(fromQml); + button.clicked.connect(onToolbarButtonClicked); + pal.visibleChanged.connect(onVisibleChanged); + pal.closed.connect(off); + } else if (mode === "tablet") { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/people-i.svg", + sortOrder: 7 + }); + tablet.fromQml.connect(fromQml); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); + } else { + print("ERROR: pal.js: bad ui mode"); + } + + Users.usernameFromIDReply.connect(usernameFromIDReply); + Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); + Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); + Messages.subscribe(CHANNEL); + Messages.messageReceived.connect(receiveMessage); + Users.avatarDisconnected.connect(avatarDisconnected); + + currentUIMode = mode; +} + +// var mode = Settings.getValue("HUDUIEnabled"); +startup(HMD.active ? "tablet" : "toolbar"); + var isWired = false; var audioTimer; var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) @@ -521,33 +560,20 @@ function off() { Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); isWired = false; } - if (audioTimer) { Script.clearInterval(audioTimer); } + if (audioTimer) { + Script.clearInterval(audioTimer); + } triggerMapping.disable(); // It's ok if we disable twice. triggerPressMapping.disable(); // see above removeOverlays(); Users.requestsDomainListData = false; } -function onClicked() { - if (Settings.getValue("HUDUIEnabled")) { - if (!pal.visible) { - Users.requestsDomainListData = true; - populateUserList(); - pal.raise(); - isWired = true; - Script.update.connect(updateOverlays); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); - } else { - off(); - } - pal.setVisible(!pal.visible); - } else { - tablet.loadQMLSource("../Pal.qml"); + +function onToolbarButtonClicked() { + if (!pal.visible) { Users.requestsDomainListData = true; populateUserList(); + pal.raise(); isWired = true; Script.update.connect(updateOverlays); Controller.mousePressEvent.connect(handleMouseEvent); @@ -555,7 +581,23 @@ function onClicked() { triggerMapping.enable(); triggerPressMapping.enable(); audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); + } else { + off(); } + pal.setVisible(!pal.visible); +} + +function onTabletButtonClicked() { + tablet.loadQMLSource("../Pal.qml"); + Users.requestsDomainListData = true; + populateUserList(); + isWired = true; + Script.update.connect(updateOverlays); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); } // @@ -570,8 +612,8 @@ function receiveMessage(channel, messageString, senderID) { var message = JSON.parse(messageString); switch (message.method) { case 'select': - if (!pal.visible) { - onClicked(); + if (currentUIMode === "toolbar" && !pal.visible) { + onToolbarButtonClicked(); } sendToQml(message); // Accepts objects, not just strings. break; @@ -579,8 +621,6 @@ function receiveMessage(channel, messageString, senderID) { print('Unrecognized PAL message', messageString); } } -Messages.subscribe(CHANNEL); -Messages.messageReceived.connect(receiveMessage); var AVERAGING_RATIO = 0.05; @@ -638,57 +678,51 @@ function avatarDisconnected(nodeID) { // remove from the pal list sendToQml({method: 'avatarDisconnected', params: [nodeID]}); } + // // Button state. // function onVisibleChanged() { button.editProperties({isActive: pal.visible}); } -button.clicked.connect(onClicked); -pal.visibleChanged.connect(onVisibleChanged); -pal.closed.connect(off); - -if (!Settings.getValue("HUDUIEnabled")) { - tablet.screenChanged.connect(function (type, url) { - if (type !== "QML" || url !== "../Pal.qml") { - off(); - } - }); -} - -Users.usernameFromIDReply.connect(usernameFromIDReply); -Users.avatarDisconnected.connect(avatarDisconnected); function clearLocalQMLDataAndClosePAL() { sendToQml({ method: 'clearLocalQMLData' }); - if (pal.visible) { - onClicked(); // Close the PAL + if (currentUIMode === "toolbar" && pal.visible) { + onToolbarButtonClicked(); // Close the PAL } } -Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); -Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); + +function shutdown() { + if (currentUIMode === "toolbar") { + button.clicked.disconnect(onToolbarButtonClicked); + toolBar.removeButton(buttonName); + pal.visibleChanged.disconnect(onVisibleChanged); + pal.closed.disconnect(off); + } else if (currentUIMode === "tablet") { + button.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(button); + tablet.screenChanged.disconnect(onTabletScreenChanged); + } + + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); + Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); + Messages.subscribe(CHANNEL); + Messages.messageReceived.disconnect(receiveMessage); + Users.avatarDisconnected.disconnect(avatarDisconnected); + + off(); +} // // Cleanup. // -Script.scriptEnding.connect(function () { - button.clicked.disconnect(onClicked); - if (tablet) { - tablet.removeButton(button); - } - if (toolBar) { - toolBar.removeButton(buttonName); - } - pal.visibleChanged.disconnect(onVisibleChanged); - pal.closed.disconnect(off); - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); - Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); - Messages.unsubscribe(CHANNEL); - Messages.messageReceived.disconnect(receiveMessage); - Users.avatarDisconnected.disconnect(avatarDisconnected); - off(); +Script.scriptEnding.connect(shutdown); + +HMD.displayModeChanged.connect(function () { + shutdown(); + startup(HMD.active ? "tablet" : "toolbar"); }); - }()); // END LOCAL_SCOPE From 5e25d073f3492da7709b64d50984a1a17c557b5e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Feb 2017 13:19:13 -0800 Subject: [PATCH 21/42] WIP commit --- interface/src/Application.cpp | 2 + .../src/TabletScriptingInterface.cpp | 65 ++++++++++++++++++- .../src/TabletScriptingInterface.h | 21 +++++- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 488e97b5e6..745cadbbdd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5462,6 +5462,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); + DependencyManager::get().data()->setToolbarScriptingInterface(DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); qScriptRegisterMetaType(scriptEngine, CustomPromptResultToScriptValue, CustomPromptResultFromScriptValue); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 7e8fdd6bc3..d048da93d2 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -22,6 +22,22 @@ TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("Hifi", 1, 0, "SoundEffect"); } +QObject* TabletScriptingInterface::getSystemToolbarProxy() { + const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != _toolbarScriptingInterface->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + QObject* toolbarProxy = nullptr; + bool hasResult = QMetaObject::invokeMethod(_toolbarScriptingInterface, "getToolbar", connectionType, Q_RETURN_ARG(QObject*, toolbarProxy), Q_ARG(QString, SYSTEM_TOOLBAR)); + if (hasResult) { + return toolbarProxy; + } else { + qCWarning(scriptengine) << "ToolbarScriptingInterface getToolbar has no result"; + return nullptr; + } +} + QObject* TabletScriptingInterface::getTablet(const QString& tabletId) { std::lock_guard guard(_mutex); @@ -145,6 +161,21 @@ TabletProxy::TabletProxy(QString name) : _name(name) { ; } +void TabletProxy::setToolbarMode(bool toolbarMode) { + if (toolbarMode == _toolbarMode) { + return; + } + + if (toolbarMode) { + removeButtonsFromHomeScreen(); + addButtonsToToolbar(); + } else { + removeButtonsFromToolbar(); + addButtonsToHomeScreen(); + } + _toolbarMode = toolbarMode; +} + static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { QVariant resultVar; Qt::ConnectionType connectionType = Qt::AutoConnection; @@ -379,12 +410,34 @@ void TabletProxy::addButtonsToMenuScreen() { } void TabletProxy::removeButtonsFromHomeScreen() { - auto tabletScriptingInterface = DependencyManager::get(); for (auto& buttonProxy : _tabletButtonProxies) { buttonProxy->setQmlButton(nullptr); } } +void TabletProxy::addButtonsToToolbar() { + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + for (auto& buttonProxy : _tabletButtonProxies) { + // copy properties from tablet button proxy to toolbar button proxy. + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != toolbarProxy->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + QObject* toolbarButtonProxy = nullptr; + bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties())); + if (hasResult) { + buttonProxy->setToolbarButtonProxy(toolbarButtonProxy); + } else { + qCWarning(scriptengine) << "ToolbarProxy addButton has no result"; + } + } +} + +void TabletProxy::removeButtonsFromToolbar() { + // +} + QQuickItem* TabletProxy::getQmlTablet() const { if (!_qmlTabletRoot) { return nullptr; @@ -444,6 +497,11 @@ void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { _qmlButton = qmlButton; } +void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { + std::lock_guard guard(_mutex); + _toolbarButtonProxy = toolbarButtonProxy; +} + QVariantMap TabletButtonProxy::getProperties() const { std::lock_guard guard(_mutex); return _properties; @@ -451,6 +509,7 @@ QVariantMap TabletButtonProxy::getProperties() const { void TabletButtonProxy::editProperties(QVariantMap properties) { std::lock_guard guard(_mutex); + QVariantMap::const_iterator iter = properties.constBegin(); while (iter != properties.constEnd()) { _properties[iter.key()] = iter.value(); @@ -459,6 +518,10 @@ void TabletButtonProxy::editProperties(QVariantMap properties) { } ++iter; } + + if (_toolbarButtonProxy) { + QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariant, properties)); + } } #include "TabletScriptingInterface.moc" diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index 8ba69ccdde..b2f9f2ab47 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -35,6 +35,9 @@ class TabletScriptingInterface : public QObject, public Dependency { public: TabletScriptingInterface(); + void setToolbarScriptingInterface(QObject* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; } + QObject* getSystemToolbarProxy(); + /**jsdoc * Creates or retruns a new TabletProxy and returns it. * @function Tablet.getTablet @@ -58,15 +61,19 @@ private: protected: std::mutex _mutex; std::map> _tabletProxies; + QObject* _toolbarScriptingInterface { nullptr }; }; /**jsdoc * @class TabletProxy * @property name {string} READ_ONLY: name of this tablet + * @property toolbarMode {bool} - used to transition this tablet into and out of toolbar mode. + * When tablet is in toolbar mode, all its buttons will appear in a floating toolbar. */ class TabletProxy : public QObject { Q_OBJECT Q_PROPERTY(QString name READ getName) + Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode) public: TabletProxy(QString name); @@ -74,6 +81,11 @@ public: Q_INVOKABLE void gotoMenuScreen(const QString& submenu = ""); + QString getName() const { return _name; } + + bool getToolbarMode() const { return _toolbarMode; } + void setToolbarMode(bool toolbarMode); + /**jsdoc * transition to the home screen * @function TabletProxy#gotoHomeScreen @@ -120,8 +132,6 @@ public: */ Q_INVOKABLE void updateAudioBar(const double micLevel); - QString getName() const { return _name; } - /**jsdoc * Used to send an event to the html/js embedded in the tablet * @function TabletProxy#emitScriptEvent @@ -169,17 +179,20 @@ signals: */ void screenChanged(QVariant type, QVariant url); -private slots: +protected slots: void addButtonsToHomeScreen(); void addButtonsToMenuScreen(); protected: void removeButtonsFromHomeScreen(); + void addButtonsToToolbar(); + void removeButtonsFromToolbar(); QString _name; std::mutex _mutex; std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; + bool _toolbarMode { false }; enum class State { Uninitialized, Home, Web, Menu, QML }; State _state { State::Uninitialized }; @@ -196,6 +209,7 @@ public: TabletButtonProxy(const QVariantMap& properties); void setQmlButton(QQuickItem* qmlButton); + void setToolbarButtonProxy(QObject* toolbarButtonProxy); QUuid getUuid() const { return _uuid; } @@ -229,6 +243,7 @@ protected: int _stableOrder; mutable std::mutex _mutex; QQuickItem* _qmlButton { nullptr }; + QObject* _toolbarButtonProxy { nullptr }; QVariantMap _properties; }; From 45039236651f197e8b7e4521d3b400b4d49cfa33 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Feb 2017 17:43:03 -0800 Subject: [PATCH 22/42] Initial version of TabletProxy.toolbarMode boolean Copies all tablet buttons between the tablet and the system toolbar. To test type this in the console: Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode = true; --- .../resources/icons/tablet-icons/blank.svg | 48 +++++++++++ .../tablet-icons/empty-toolbar-button.svg | 79 +++++++++++++++++++ .../qml/hifi/toolbars/StateImage.qml | 1 + .../qml/hifi/toolbars/ToolbarButton.qml | 36 ++++++++- .../src/TabletScriptingInterface.cpp | 29 +++++-- 5 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/blank.svg create mode 100644 interface/resources/icons/tablet-icons/empty-toolbar-button.svg diff --git a/interface/resources/icons/tablet-icons/blank.svg b/interface/resources/icons/tablet-icons/blank.svg new file mode 100644 index 0000000000..ae463c4242 --- /dev/null +++ b/interface/resources/icons/tablet-icons/blank.svg @@ -0,0 +1,48 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/empty-toolbar-button.svg b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg new file mode 100644 index 0000000000..5ba101c618 --- /dev/null +++ b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg @@ -0,0 +1,79 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/qml/hifi/toolbars/StateImage.qml b/interface/resources/qml/hifi/toolbars/StateImage.qml index ee0778626d..e0389c5e02 100644 --- a/interface/resources/qml/hifi/toolbars/StateImage.qml +++ b/interface/resources/qml/hifi/toolbars/StateImage.qml @@ -29,6 +29,7 @@ Item { id: image y: -parent.yOffset; width: parent.width + source: "../../../icons/tablet-icons/empty-toolbar-button.svg" } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 91c992bf0d..0e4423268b 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -11,12 +11,23 @@ StateImage { property int imageOnOut: 0 property int imageOnIn: 2 + property string text: "" + property string icon: "icons/tablet-icons/blank.svg" + signal clicked() function changeProperty(key, value) { button[key] = value; } + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + function updateState() { if (!button.isEntered && !button.isActive) { buttonState = imageOffOut; @@ -38,7 +49,7 @@ StateImage { running: false onTriggered: button.clicked(); } - + MouseArea { id: mouseArea hoverEnabled: true @@ -53,5 +64,28 @@ StateImage { updateState(); } } + + Image { + id: icon + width: 28 + height: 28 + anchors.bottom: caption.top + anchors.bottomMargin: 0 + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.Stretch + source: urlHelper(button.icon) + } + + Text { + id: caption + color: "#ffffff" + text: button.text + font.bold: false + font.pixelSize: 9 + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + } } diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index d048da93d2..576fec0ca3 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -166,6 +166,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { return; } + _toolbarMode = toolbarMode; + if (toolbarMode) { removeButtonsFromHomeScreen(); addButtonsToToolbar(); @@ -173,7 +175,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { removeButtonsFromToolbar(); addButtonsToHomeScreen(); } - _toolbarMode = toolbarMode; } static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { @@ -373,7 +374,7 @@ void TabletProxy::sendToQml(QVariant msg) { void TabletProxy::addButtonsToHomeScreen() { auto tablet = getQmlTablet(); - if (!tablet) { + if (!tablet || _toolbarMode) { return; } @@ -410,7 +411,11 @@ void TabletProxy::addButtonsToMenuScreen() { } 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); } } @@ -418,12 +423,14 @@ void TabletProxy::removeButtonsFromHomeScreen() { void TabletProxy::addButtonsToToolbar() { auto tabletScriptingInterface = DependencyManager::get(); QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != toolbarProxy->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + for (auto& buttonProxy : _tabletButtonProxies) { // copy properties from tablet button proxy to toolbar button proxy. - Qt::ConnectionType connectionType = Qt::AutoConnection; - if (QThread::currentThread() != toolbarProxy->thread()) { - connectionType = Qt::BlockingQueuedConnection; - } QObject* toolbarButtonProxy = nullptr; bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties())); if (hasResult) { @@ -435,7 +442,13 @@ void TabletProxy::addButtonsToToolbar() { } void TabletProxy::removeButtonsFromToolbar() { - // + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + for (auto& buttonProxy : _tabletButtonProxies) { + // remove button from toolbarProxy + QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString())); + buttonProxy->setToolbarButtonProxy(nullptr); + } } QQuickItem* TabletProxy::getQmlTablet() const { @@ -483,12 +496,14 @@ QQuickItem* TabletProxy::getQmlMenu() const { // const QString UUID_KEY = "uuid"; +const QString OBJECT_NAME_KEY = "objectName"; const QString STABLE_ORDER_KEY = "stableOrder"; static int s_stableOrder = 1; TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _uuid(QUuid::createUuid()), _stableOrder(++s_stableOrder), _properties(properties) { // this is used to uniquely identify this button. _properties[UUID_KEY] = _uuid; + _properties[OBJECT_NAME_KEY] = _uuid.toString(); _properties[STABLE_ORDER_KEY] = _stableOrder; } From 912e8aa04a18cd486a4baaf68a5d6e5aaeb88816 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 14 Feb 2017 15:18:24 -0800 Subject: [PATCH 23/42] Tablet buttons now work in toolbar. Switching between tablet and toolbar is not as dynamic as I'd like but it's a start. --- .../resources/qml/hifi/tablet/TabletRoot.qml | 4 +- .../resources/qml/hifi/tablet/WindowRoot.qml | 94 +++++++++++++++ .../qml/hifi/toolbars/ToolbarButton.qml | 8 +- interface/resources/qml/windows/Window.qml | 4 + .../src/TabletScriptingInterface.cpp | 113 ++++++++++++++---- .../src/TabletScriptingInterface.h | 2 + libraries/ui/src/InfoView.cpp | 26 ++-- scripts/system/pal.js | 7 +- 8 files changed, 211 insertions(+), 47 deletions(-) create mode 100644 interface/resources/qml/hifi/tablet/WindowRoot.qml diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 481c7846a9..792c8d5082 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -33,6 +33,8 @@ Item { } } + function setShown(value) {} + SoundEffect { id: buttonClickSound volume: 0.1 @@ -85,5 +87,5 @@ Item { } width: 480 - height: 720 + height: 706 } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml new file mode 100644 index 0000000000..14e969b3a6 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -0,0 +1,94 @@ +// +// WindowRoot.qml +// +// Created by Anthony Thibault on 14 Feb 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// This qml is used when tablet content is shown on the 2d overlay ui +// TODO: FIXME: this is practically identical to TabletRoot.qml + +import "../../windows" as Windows +import QtQuick 2.0 +import Hifi 1.0 + +Windows.ScrollingWindow { + id: tabletRoot + objectName: "tabletRoot" + property string username: "Unknown user" + property var eventBridge; + shown: false + resizable: false + + signal showDesktop(); + + function loadSource(url) { + loader.source = url; + } + + function loadWebUrl(url, injectedJavaScriptUrl) { + loader.item.url = url; + loader.item.scriptURL = injectedJavaScriptUrl; + } + + // used to send a message from qml to interface script. + signal sendToScript(var message); + + // used to receive messages from interface script + function fromScript(message) { + if (loader.item.hasOwnProperty("fromScript")) { + loader.item.fromScript(message); + } + } + + SoundEffect { + id: buttonClickSound + volume: 0.1 + source: "../../../sounds/Gamemaster-Audio-button-click.wav" + } + + function playButtonClickSound() { + // Because of the asynchronous nature of initalization, it is possible for this function to be + // called before the C++ has set the globalPosition context variable. + if (typeof globalPosition !== 'undefined') { + buttonClickSound.play(globalPosition); + } + } + + function setUsername(newUsername) { + username = newUsername; + } + + Loader { + id: loader + objectName: "loader" + asynchronous: false + + height: pane.scrollHeight + width: pane.contentWidth + anchors.left: parent.left + anchors.top: parent.top + + onLoaded: { + if (loader.item.hasOwnProperty("eventBridge")) { + loader.item.eventBridge = eventBridge; + + // Hook up callback for clara.io download from the marketplace. + eventBridge.webEventReceived.connect(function (event) { + if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); + } + }); + } + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + loader.item.forceActiveFocus(); + } + } + + implicitWidth: 480 + implicitHeight: 706 +} diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 0e4423268b..591544d614 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -12,7 +12,9 @@ StateImage { property int imageOnIn: 2 property string text: "" + property string activeText: button.text property string icon: "icons/tablet-icons/blank.svg" + property string activeIcon: button.icon signal clicked() @@ -73,13 +75,13 @@ StateImage { anchors.bottomMargin: 0 anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.Stretch - source: urlHelper(button.icon) + source: urlHelper(button.isActive ? button.activeIcon : button.icon) } Text { id: caption - color: "#ffffff" - text: button.text + color: button.isActive ? "#000000" : "#ffffff" + text: button.isActive ? button.activeText : button.text font.bold: false font.pixelSize: 9 anchors.bottom: parent.bottom diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index d22d8ecbe8..20216ed7ae 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -85,6 +85,10 @@ Fadable { function setDefaultFocus() {} // Default function; can be overridden by dialogs. + function setShown(value) { + window.shown = value; + } + property var rectifier: Timer { property bool executing: false; interval: 100 diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 576fec0ca3..dc6b1d6aa3 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -15,7 +15,8 @@ #include #include "ScriptEngineLogging.h" #include "DependencyManager.h" -#include "OffscreenUi.h" +#include +#include #include "SoundEffect.h" TabletScriptingInterface::TabletScriptingInterface() { @@ -156,9 +157,10 @@ QObject* TabletScriptingInterface::getFlags() static const char* TABLET_SOURCE_URL = "Tablet.qml"; static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; +static int s_windowNameCounter = 1; TabletProxy::TabletProxy(QString name) : _name(name) { - ; + } void TabletProxy::setToolbarMode(bool toolbarMode) { @@ -171,9 +173,27 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { if (toolbarMode) { removeButtonsFromHomeScreen(); addButtonsToToolbar(); + + // create new desktop window + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=, this] { + InfoView::registerType(); + QString name = _name + QString("%1").arg(s_windowNameCounter++); + offscreenUi->show(QUrl("hifi/tablet/WindowRoot.qml"), name, [&](QQmlContext* context, QObject* newObject) { + QQuickItem* item = dynamic_cast(newObject); + _desktopWindow = item; + QObject::connect(_desktopWindow, SIGNAL(windowClosed), this, SLOT(desktopWindowClosed())); + }); + }); } else { removeButtonsFromToolbar(); addButtonsToHomeScreen(); + + // destroy desktop window + if (_desktopWindow) { + _desktopWindow->deleteLater(); + _desktopWindow = nullptr; + } } } @@ -246,39 +266,63 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr } void TabletProxy::gotoMenuScreen(const QString& submenu) { - if (_qmlTabletRoot) { + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow; + } + + if (root) { if (_state != State::Menu) { removeButtonsFromHomeScreen(); - QMetaObject::invokeMethod(_qmlTabletRoot, "setOption", Q_ARG(const QVariant&, QVariant(submenu))); - auto loader = _qmlTabletRoot->findChild("loader"); + QMetaObject::invokeMethod(root, "setOption", Q_ARG(const QVariant&, QVariant(submenu))); + auto loader = root->findChild("loader"); QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()), Qt::DirectConnection); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); _state = State::Menu; emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL)); + QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); } } } void TabletProxy::loadQMLSource(const QVariant& path) { - if (_qmlTabletRoot) { + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow; + } + + if (root) { if (_state != State::QML) { removeButtonsFromHomeScreen(); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, path)); + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); _state = State::QML; emit screenChanged(QVariant("QML"), path); + QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); } } } + void TabletProxy::gotoHomeScreen() { - if (_qmlTabletRoot) { - if (_state != State::Home) { + if (_state != State::Home) { + if (!_toolbarMode && _qmlTabletRoot) { auto loader = _qmlTabletRoot->findChild("loader"); QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection); QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); - _state = State::Home; - emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); + } else if (_toolbarMode && _desktopWindow) { + // close desktop window + if (_desktopWindow) { + QMetaObject::invokeMethod(_desktopWindow, "setShown", Q_ARG(const QVariant&, QVariant(false))); + } } + _state = State::Home; + emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); } } @@ -287,17 +331,24 @@ void TabletProxy::gotoWebScreen(const QString& url) { } void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) { - if (_qmlTabletRoot) { - if (_state == State::Home) { - removeButtonsFromHomeScreen(); + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow; + } + + if (_state != State::Web) { + if (root) { + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); } - if (_state != State::Web) { - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); - _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); - } - QMetaObject::invokeMethod(_qmlTabletRoot, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), - Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); + _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); + } + if (root) { + QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); + QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } } @@ -391,11 +442,18 @@ QObject* TabletProxy::getTabletSurface() { } void TabletProxy::addButtonsToMenuScreen() { - if (!_qmlTabletRoot) { + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow; + } + + if (!root) { return; } - auto loader = _qmlTabletRoot->findChild("loader"); + auto loader = root->findChild("loader"); if (!loader) { return; } @@ -420,6 +478,10 @@ void TabletProxy::removeButtonsFromHomeScreen() { } } +void TabletProxy::desktopWindowClosed() { + gotoHomeScreen(); +} + void TabletProxy::addButtonsToToolbar() { auto tabletScriptingInterface = DependencyManager::get(); QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); @@ -515,6 +577,9 @@ void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { std::lock_guard guard(_mutex); _toolbarButtonProxy = toolbarButtonProxy; + if (_toolbarButtonProxy) { + QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot())); + } } QVariantMap TabletButtonProxy::getProperties() const { diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index b2f9f2ab47..c9aee518fc 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -182,6 +182,7 @@ signals: protected slots: void addButtonsToHomeScreen(); void addButtonsToMenuScreen(); + void desktopWindowClosed(); protected: void removeButtonsFromHomeScreen(); void addButtonsToToolbar(); @@ -192,6 +193,7 @@ protected: std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; + QQuickItem* _desktopWindow { nullptr }; bool _toolbarMode { false }; enum class State { Uninitialized, Home, Web, Menu, QML }; diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp index d2c72bf5f2..cb80e3f6db 100644 --- a/libraries/ui/src/InfoView.cpp +++ b/libraries/ui/src/InfoView.cpp @@ -20,17 +20,22 @@ const QString InfoView::NAME{ "InfoView" }; Setting::Handle infoVersion("info-version", QString()); -InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) { +static bool registered{ false }; +InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) { + registerType(); } -void InfoView::registerType() { - qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); -} +void InfoView::registerType() { + if (!registered) { + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); + registered = true; + } +} QString fetchVersion(const QUrl& url) { QXmlQuery query; - query.bindVariable("file", QVariant(url)); + query.bindVariable("file", QVariant(url)); query.setQuery("string((doc($file)//input[@id='version'])[1]/@value)"); QString r; query.evaluateTo(&r); @@ -38,14 +43,10 @@ QString fetchVersion(const QUrl& url) { } void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQuery) { - static bool registered{ false }; - if (!registered) { - registerType(); - registered = true; - } + registerType(); QUrl url; if (QDir(path).isRelative()) { - url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path); + url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path); } else { url = QUrl::fromLocalFile(path); } @@ -56,7 +57,7 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQue const QString version = fetchVersion(url); // If we have version information stored if (lastVersion != QString::null) { - // Check to see the document version. If it's valid and matches + // Check to see the document version. If it's valid and matches // the stored version, we're done, so exit if (version == QString::null || version == lastVersion) { return; @@ -87,4 +88,3 @@ void InfoView::setUrl(const QUrl& url) { emit urlChanged(); } } - diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 3e52eec4dd..de869ad7de 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -547,7 +547,7 @@ function startup(mode) { } // var mode = Settings.getValue("HUDUIEnabled"); -startup(HMD.active ? "tablet" : "toolbar"); +startup("tablet"); var isWired = false; var audioTimer; @@ -720,9 +720,4 @@ function shutdown() { // Script.scriptEnding.connect(shutdown); -HMD.displayModeChanged.connect(function () { - shutdown(); - startup(HMD.active ? "tablet" : "toolbar"); -}); - }()); // END LOCAL_SCOPE From e6969321caa45c57ea568e39290ceb0a9b55ac6b Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 15 Feb 2017 09:15:35 -0700 Subject: [PATCH 24/42] fix for not properly filtering entity adds --- libraries/entities/src/EntityEditFilters.cpp | 6 ++---- libraries/entities/src/EntityTree.cpp | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index d58b996878..3071072efc 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -48,7 +48,7 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // lies within auto zoneIDs = getZonesByPosition(position); for (auto id : zoneIDs) { - if (id == itemID) { + if (!itemID.isInvalidID() && id == itemID) { continue; } @@ -74,7 +74,7 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args); if (filterData.uncaughtExceptions()) { - result = QScriptValue(); + return false; } if (result.isObject()){ @@ -87,8 +87,6 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper auto out = QJsonValue::fromVariant(result.toVariant()); wasChanged |= (in != out); } else { - // an edit was rejected, so we stop here and return false - qCDebug(entities) << "Edit rejected by filter " << id ; return false; } } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 04f185ce97..0f65ac55ff 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -928,9 +928,9 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList(); - if (entityEditFilters && existingEntity) { - auto position = existingEntity->getPosition(); - auto entityID = existingEntity->getEntityItemID(); + if (entityEditFilters) { + auto position = existingEntity ? existingEntity->getPosition() : propertiesIn.getPosition(); + auto entityID = existingEntity ? existingEntity->getEntityItemID() : EntityItemID(); accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID); } From 2e9217fb47571813731a33b1a870c45049fd1b02 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 15 Feb 2017 13:36:03 -0500 Subject: [PATCH 25/42] loosen audio memoization precondition --- assignment-client/src/audio/AudioMixerClientData.cpp | 3 --- assignment-client/src/audio/AudioMixerClientData.h | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 87eb5ee1ab..fe43e6f730 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -548,9 +548,6 @@ AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsi _zone = box; unsigned int oldFrame = _frame.exchange(frame, std::memory_order_release); Q_UNUSED(oldFrame); - - // check the precondition - assert(oldFrame == 0 || frame == (oldFrame + 1)); } } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 1047e10570..8d76cda2f1 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -44,7 +44,7 @@ public: AvatarAudioStream* getAvatarAudioStream(); // returns whether self (this data's node) should ignore node, memoized by frame - // precondition: frame is monotonically increasing after first call + // precondition: frame is increasing after first call (including overflow wrap) bool shouldIgnore(SharedNodePointer self, SharedNodePointer node, unsigned int frame); // the following methods should be called from the AudioMixer assignment thread ONLY @@ -131,7 +131,7 @@ private: // returns an ignore zone, memoized by frame (lockless if the zone is already memoized) // preconditions: - // - frame is monotonically increasing after first call + // - frame is increasing after first call (including overflow wrap) // - there are no references left from calls to getIgnoreZone(frame - 1) IgnoreZone& get(unsigned int frame); From f0e87360c15fff5307517c6fb1ee86757f1fa348 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 15 Feb 2017 12:37:00 -0700 Subject: [PATCH 26/42] don't capture a temporary by reference. Ugh --- libraries/entities/src/EntityEditFilters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 3071072efc..d62495d95e 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -199,7 +199,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { // define the uncaughtException function QScriptEngine& engineRef = *engine; - filterData.uncaughtExceptions = [this, &engineRef, &urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; + filterData.uncaughtExceptions = [this, &engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; // now get the filter function auto global = engine->globalObject(); From ed11edad56a760c811db401cc54325a2b35d61c0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 15 Feb 2017 13:59:04 -0800 Subject: [PATCH 27/42] EventBridge support --- .../resources/qml/hifi/tablet/TabletRoot.qml | 1 + .../resources/qml/hifi/tablet/WindowRoot.qml | 1 + .../src/TabletScriptingInterface.cpp | 83 +++++++++++++------ .../src/TabletScriptingInterface.h | 3 +- libraries/ui/src/QmlWindowClass.h | 6 +- 5 files changed, 65 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 792c8d5082..edc2666c27 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -15,6 +15,7 @@ Item { } function loadSource(url) { + loader.source = ""; // HACK: make sure we load the qml fresh each time. loader.source = url; } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index 14e969b3a6..6fb41512c2 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -25,6 +25,7 @@ Windows.ScrollingWindow { signal showDesktop(); function loadSource(url) { + loader.source = ""; // HACK: make sure we load the qml fresh each time. loader.source = url; } diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index dc6b1d6aa3..3142e27d15 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -11,10 +11,11 @@ #include #include +#include "DependencyManager.h" #include +#include #include #include "ScriptEngineLogging.h" -#include "DependencyManager.h" #include #include #include "SoundEffect.h" @@ -159,6 +160,10 @@ static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; static int s_windowNameCounter = 1; +class TabletRootWindow : public QmlWindowClass { + virtual QString qmlSource() const { return "hifi/tablet/WindowRoot.qml"; } +}; + TabletProxy::TabletProxy(QString name) : _name(name) { } @@ -177,13 +182,16 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // create new desktop window auto offscreenUi = DependencyManager::get(); offscreenUi->executeOnUiThread([=, this] { - InfoView::registerType(); - QString name = _name + QString("%1").arg(s_windowNameCounter++); - offscreenUi->show(QUrl("hifi/tablet/WindowRoot.qml"), name, [&](QQmlContext* context, QObject* newObject) { - QQuickItem* item = dynamic_cast(newObject); - _desktopWindow = item; - QObject::connect(_desktopWindow, SIGNAL(windowClosed), this, SLOT(desktopWindowClosed())); - }); + auto tabletRootWindow = new TabletRootWindow(); + tabletRootWindow->initQml(QVariantMap()); + auto quickItem = tabletRootWindow->asQuickItem(); + _desktopWindow = tabletRootWindow; + QMetaObject::invokeMethod(quickItem, "setShown", Q_ARG(const QVariant&, QVariant(false))); + + QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant))); + + // forward qml surface events to interface js + connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); }); } else { removeButtonsFromToolbar(); @@ -271,7 +279,7 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } if (root) { @@ -294,7 +302,7 @@ void TabletProxy::loadQMLSource(const QVariant& path) { if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } if (root) { @@ -317,8 +325,8 @@ void TabletProxy::gotoHomeScreen() { QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); } else if (_toolbarMode && _desktopWindow) { // close desktop window - if (_desktopWindow) { - QMetaObject::invokeMethod(_desktopWindow, "setShown", Q_ARG(const QVariant&, QVariant(false))); + if (_desktopWindow->asQuickItem()) { + QMetaObject::invokeMethod(_desktopWindow->asQuickItem(), "setShown", Q_ARG(const QVariant&, QVariant(false))); } } _state = State::Home; @@ -336,33 +344,47 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } - if (_state != State::Web) { - if (root) { - QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); - } - _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); - } if (root) { + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } + _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); } QObject* TabletProxy::addButton(const QVariant& properties) { auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); std::lock_guard guard(_mutex); _tabletButtonProxies.push_back(tabletButtonProxy); - if (_qmlTabletRoot) { + if (!_toolbarMode && _qmlTabletRoot) { auto tablet = getQmlTablet(); if (tablet) { addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data()); } else { qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; } + } else if (_toolbarMode) { + + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != toolbarProxy->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + + // copy properties from tablet button proxy to toolbar button proxy. + QObject* toolbarButtonProxy = nullptr; + bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties())); + if (hasResult) { + tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy); + } else { + qCWarning(scriptengine) << "ToolbarProxy addButton has no result"; + } } return tabletButtonProxy.data(); } @@ -381,11 +403,18 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) { auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); if (iter != _tabletButtonProxies.end()) { - if (_qmlTabletRoot) { + if (!_toolbarMode && _qmlTabletRoot) { (*iter)->setQmlButton(nullptr); if (tablet) { QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties())); } + } else if (_toolbarMode) { + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + + // remove button from toolbarProxy + QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString())); + (*iter)->setToolbarButtonProxy(nullptr); } _tabletButtonProxies.erase(iter); } else { @@ -412,14 +441,18 @@ void TabletProxy::updateAudioBar(const double micLevel) { } void TabletProxy::emitScriptEvent(QVariant msg) { - if (_qmlOffscreenSurface) { + if (!_toolbarMode && _qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); + } else if (_toolbarMode && _desktopWindow) { + QMetaObject::invokeMethod(_desktopWindow, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); } } void TabletProxy::sendToQml(QVariant msg) { - if (_qmlOffscreenSurface) { + if (!_toolbarMode && _qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg)); + } else if (_toolbarMode && _desktopWindow) { + QMetaObject::invokeMethod(_desktopWindow, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg)); } } @@ -446,7 +479,7 @@ void TabletProxy::addButtonsToMenuScreen() { if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } if (!root) { diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index c9aee518fc..88e1cca60b 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -26,6 +26,7 @@ class TabletProxy; class TabletButtonProxy; +class QmlWindowClass; /**jsdoc * @namespace Tablet @@ -193,7 +194,7 @@ protected: std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; - QQuickItem* _desktopWindow { nullptr }; + QmlWindowClass* _desktopWindow { nullptr }; bool _toolbarMode { false }; enum class State { Uninitialized, Home, Web, Menu, QML }; diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index a6f59104fd..95777718bf 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -31,6 +31,9 @@ public: QmlWindowClass(); ~QmlWindowClass(); + virtual void initQml(QVariantMap properties); + QQuickItem* asQuickItem() const; + public slots: bool isVisible() const; void setVisible(bool visible); @@ -81,9 +84,6 @@ protected: virtual QString qmlSource() const { return "QmlWindow.qml"; } - virtual void initQml(QVariantMap properties); - QQuickItem* asQuickItem() const; - // FIXME needs to be initialized in the ctor once we have support // for tool window panes in QML bool _toolWindow { false }; From 67302cbd2bd0883584caeffd0d8ba2384bc3d123 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 15 Feb 2017 15:53:08 -0800 Subject: [PATCH 28/42] toolbar work to better support tablet buttons --- .../tablet-icons/empty-toolbar-button.svg | 20 +++++++------ .../resources/qml/hifi/tablet/TabletRoot.qml | 4 +-- .../resources/qml/hifi/tablet/WindowRoot.qml | 8 +++++ .../resources/qml/hifi/toolbars/Toolbar.qml | 29 +++++++++++++++++++ .../qml/hifi/toolbars/ToolbarButton.qml | 12 ++++++-- libraries/script-engine/src/SoundEffect.cpp | 3 -- .../src/TabletScriptingInterface.cpp | 11 ++++++- 7 files changed, 70 insertions(+), 17 deletions(-) diff --git a/interface/resources/icons/tablet-icons/empty-toolbar-button.svg b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg index 5ba101c618..19791e6c29 100644 --- a/interface/resources/icons/tablet-icons/empty-toolbar-button.svg +++ b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg @@ -20,7 +20,7 @@ sodipodi:docname="empty-toolbar-button.svg">image/svg+xml