diff --git a/examples/dialogExample.js b/examples/dialogExample.js new file mode 100644 index 0000000000..144b681950 --- /dev/null +++ b/examples/dialogExample.js @@ -0,0 +1,7 @@ +Window.alert("This is an alert box"); + +var confirmed = Window.confirm("This is a confirmation dialog") +Window.alert("Your response was: " + confirmed); + +var prompt = Window.prompt("This is a prompt dialog", "This is the default text"); +Window.alert("Your response was: " + prompt); diff --git a/examples/locationExample.js b/examples/locationExample.js new file mode 100644 index 0000000000..7530a3e3b6 --- /dev/null +++ b/examples/locationExample.js @@ -0,0 +1,11 @@ +var goto = Window.prompt("Where would you like to go? (ex. @username, #location, 1.0,500.3,100.2)"); +var url = "hifi://" + goto; +print("Going to: " + url); +location = url; + +// If the destination location is a user or named location the new location may not be set by the time execution reaches here +// because it requires an asynchronous lookup. Coordinate changes should be be reflected immediately, though. (ex: hifi://0,0,0) +print("URL: " + location.href); +print("Protocol: " + location.protocol); +print("Hostname: " + location.hostname); +print("Pathname: " + location.pathname); diff --git a/examples/windowExample.js b/examples/windowExample.js new file mode 100644 index 0000000000..afde83591d --- /dev/null +++ b/examples/windowExample.js @@ -0,0 +1,12 @@ +var width = 0, + height = 0; + +function onUpdate(dt) { + if (width != Window.innerWidth || height != Window.innerHeight) { + width = Window.innerWidth; + height = Window.innerHeight; + print("New window dimensions: " + width + ", " + height); + } +} + +Script.update.connect(onUpdate); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a2c10c02cd..6f101c062c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -76,6 +76,8 @@ #include "scripting/ClipboardScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" +#include "scripting/WindowScriptingInterface.h" +#include "scripting/LocationScriptingInterface.h" #include "ui/InfoView.h" #include "ui/Snapshot.h" @@ -3404,6 +3406,15 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater())); scriptEngine->registerGlobalObject("Overlays", &_overlays); + + QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); + scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, + LocationScriptingInterface::locationSetter, windowValue); + + // register `location` on the global object. + scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, + LocationScriptingInterface::locationSetter); + scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e194734928..7ab2639065 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -898,44 +898,53 @@ void Menu::goTo() { int dialogReturn = gotoDialog.exec(); if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { QString desiredDestination = gotoDialog.textValue(); - - if (desiredDestination.startsWith(CUSTOM_URL_SCHEME + "//")) { - QStringList urlParts = desiredDestination.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); - - if (urlParts.count() == 1) { - // location coordinates or place name - QString domain = urlParts[0]; - goToDomain(domain); - } - else if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - QString domain = urlParts[0]; - - // second part is either a destination coordinate or - // a place name - QString destination = urlParts[1]; - - // any third part is an avatar orientation. - QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); - - goToDomain(domain); - - // goto either @user, #place, or x-xx,y-yy,z-zz - // style co-ordinate. - goTo(destination); - - if (!orientation.isEmpty()) { - // location orientation - goToOrientation(orientation); - } - } - } else { - goToUser(gotoDialog.textValue()); + if (!goToURL(desiredDestination)) {; + goTo(desiredDestination); } } sendFakeEnterEvent(); } +bool Menu::goToURL(QString location) { + if (location.startsWith(CUSTOM_URL_SCHEME + "//")) { + QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + + if (urlParts.count() > 1) { + // if url has 2 or more parts, the first one is domain name + QString domain = urlParts[0]; + + // second part is either a destination coordinate or + // a place name + QString destination = urlParts[1]; + + // any third part is an avatar orientation. + QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); + + goToDomain(domain); + + // goto either @user, #place, or x-xx,y-yy,z-zz + // style co-ordinate. + goTo(destination); + + if (!orientation.isEmpty()) { + // location orientation + goToOrientation(orientation); + } + } else if (urlParts.count() == 1) { + QString destination = urlParts[0]; + + // If this starts with # or @, treat it as a user/location, otherwise treat it as a domain + if (destination[0] == '#' || destination[0] == '@') { + goTo(destination); + } else { + goToDomain(destination); + } + } + return true; + } + return false; +} + void Menu::goToUser(const QString& user) { LocationManager* manager = &LocationManager::getInstance(); manager->goTo(user); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 967312dca9..ef3e7bd6ce 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -152,6 +152,7 @@ public slots: void importSettings(); void exportSettings(); void goTo(); + bool goToURL(QString location); void goToUser(const QString& user); void pasteToVoxel(); void openUrl(const QUrl& url); diff --git a/interface/src/scripting/LocationScriptingInterface.cpp b/interface/src/scripting/LocationScriptingInterface.cpp new file mode 100644 index 0000000000..44ff94aa1f --- /dev/null +++ b/interface/src/scripting/LocationScriptingInterface.cpp @@ -0,0 +1,49 @@ +// +// LocationScriptingInterface.cpp +// interface/src/scripting +// +// Created by Ryan Huffman on 4/29/14. +// Copyright 2014 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 "NodeList.h" + +#include "LocationScriptingInterface.h" + +LocationScriptingInterface* LocationScriptingInterface::getInstance() { + static LocationScriptingInterface sharedInstance; + return &sharedInstance; +} + +QString LocationScriptingInterface::getHref() { + return getProtocol() + "//" + getHostname() + getPathname(); +} + +QString LocationScriptingInterface::getPathname() { + const glm::vec3& position = Application::getInstance()->getAvatar()->getPosition(); + QString path; + path.sprintf("/%.4f,%.4f,%.4f", position.x, position.y, position.z); + return path; +} + +QString LocationScriptingInterface::getHostname() { + return NodeList::getInstance()->getDomainHandler().getHostname(); +} + +void LocationScriptingInterface::assign(const QString& url) { + QMetaObject::invokeMethod(Menu::getInstance(), "goToURL", Q_ARG(const QString&, url)); +} + +QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) { + return engine->newQObject(getInstance()); +} + +QScriptValue LocationScriptingInterface::locationSetter(QScriptContext* context, QScriptEngine* engine) { + LocationScriptingInterface::getInstance()->assign(context->argument(0).toString()); + return QScriptValue::UndefinedValue; +} diff --git a/interface/src/scripting/LocationScriptingInterface.h b/interface/src/scripting/LocationScriptingInterface.h new file mode 100644 index 0000000000..36b6d97561 --- /dev/null +++ b/interface/src/scripting/LocationScriptingInterface.h @@ -0,0 +1,46 @@ +// +// LocationScriptingInterface.h +// interface/src/scripting +// +// Created by Ryan Huffman on 4/29/14. +// Copyright 2014 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_LocationScriptingInterface_h +#define hifi_LocationScriptingInterface_h + +#include +#include +#include +#include +#include + +#include "Application.h" + +class LocationScriptingInterface : public QObject { + Q_OBJECT + Q_PROPERTY(QString href READ getHref) + Q_PROPERTY(QString protocol READ getProtocol) + Q_PROPERTY(QString hostname READ getHostname) + Q_PROPERTY(QString pathname READ getPathname) + LocationScriptingInterface() { }; +public: + static LocationScriptingInterface* getInstance(); + + QString getHref(); + QString getProtocol() { return CUSTOM_URL_SCHEME; }; + QString getPathname(); + QString getHostname(); + + static QScriptValue locationGetter(QScriptContext* context, QScriptEngine* engine); + static QScriptValue locationSetter(QScriptContext* context, QScriptEngine* engine); + +public slots: + void assign(const QString& url); + +}; + +#endif // hifi_LocationScriptingInterface_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp new file mode 100644 index 0000000000..3366e28f6e --- /dev/null +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -0,0 +1,85 @@ +// +// WindowScriptingInterface.cpp +// interface/src/scripting +// +// Created by Ryan Huffman on 4/29/14. +// Copyright 2014 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 "Application.h" +#include "Menu.h" + +#include "WindowScriptingInterface.h" + +WindowScriptingInterface* WindowScriptingInterface::getInstance() { + static WindowScriptingInterface sharedInstance; + return &sharedInstance; +} + +QScriptValue WindowScriptingInterface::alert(const QString& message) { + QScriptValue retVal; + QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message)); + return retVal; +} + +QScriptValue WindowScriptingInterface::confirm(const QString& message) { + QScriptValue retVal; + QMetaObject::invokeMethod(this, "showConfirm", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message)); + return retVal; +} + +QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) { + QScriptValue retVal; + QMetaObject::invokeMethod(this, "showPrompt", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QScriptValue, retVal), + Q_ARG(const QString&, message), Q_ARG(const QString&, defaultText)); + return retVal; +} + +/// Display an alert box +/// \param const QString& message message to display +/// \return QScriptValue::UndefinedValue +QScriptValue WindowScriptingInterface::showAlert(const QString& message) { + QMessageBox::warning(Application::getInstance()->getWindow(), "", message); + return QScriptValue::UndefinedValue; +} + +/// Display a confirmation box with the options 'Yes' and 'No' +/// \param const QString& message message to display +/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise +QScriptValue WindowScriptingInterface::showConfirm(const QString& message) { + QMessageBox::StandardButton response = QMessageBox::question(Application::getInstance()->getWindow(), "", message); + return QScriptValue(response == QMessageBox::Yes); +} + +/// Display a prompt with a text box +/// \param const QString& message message to display +/// \param const QString& defaultText default text in the text box +/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise. +QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const QString& defaultText) { + QInputDialog promptDialog(Application::getInstance()->getWindow()); + promptDialog.setWindowTitle(""); + promptDialog.setLabelText(message); + promptDialog.setTextValue(defaultText); + + if (promptDialog.exec() == QDialog::Accepted) { + return QScriptValue(promptDialog.textValue()); + } + + return QScriptValue::NullValue; +} + +int WindowScriptingInterface::getInnerWidth() { + return Application::getInstance()->getWindow()->geometry().width(); +} + +int WindowScriptingInterface::getInnerHeight() { + return Application::getInstance()->getWindow()->geometry().height(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h new file mode 100644 index 0000000000..af9b15a96d --- /dev/null +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -0,0 +1,40 @@ +// +// WindowScriptingInterface.cpp +// interface/src/scripting +// +// Created by Ryan Huffman on 4/29/14. +// Copyright 2014 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_WindowScriptingInterface_h +#define hifi_WindowScriptingInterface_h + +#include +#include +#include + +class WindowScriptingInterface : public QObject { + Q_OBJECT + Q_PROPERTY(int innerWidth READ getInnerWidth) + Q_PROPERTY(int innerHeight READ getInnerHeight) + WindowScriptingInterface() { }; +public: + static WindowScriptingInterface* getInstance(); + int getInnerWidth(); + int getInnerHeight(); + +public slots: + QScriptValue alert(const QString& message = ""); + QScriptValue confirm(const QString& message = ""); + QScriptValue prompt(const QString& message = "", const QString& defaultText = ""); + +private slots: + QScriptValue showAlert(const QString& message); + QScriptValue showConfirm(const QString& message); + QScriptValue showPrompt(const QString& message, const QString& defaultText); +}; + +#endif // hifi_WindowScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a31e6fcddc..82209b1f5b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -247,10 +248,26 @@ void ScriptEngine::init() { _particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); } -void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { +QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { if (object) { QScriptValue value = _engine.newQObject(object); _engine.globalObject().setProperty(name, value); + return value; + } + return QScriptValue::NullValue; +} + +void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, QScriptValue object) { + QScriptValue setterFunction = _engine.newFunction(setter, 1); + QScriptValue getterFunction = _engine.newFunction(getter); + + if (!object.isNull()) { + object.setProperty(name, setterFunction, QScriptValue::PropertySetter); + object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); + } else { + _engine.globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter); + _engine.globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter); } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 9ea99276d3..61c3c559c5 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -58,7 +58,9 @@ public: const QString& getScriptName() const { return _scriptName; } void cleanupMenuItems(); - void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name + QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name + void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue); Q_INVOKABLE void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } @@ -136,6 +138,7 @@ private: Vec3 _vec3Library; ScriptUUID _uuidLibrary; AnimationCache _animationCache; + }; #endif // hifi_ScriptEngine_h