diff --git a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml new file mode 100644 index 0000000000..bafa518eb9 --- /dev/null +++ b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml @@ -0,0 +1,158 @@ +// +// LetterboxMessage.qml +// qml/hifi +// +// Created by Dante Ruiz on 7/21/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 "../styles-uit" + +Item { + property alias text: popupText.text + property alias headerGlyph: headerGlyph.text + property alias headerText: headerText.text + property alias headerGlyphSize: headerGlyph.size + property real popupRadius: hifi.dimensions.borderRadius + property real headerTextPixelSize: 22 + property real popupTextPixelSize: 16 + property real headerTextMargin: -5 + property real headerGlyphMargin: -15 + property bool isDesktop: false + FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; } + FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; } + visible: false + id: letterbox + anchors.fill: parent + Rectangle { + id: textContainer; + width: parent.width + height: parent.height + anchors.centerIn: parent + radius: popupRadius + color: "white" + Item { + id: contentContainer + width: parent.width - 50 + height: childrenRect.height + anchors.centerIn: parent + Item { + id: popupHeaderContainer + visible: headerText.text !== "" || headerGlyph.text !== "" + height: 30 + // Anchors + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + // Header Glyph + HiFiGlyphs { + id: headerGlyph + visible: headerGlyph.text !== "" + // Size + height: parent.height + // Anchors + anchors.left: parent.left + anchors.leftMargin: headerGlyphMargin + // Text Size + size: headerTextPixelSize*2.5 + // Style + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignVCenter + color: hifi.colors.darkGray + } + // Header Text + Text { + id: headerText + visible: headerText.text !== "" + // Size + + height: parent.height + // Anchors + anchors.left: headerGlyph.right + anchors.leftMargin: headerTextMargin + // Text Size + font.pixelSize: headerTextPixelSize + // Style + font.family: ralewaySemiBold.name + color: hifi.colors.darkGray + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + textFormat: Text.StyledText + } + } + // Popup Text + Text { + id: popupText + // Size + width: parent.width + // Anchors + anchors.top: popupHeaderContainer.visible ? popupHeaderContainer.bottom : parent.top + anchors.topMargin: popupHeaderContainer.visible ? 15 : 0 + anchors.left: parent.left + anchors.right: parent.right + // Text alignment + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHLeft + // Style + font.pixelSize: popupTextPixelSize + font.family: ralewayRegular.name + color: hifi.colors.darkGray + wrapMode: Text.WordWrap + textFormat: Text.StyledText + onLinkActivated: { + Qt.openUrlExternally(link) + } + } + } + } + // Left gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: textContainer.left; + anchors.top: textContainer.top; + anchors.bottom: textContainer.bottom; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } + // Right gray MouseArea + MouseArea { + anchors.left: textContainer.left; + anchors.right: parent.left; + anchors.top: textContainer.top; + anchors.bottom: textContainer.bottom; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } + // Top gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.bottom: textContainer.top; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } + // Bottom gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: textContainer.bottom; + anchors.bottom: parent.bottom; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } +} diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index fa9d7aa6f0..6f41154d4d 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -17,9 +17,12 @@ Item { property alias text: popupText.text property alias headerGlyph: headerGlyph.text property alias headerText: headerText.text + property alias headerGlyphSize: headerGlyph.size property real popupRadius: hifi.dimensions.borderRadius property real headerTextPixelSize: 22 property real popupTextPixelSize: 16 + property real headerTextMargin: -5 + property real headerGlyphMargin: -15 FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; } FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; } visible: false @@ -59,7 +62,7 @@ Item { height: parent.height // Anchors anchors.left: parent.left - anchors.leftMargin: -15 + anchors.leftMargin: headerGlyphMargin // Text Size size: headerTextPixelSize*2.5 // Style @@ -75,7 +78,7 @@ Item { height: parent.height // Anchors anchors.left: headerGlyph.right - anchors.leftMargin: -5 + anchors.leftMargin: headerTextMargin // Text Size font.pixelSize: headerTextPixelSize // Style diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index da9ffdb07e..952cc03733 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -16,6 +16,7 @@ import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" +import "../" ScrollingWindow { id: root @@ -28,10 +29,11 @@ ScrollingWindow { minSize: Qt.vector2d(424, 300) HifiConstants { id: hifi } - + property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter property var runningScriptsModel: ListModel { } + property bool developerMenuEnabled: false property bool isHMD: false Settings { @@ -39,6 +41,28 @@ ScrollingWindow { property alias x: root.x property alias y: root.y } + + Component { + id: letterBoxMessage + Window { + implicitWidth: 400 + implicitHeight: 300 + minSize: Qt.vector2d(424, 300) + DesktopLetterboxMessage { + visible: true + headerGlyph: hifi.glyphs.lock + headerText: "Developer Mode only" + text: ( "In order to edit, delete or reload this script," + + " turn on Developer Mode by going to:" + + " Menu > Settings > Developer Menus") + popupRadius: 0 + headerGlyphSize: 20 + headerTextMargin: 2 + headerGlyphMargin: -3 + } + } + } + Timer { id: refreshTimer @@ -47,6 +71,15 @@ ScrollingWindow { running: false onTriggered: updateRunningScripts(); } + + + Timer { + id: checkMenu + interval: 1000 + repeat: true + running: false + onTriggered: developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + } Component { id: listModelBuilder @@ -64,6 +97,8 @@ ScrollingWindow { Component.onCompleted: { isHMD = HMD.active; updateRunningScripts(); + developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + checkMenu.restart(); } function updateRunningScripts() { @@ -110,7 +145,17 @@ ScrollingWindow { function reloadAll() { console.log("Reload all scripts"); - scripts.reloadAllScripts(); + if (!developerMenuEnabled) { + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url, true); + } + } + } else { + scripts.reloadAllScripts(); + } } function loadDefaults() { @@ -120,7 +165,22 @@ ScrollingWindow { function stopAll() { console.log("Stop all scripts"); - scripts.stopAllScripts(); + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url); + } + } + } + + + function canEditScript(script) { + if ((script === "controllerScripts.js") || (script === "defaultScripts.js")) { + return developerMenuEnabled; + } + + return true; } Column { @@ -146,6 +206,14 @@ ScrollingWindow { color: hifi.buttons.red onClicked: stopAll() } + + HifiControls.Button { + text: "Load Defaults" + color: hifi.buttons.black + height: 26 + visible: root.developerMenuEnabled; + onClicked: loadDefaults() + } } HifiControls.VerticalSpacer { @@ -162,6 +230,7 @@ ScrollingWindow { expandSelectedRow: true itemDelegate: Item { + property bool canEdit: canEditScript(styleData.value); anchors { left: parent ? parent.left : undefined leftMargin: hifi.dimensions.tablePadding @@ -185,8 +254,9 @@ ScrollingWindow { HiFiGlyphs { id: reloadButton - text: hifi.glyphs.reloadSmall + text: ((canEditScript(styleData.value)) ? hifi.glyphs.reload : hifi.glyphs.lock) color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + size: 21 anchors { top: parent.top right: stopButton.left @@ -195,7 +265,13 @@ ScrollingWindow { MouseArea { id: reloadButtonArea anchors { fill: parent; margins: -2 } - onClicked: reloadScript(model.url) + onClicked: { + if (canEdit) { + reloadScript(model.url) + } else { + letterBoxMessage.createObject(desktop) + } + } } } @@ -203,6 +279,7 @@ ScrollingWindow { id: stopButton text: hifi.glyphs.closeSmall color: stopButtonArea.pressed ? hifi.colors.white : parent.color + visible: canEditScript(styleData.value) anchors { top: parent.top right: parent.right @@ -211,7 +288,11 @@ ScrollingWindow { MouseArea { id: stopButtonArea anchors { fill: parent; margins: -2 } - onClicked: stopScript(model.url) + onClicked: { + if (canEdit) { + stopScript(model.url); + } + } } } @@ -264,13 +345,6 @@ ScrollingWindow { height: 26 onClickedQueued: ApplicationInterface.loadDialog() } - - HifiControls.Button { - text: "Load Defaults" - color: hifi.buttons.black - height: 26 - onClicked: loadDefaults() - } } HifiControls.VerticalSpacer {} diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index 11643ae1f1..5677cc92a1 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -16,6 +16,7 @@ import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" +import "../" Rectangle { id: root @@ -26,26 +27,90 @@ Rectangle { property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter property var runningScriptsModel: ListModel { } + property bool developerMenuEnabled: false property bool isHMD: false color: hifi.colors.baseGray + + LetterboxMessage { + id: letterBoxMessage + z: 999 + visible: false + } + + function letterBox(glyph, text, message) { + letterBoxMessage.headerGlyph = glyph; + letterBoxMessage.headerText = text; + letterBoxMessage.text = message; + letterBoxMessage.visible = true; + letterBoxMessage.popupRadius = 0; + letterBoxMessage.headerGlyphSize = 20 + letterBoxMessage.headerTextMargin = 2 + letterBoxMessage.headerGlyphMargin = -3 + } + + Timer { + id: refreshTimer + interval: 100 + repeat: false + running: false + onTriggered: updateRunningScripts(); + } + + Timer { + id: checkMenu + interval: 1000 + repeat: true + running: false + onTriggered: developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + } + + Component { + id: listModelBuilder + ListModel {} + } + Connections { target: ScriptDiscoveryService - onScriptCountChanged: updateRunningScripts(); + onScriptCountChanged: { + runningScriptsModel = listModelBuilder.createObject(root); + refreshTimer.restart(); + } } Component.onCompleted: { isHMD = HMD.active; updateRunningScripts(); + developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + checkMenu.restart(); } function updateRunningScripts() { - var runningScripts = ScriptDiscoveryService.getRunning(); - runningScriptsModel.clear() - for (var i = 0; i < runningScripts.length; ++i) { - runningScriptsModel.append(runningScripts[i]); + function simplify(path) { + // trim URI querystring/fragment + path = (path+'').replace(/[#?].*$/,''); + // normalize separators and grab last path segment (ie: just the filename) + path = path.replace(/\\/g, '/').split('/').pop(); + // return lowercased because we want to sort mnemonically + return path.toLowerCase(); } + var runningScripts = ScriptDiscoveryService.getRunning(); + runningScripts.sort(function(a,b) { + a = simplify(a.path); + b = simplify(b.path); + return a < b ? -1 : a > b ? 1 : 0; + }); + // Calling `runningScriptsModel.clear()` here instead of creating a new object + // triggers some kind of weird heap corruption deep inside Qt. So instead of + // modifying the model in place, possibly triggering behaviors in the table + // instead we create a new `ListModel`, populate it and update the + // existing model atomically. + var newRunningScriptsModel = listModelBuilder.createObject(root); + for (var i = 0; i < runningScripts.length; ++i) { + newRunningScriptsModel.append(runningScripts[i]); + } + runningScriptsModel = newRunningScriptsModel; } function loadScript(script) { @@ -65,7 +130,17 @@ Rectangle { function reloadAll() { console.log("Reload all scripts"); - scripts.reloadAllScripts(); + if (!developerMenuEnabled) { + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url, true); + } + } + } else { + scripts.reloadAllScripts(); + } } function loadDefaults() { @@ -75,7 +150,22 @@ Rectangle { function stopAll() { console.log("Stop all scripts"); - scripts.stopAllScripts(); + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + console.log(url); + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url); + } + } + } + + function canEditScript(script) { + if ((script === "controllerScripts.js") || (script === "defaultScripts.js")) { + return developerMenuEnabled; + } + + return true; } Flickable { @@ -110,6 +200,14 @@ Rectangle { color: hifi.buttons.red onClicked: stopAll() } + + HifiControls.Button { + text: "Load Defaults" + color: hifi.buttons.black + height: 26 + visible: root.developerMenuEnabled; + onClicked: loadDefaults() + } } HifiControls.VerticalSpacer { @@ -125,6 +223,7 @@ Rectangle { expandSelectedRow: true itemDelegate: Item { + property bool canEdit: canEditScript(styleData.value); anchors { left: parent ? parent.left : undefined leftMargin: hifi.dimensions.tablePadding @@ -148,8 +247,9 @@ Rectangle { HiFiGlyphs { id: reloadButton - text: hifi.glyphs.reloadSmall + text: ((canEditScript(styleData.value)) ? hifi.glyphs.reload : hifi.glyphs.lock) color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + size: 21 anchors { top: parent.top right: stopButton.left @@ -158,7 +258,17 @@ Rectangle { MouseArea { id: reloadButtonArea anchors { fill: parent; margins: -2 } - onClicked: reloadScript(model.url) + onClicked: { + if (canEdit) { + reloadScript(model.url) + } else { + letterBox(hifi.glyphs.lock, + "Developer Mode only", + "In order to edit, delete or reload this script," + + " turn on Developer Mode by going to:" + + " Menu > Settings > Developer Menus"); + } + } } } @@ -166,6 +276,7 @@ Rectangle { id: stopButton text: hifi.glyphs.closeSmall color: stopButtonArea.pressed ? hifi.colors.white : parent.color + visible: canEditScript(styleData.value) anchors { top: parent.top right: parent.right @@ -174,7 +285,11 @@ Rectangle { MouseArea { id: stopButtonArea anchors { fill: parent; margins: -2 } - onClicked: stopScript(model.url) + onClicked: { + if (canEdit) { + stopScript(model.url) + } + } } } @@ -250,13 +365,6 @@ Rectangle { onTriggered: ApplicationInterface.loadDialog(); } } - - HifiControls.Button { - text: "Load Defaults" - color: hifi.buttons.black - height: 26 - onClicked: loadDefaults() - } } HifiControls.VerticalSpacer {} diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 1556a9c0c0..4a26d11128 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -337,5 +337,6 @@ Item { readonly property string playback_play: "\ue01d" readonly property string stop_square: "\ue01e" readonly property string avatarTPose: "\ue01f" + readonly property string lock: "\ue006" } } diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 8e58b648e6..40af679a13 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -38,6 +38,7 @@ #include "scripting/AccountScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" +#include "scripting/MenuScriptingInterface.h" #include #include #include "FileDialogHelper.h" @@ -191,6 +192,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); + _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");