From dae6d6fb698f212543af7799550d8a8899a87185 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Apr 2017 11:43:42 +1200 Subject: [PATCH] Stub desktop dialog to select recording to play from ATP assets --- interface/resources/qml/desktop/Desktop.qml | 5 + .../resources/qml/dialogs/AssetDialog.qml | 137 ++++++++++++++++++ .../qml/dialogs/TabletAssetDialog.qml | 16 ++ .../resources/qml/hifi/tablet/TabletRoot.qml | 6 + .../scripting/WindowScriptingInterface.cpp | 29 ++++ .../src/scripting/WindowScriptingInterface.h | 4 + libraries/ui/src/OffscreenUi.cpp | 80 ++++++++++ libraries/ui/src/OffscreenUi.h | 5 + scripts/system/record.js | 11 +- 9 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 interface/resources/qml/dialogs/AssetDialog.qml create mode 100644 interface/resources/qml/dialogs/TabletAssetDialog.qml diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index d8aedf6666..42db16aa72 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -466,6 +466,11 @@ FocusScope { return fileDialogBuilder.createObject(desktop, properties); } + Component { id: assetDialogBuilder; AssetDialog { } } + function assetDialog(properties) { + return assetDialogBuilder.createObject(desktop, properties); + } + function unfocusWindows() { // First find the active focus item, and unfocus it, all the way // up the parent chain to the window diff --git a/interface/resources/qml/dialogs/AssetDialog.qml b/interface/resources/qml/dialogs/AssetDialog.qml new file mode 100644 index 0000000000..6b83f6670b --- /dev/null +++ b/interface/resources/qml/dialogs/AssetDialog.qml @@ -0,0 +1,137 @@ +// +// AssetDialog.qml +// +// Created by David Rowe on 18 Apr 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../controls-uit" +import "../styles-uit" +import "../windows" + +ModalWindow { + id: root + resizable: true + implicitWidth: 480 + implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0) + + minSize: Qt.vector2d(360, 240) + draggable: true + + HifiConstants { id: hifi } + + Settings { + category: "FileDialog" + property alias width: root.width + property alias height: root.height + property alias x: root.x + property alias y: root.y + } + + // Set from OffscreenUi::assetDialog() + //property alias caption: root.title; + property string caption + //property alias dir: fileTableModel.folder; + property string dir + //property alias filter: selectionType.filtersString; + property string filter + property int options // Unused + + // TODO: Other properties + + signal selectedAsset(var asset); + signal canceled(); + + Component.onCompleted: { + // TODO: Required? + } + + Item { + id: assetDialogItem + clip: true + width: pane.width + height: pane.height + anchors.margins: 0 + + Action { + id: okAction + // TODO + /* + text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open" + enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false + onTriggered: { + if (!root.selectDirectory && !d.currentSelectionIsFolder + || root.selectDirectory && fileTableView.currentRow === -1) { + okActionTimer.start(); + } else { + fileTableView.navigateToCurrentRow(); + } + } + */ + text: "Choose" + + onTriggered: { + selectedAsset("/recordings/20170417-233012.hfr"); + root.destroy(); // TODO: root.shown = false? + } + } + + Action { + id: cancelAction + text: "Cancel" + onTriggered: { + canceled(); + root.shown = false; + } + } + + Row { + id: buttonRow + anchors { + right: parent.right + bottom: parent.bottom + } + spacing: hifi.dimensions.contentSpacing.y + + Button { + id: openButton + color: hifi.buttons.blue + action: okAction + Keys.onReturnPressed: okAction.trigger() + KeyNavigation.up: selectionType + KeyNavigation.left: selectionType + KeyNavigation.right: cancelButton + } + + Button { + id: cancelButton + action: cancelAction + KeyNavigation.up: selectionType + KeyNavigation.left: openButton + KeyNavigation.right: fileTableView.contentItem + Keys.onReturnPressed: { canceled(); root.enabled = false } + } + } + + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Backspace: + event.accepted = d.navigateUp(); + break; + + case Qt.Key_Home: + event.accepted = d.navigateHome(); + break; + + } + } +} diff --git a/interface/resources/qml/dialogs/TabletAssetDialog.qml b/interface/resources/qml/dialogs/TabletAssetDialog.qml new file mode 100644 index 0000000000..04b2b0414a --- /dev/null +++ b/interface/resources/qml/dialogs/TabletAssetDialog.qml @@ -0,0 +1,16 @@ +// +// TabletAssetDialog.qml +// +// Created by David Rowe on 18 Apr 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +AssetDialog { + +} diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 446d4c91ff..31e6174563 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -44,6 +44,12 @@ Item { return openModal; } + Component { id: assetDialogBuilder; TabletAssetDialog { } } + function assetDialog(properties) { + openModal = assetDialogBuilder.createObject(tabletRoot, properties); + return openModal; + } + function setMenuProperties(rootMenu, subMenu) { tabletRoot.rootMenu = rootMenu; tabletRoot.subMenu = subMenu; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 39c2f2e402..13ffd2f78d 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -28,6 +28,7 @@ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation"; +static const QString LAST_BROWSE_ASSETS_LOCATION_SETTING = "LastBrowseAssetsLocation"; QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result) { @@ -149,6 +150,15 @@ void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location Setting::Handle(LAST_BROWSE_LOCATION_SETTING).set(location); } +QString WindowScriptingInterface::getPreviousBrowseAssetLocation() const { + QString ASSETS_ROOT_PATH = "/"; + return Setting::Handle(LAST_BROWSE_ASSETS_LOCATION_SETTING, ASSETS_ROOT_PATH).get(); +} + +void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& location) { + Setting::Handle(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location); +} + /// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and /// might be in same thread as a script that sets the reticle to invisible void WindowScriptingInterface::ensureReticleVisible() const { @@ -202,6 +212,25 @@ QScriptValue WindowScriptingInterface::save(const QString& title, const QString& return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); } +/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid +/// directory the browser will start at the root directory. +/// \param const QString& title title of the window +/// \param const QString& directory directory to start the asset browser at +/// \param const QString& nameFilter filter to filter asset names by - see `QFileDialog` +/// \return QScriptValue asset path as a string if one was selected, otherwise `QScriptValue::NullValue` +QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const QString& directory, const QString& nameFilter) { + ensureReticleVisible(); + QString path = directory; + if (path.isEmpty()) { + path = getPreviousBrowseAssetLocation(); + } + QString result = OffscreenUi::getOpenAssetName(nullptr, title, path, nameFilter); + if (!result.isEmpty()) { + setPreviousBrowseAssetLocation(QFileInfo(result).absolutePath()); + } + return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); +} + void WindowScriptingInterface::showAssetServer(const QString& upload) { QMetaObject::invokeMethod(qApp, "showAssetServerWidget", Qt::QueuedConnection, Q_ARG(QString, upload)); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index d4ff278fea..6934dea0af 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -53,6 +53,7 @@ public slots: CustomPromptResult customPrompt(const QVariant& config); QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); void showAssetServer(const QString& upload = ""); void copyToClipboard(const QString& text); void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); @@ -88,6 +89,9 @@ private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); + QString getPreviousBrowseAssetLocation() const; + void setPreviousBrowseAssetLocation(const QString& location); + void ensureReticleVisible() const; int createMessageBox(QString title, QString text, int buttons, int defaultButton); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 1cb9045e79..3cec37b7aa 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -713,6 +713,86 @@ QString OffscreenUi::getExistingDirectory(void* ignored, const QString &caption, return DependencyManager::get()->existingDirectoryDialog(caption, dir, filter, selectedFilter, options); } +class AssetDialogListener : public ModalDialogListener { + // ATP equivalent of FileDialogListener. + Q_OBJECT + + friend class OffscreenUi; + AssetDialogListener(QQuickItem* messageBox) : ModalDialogListener(messageBox) { + if (_finished) { + return; + } + connect(_dialog, SIGNAL(selectedAsset(QVariant)), this, SLOT(onSelectedAsset(QVariant))); + } + + private slots: + void onSelectedAsset(QVariant asset) { + _result = asset; + _finished = true; + disconnect(_dialog); + } +}; + + +QString OffscreenUi::assetDialog(const QVariantMap& properties) { + // ATP equivalent of fileDialog(). + QVariant buildDialogResult; + bool invokeResult; + auto tabletScriptingInterface = DependencyManager::get(); + TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + if (tablet->getToolbarMode()) { + invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog", + Q_RETURN_ARG(QVariant, buildDialogResult), + Q_ARG(QVariant, QVariant::fromValue(properties))); + } else { + QQuickItem* tabletRoot = tablet->getTabletRoot(); + invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog", + Q_RETURN_ARG(QVariant, buildDialogResult), + Q_ARG(QVariant, QVariant::fromValue(properties))); + emit tabletScriptingInterface->tabletNotification(); + } + + if (!invokeResult) { + qWarning() << "Failed to create asset open dialog"; + return QString(); + } + + QVariant result = AssetDialogListener(qvariant_cast(buildDialogResult)).waitForResult(); + if (!result.isValid()) { + return QString(); + } + qCDebug(uiLogging) << result.toString(); + return result.toUrl().toString(); +} + +QString OffscreenUi::assetOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) { + // ATP equivalent of fileOpenDialog(). + if (QThread::currentThread() != thread()) { + QString result; + QMetaObject::invokeMethod(this, "assetOpenDialog", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QString, result), + Q_ARG(QString, caption), + Q_ARG(QString, dir), + Q_ARG(QString, filter), + Q_ARG(QString*, selectedFilter), + Q_ARG(QFileDialog::Options, options)); + return result; + } + + // FIXME support returning the selected filter... somehow? + QVariantMap map; + map.insert("caption", caption); + map.insert("dir", QUrl::fromLocalFile(dir)); + map.insert("filter", filter); + map.insert("options", static_cast(options)); + return assetDialog(map); +} + +QString OffscreenUi::getOpenAssetName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { + // ATP equivalent of getOpenFileName(). + return DependencyManager::get()->assetOpenDialog(caption, dir, filter, selectedFilter, options); +} + bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { if (!filterEnabled(originalDestination, event)) { return false; diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 5813d0bfd2..55fb8b2c3d 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -118,6 +118,8 @@ public: Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); + Q_INVOKABLE QString assetOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); + // Compatibility with QFileDialog::getOpenFileName static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); // Compatibility with QFileDialog::getSaveFileName @@ -125,6 +127,8 @@ public: // Compatibility with QFileDialog::getExistingDirectory static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); + static QString getOpenAssetName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); + Q_INVOKABLE QVariant inputDialog(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant()); Q_INVOKABLE QVariant customInputDialog(const Icon icon, const QString& title, const QVariantMap& config); QQuickItem* createInputDialog(const Icon icon, const QString& title, const QString& label, const QVariant& current); @@ -155,6 +159,7 @@ signals: private: QString fileDialog(const QVariantMap& properties); + QString assetDialog(const QVariantMap& properties); QQuickItem* _desktop { nullptr }; QQuickItem* _toolWindow { nullptr }; diff --git a/scripts/system/record.js b/scripts/system/record.js index 20d69cc35a..6790ca7007 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -458,7 +458,10 @@ } function onWebEventReceived(data) { - var message = JSON.parse(data); + var message, + recording; + + message = JSON.parse(data); if (message.type === EVENT_BRIDGE_TYPE) { switch (message.action) { case BODY_LOADED_ACTION: @@ -486,7 +489,11 @@ break; case LOAD_RECORDING_ACTION: // User wants to select an ATP recording to play. - log("TODO: Open dialog for user to select ATP recording to play"); + recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hrf"); + if (recording) { + log("Load recording " + recording); + Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation); + } break; case START_RECORDING_ACTION: // Start making a recording.