From 73909837218f17d78b37f94d294b99c2a11bbf49 Mon Sep 17 00:00:00 2001
From: Thijs Wenker <me@thoys.nl>
Date: Tue, 3 Jul 2018 16:49:49 +0200
Subject: [PATCH] introduce scriptable presentationMode instead of automatic
 mode switching windows

---
 interface/resources/qml/InteractiveWindow.qml | 191 ++++++++----------
 interface/src/Application.cpp                 |   3 +-
 .../scripting/DesktopScriptingInterface.cpp   |   8 +
 .../src/scripting/DesktopScriptingInterface.h |  23 ++-
 libraries/ui/src/InteractiveWindow.cpp        |  56 ++++-
 libraries/ui/src/InteractiveWindow.h          |  77 +++++--
 6 files changed, 219 insertions(+), 139 deletions(-)

diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml
index 92c60c653b..d1e9284101 100644
--- a/interface/resources/qml/InteractiveWindow.qml
+++ b/interface/resources/qml/InteractiveWindow.qml
@@ -9,7 +9,6 @@
 //
 
 import QtQuick 2.3
-import InteractiveWindowFlags 1.0
 
 import "windows" as Windows
 import "controls"
@@ -47,33 +46,9 @@ Windows.Window {
     property bool keyboardRaised: false;
     property bool punctuationMode: false;
 
-    readonly property int modeNotSet: 0;
-    readonly property int modeNative: 1;
-    readonly property int modeVirtual: 2;
-
-    property int windowMode: modeNotSet;
-
-    property bool forceNative: false;
-    property bool forceVirtual: false;
-
-    property string windowModeText: getModeString();
-
-    function getModeString() {
-        switch (windowMode) {
-            case modeNotSet:
-                return "none";
-            case modeNative:
-                return "native";
-            case modeVirtual:
-                return "virtual";
-        }
-        return "unknown";
-    }
-
-    onWindowModeChanged: {
-        windowModeText = getModeString();
-    }
+    property int presentationMode: 0;
 
+    property var initialized: false;
     onSourceChanged: {
         if (dynamicContent) {
             dynamicContent.destroy();
@@ -88,117 +63,108 @@ Windows.Window {
     }
 
     function updateInteractiveWindowPositionForMode() {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             x = interactiveWindowPosition.x;
             y = interactiveWindowPosition.y;
-        } else if (windowMode === modeVirtual && nativeWindow) {
+        } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
             nativeWindow.x = interactiveWindowPosition.x;
             nativeWindow.y = interactiveWindowPosition.y;
         }
     }
 
     function updateInteractiveWindowSizeForMode() {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             width = interactiveWindowSize.width;
             height = interactiveWindowSize.height;
-        } else if (windowMode === modeVirtual && nativeWindow) {
+        } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
             nativeWindow.width = interactiveWindowSize.width;
-            nativeWindow.height = interactiveWindowSize.heigth;
+            nativeWindow.height = interactiveWindowSize.height;
         }
     }
 
-    function trySwitchWindowMode() {
-        if (windowMode !== modeVirtual && (HMD.active || (forceVirtual && !forceNative))) {
-            windowMode = modeVirtual;
+    function setupPresentationMode() {
+        console.warn(presentationMode);
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             if (nativeWindow) {
                 nativeWindow.setVisible(false);
             }
             contentHolder.parent = root;
             updateInteractiveWindowPositionForMode();
             shown = interactiveWindowVisible;
-        } else if (windowMode !== modeNative && (!HMD.active || (forceNative && !forceVirtual))) {
-            windowMode = modeNative;
+        } else if (presentationMode === Desktop.PresentationMode.NATIVE) {
             shown = false;
             if (nativeWindow) {
                 contentHolder.parent = nativeWindow.contentItem;
                 nativeWindow.setVisible(interactiveWindowVisible);
                 updateInteractiveWindowPositionForMode();
             }
-        } else if (windowMode === modeNotSet) {
-            console.error("windowMode should be set.");
+        } else if (presentationMode === modeNotSet) {
+            console.error("presentationMode should be set.");
         }
     }
-
-    function displayModeChanged(isHMD) {
-        trySwitchWindowMode();
-    }
     
     Component.onCompleted: {
-        HMD.displayModeChanged.connect(displayModeChanged);
-
-        forceVirtual = (flags & InteractiveWindowFlags.ForceVirtual) === InteractiveWindowFlags.ForceVirtual;
-        forceNative = (flags & InteractiveWindowFlags.ForceNative) === InteractiveWindowFlags.ForceNative;
 
         x = interactiveWindowPosition.x;
         y = interactiveWindowPosition.y;
         width = interactiveWindowSize.width;
         height = interactiveWindowSize.height;
 
-        if (!forceVirtual || (forceVirtual && forceNative)) {
-            nativeWindow = Qt.createQmlObject('
-                import QtQuick 2.3;
-                import QtQuick.Window 2.3;
+        nativeWindow = Qt.createQmlObject('
+            import QtQuick 2.3;
+            import QtQuick.Window 2.3;
 
-                Window {
-                    id: root;
-                    Rectangle {
-                        color: hifi.colors.baseGray
-                        anchors.fill: parent
-                    }
-                }', root, 'InteractiveWindow.qml->nativeWindow');
-            nativeWindow.title = root.title;
-            var nativeWindowFlags = Qt.Window |
-                Qt.WindowTitleHint |
-                Qt.WindowSystemMenuHint |
-                Qt.WindowCloseButtonHint |
-                Qt.WindowMaximizeButtonHint |
-                Qt.WindowMinimizeButtonHint;
-            if ((flags & InteractiveWindowFlags.AlwaysOnTop) === InteractiveWindowFlags.AlwaysOnTop) {
-                nativeWindowFlags |= Qt.WindowStaysOnTopHint;
-            }
-            nativeWindow.flags = nativeWindowFlags;
-
-            nativeWindow.x = interactiveWindowPosition.x;
-            nativeWindow.y = interactiveWindowPosition.y;
-
-            nativeWindow.width = interactiveWindowSize.width;
-            nativeWindow.height = interactiveWindowSize.height;
-
-            nativeWindow.xChanged.connect(function() {
-                if (windowMode === modeNative && nativeWindow.visible) {
-                    interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
+            Window {
+                id: root;
+                Rectangle {
+                    color: hifi.colors.baseGray
+                    anchors.fill: parent
                 }
-            });
-            nativeWindow.yChanged.connect(function() {
-                if (windowMode === modeNative && nativeWindow.visible) {
-                    interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
-                }
-            });
-
-            nativeWindow.widthChanged.connect(function() {
-                if (windowMode === modeNative && nativeWindow.visible) {
-                    interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
-                }
-            });
-            nativeWindow.heightChanged.connect(function() {
-                if (windowMode === modeNative && nativeWindow.visible) {
-                    interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
-                }
-            });
+            }', root, 'InteractiveWindow.qml->nativeWindow');
+        nativeWindow.title = root.title;
+        var nativeWindowFlags = Qt.Window |
+            Qt.WindowTitleHint |
+            Qt.WindowSystemMenuHint |
+            Qt.WindowCloseButtonHint |
+            Qt.WindowMaximizeButtonHint |
+            Qt.WindowMinimizeButtonHint;
+        if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) {
+            nativeWindowFlags |= Qt.WindowStaysOnTopHint;
         }
+        nativeWindow.flags = nativeWindowFlags;
+
+        nativeWindow.x = interactiveWindowPosition.x;
+        nativeWindow.y = interactiveWindowPosition.y;
+
+        nativeWindow.width = interactiveWindowSize.width;
+        nativeWindow.height = interactiveWindowSize.height;
+
+        nativeWindow.xChanged.connect(function() {
+            if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+                interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
+            }
+        });
+        nativeWindow.yChanged.connect(function() {
+            if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+                interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
+            }
+        });
+
+        nativeWindow.widthChanged.connect(function() {
+            if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+                interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
+            }
+        });
+        nativeWindow.heightChanged.connect(function() {
+            if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+                interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
+            }
+        });
 
         // finally set the initial window mode:
-        trySwitchWindowMode();
+        setupPresentationMode();
+
+        initialized = true;
     }
 
     // Handle message traffic from the script that launched us to the loaded QML
@@ -214,9 +180,9 @@ Windows.Window {
     }
 
     function raiseWindow() {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             raise();
-        } else if (windowMode === modeNative && nativeWindow) {
+        } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
             nativeWindow.raise();
         }
     }
@@ -231,9 +197,9 @@ Windows.Window {
     }
 
     onInteractiveWindowVisibleChanged: {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             shown = interactiveWindowVisible;
-        } else if (windowMode === modeNative && nativeWindow) {
+        } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
             nativeWindow.setVisible(interactiveWindowVisible);
         }
     }
@@ -245,35 +211,42 @@ Windows.Window {
     }
 
     onXChanged: {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             interactiveWindowPosition = Qt.point(x, interactiveWindowPosition.y);
         }
     }
 
     onYChanged: {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y);
         }
     }
 
     onWidthChanged: {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             interactiveWindowSize = Qt.size(width, interactiveWindowSize.height);
         }
     }
 
     onHeightChanged: {
-        if (windowMode === modeVirtual) {
+        if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
             interactiveWindowSize = Qt.size(interactiveWindowSize.width, height);
         }
     }
 
-    onInteractiveWindowPositionChanged: {
-        updateInteractiveWindowPositionForMode();
+    onPresentationModeChanged: {
+        if (initialized) {
+            setupPresentationMode();
+        }
     }
 
-    onInteractiveWindowSizeChanged: {
-        updateInteractiveWindowSizeForMode();
+    onWindowClosed: {
+        // set invisible on close, to make it not re-appear unintended after switching PresentationMode
+        interactiveWindowVisible = false;
+    }
+
+    onWindowDestroyed: {
+        console.warn("destroyed");
     }
 
     Item {
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 7b2987b092..fba64e1e00 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2828,8 +2828,6 @@ void Application::initializeUi() {
     qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
     qmlRegisterType<WebBrowserSuggestionsEngine>("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine");
 
-    InteractiveWindowEnums::declareQML();
-
     {
         auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
         tabletScriptingInterface->getTablet(SYSTEM_TABLET);
@@ -2978,6 +2976,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
 
     surfaceContext->setContextProperty("Overlays", &_overlays);
     surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
+    surfaceContext->setContextProperty("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
     surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
     surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
     surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp
index e326b765a9..bda06cda48 100644
--- a/interface/src/scripting/DesktopScriptingInterface.cpp
+++ b/interface/src/scripting/DesktopScriptingInterface.cpp
@@ -31,6 +31,14 @@ int DesktopScriptingInterface::getHeight() {
     return size.height();
 }
 
+QVariantMap DesktopScriptingInterface::getPresentationMode() {
+    static QVariantMap presentationModes {
+        { "VIRTUAL", Virtual },
+        { "NATIVE", Native }
+    };
+    return presentationModes;
+}
+
 void DesktopScriptingInterface::setHUDAlpha(float alpha) {
     qApp->getApplicationCompositor().setAlpha(alpha);
 }
diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h
index 6003153ca7..ae0e8d5a4f 100644
--- a/interface/src/scripting/DesktopScriptingInterface.h
+++ b/interface/src/scripting/DesktopScriptingInterface.h
@@ -19,16 +19,20 @@
 
 #include "InteractiveWindow.h"
 
-
+/**jsdoc
+ * @namespace Desktop
+ *
+ * @hifi-interface
+ * @hifi-client-entity
+ */
 class DesktopScriptingInterface : public QObject, public Dependency {
     Q_OBJECT
     Q_PROPERTY(int width READ getWidth)  // Physical width of screen(s) including task bars and system menus
     Q_PROPERTY(int height READ getHeight)  // Physical height of screen(s) including task bars and system menus
 
-    Q_PROPERTY(int ForceNative READ flagForceNative)
-    Q_PROPERTY(int ForceVirtual READ flagForceVirtual)
-    Q_PROPERTY(int AlwaysOnTop READ flagAlwaysOnTop)
-    Q_PROPERTY(int CloseButtonHides READ flagCloseButtonHides)
+    Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL)
+    Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL)
+    Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL)
 
 public:
     Q_INVOKABLE void setHUDAlpha(float alpha);
@@ -41,10 +45,11 @@ public:
 
 
 private:
-    int flagForceNative() { return ForceNative; }
-    int flagForceVirtual() { return ForceVirtual; }
-    int flagAlwaysOnTop() { return AlwaysOnTop; }
-    int flagCloseButtonHides() { return CloseButtonHides; }
+    static int flagAlwaysOnTop() { return AlwaysOnTop; }
+    static int flagCloseButtonHides() { return CloseButtonHides; }
+
+    Q_INVOKABLE static QVariantMap getPresentationMode();
 };
 
+
 #endif // hifi_DesktopScriptingInterface_h
diff --git a/libraries/ui/src/InteractiveWindow.cpp b/libraries/ui/src/InteractiveWindow.cpp
index cadd0080e7..ad74ff3720 100644
--- a/libraries/ui/src/InteractiveWindow.cpp
+++ b/libraries/ui/src/InteractiveWindow.cpp
@@ -32,7 +32,7 @@ static const char* const INTERACTIVE_WINDOW_SIZE_PROPERTY = "interactiveWindowSi
 static const char* const VISIBLE_PROPERTY = "visible";
 static const char* const INTERACTIVE_WINDOW_VISIBLE_PROPERTY = "interactiveWindowVisible";
 static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge";
-static const char* const WINDOW_MODE_TEXT_PROPERTY = "windowModeText";
+static const char* const PRESENTATION_MODE_PROPERTY = "presentationMode";
 
 static const QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "file" << "about" << "atp" << "qrc";
 
@@ -60,6 +60,9 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
         if (properties.contains(FLAGS_PROPERTY)) {
             object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt());
         }
+        if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
+            object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt());
+        }
         if (properties.contains(TITLE_PROPERTY)) {
             object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
         }
@@ -79,9 +82,10 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
         connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection);
         connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection);
         connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
-        connect(object, SIGNAL(windowModeChanged()), this, SIGNAL(modeChanged()), Qt::QueuedConnection);
+        connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection);
         connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection);
 
+
         QUrl sourceURL{ sourceUrl };
         // If the passed URL doesn't correspond to a known scheme, assume it's a local file path
         if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) {
@@ -188,7 +192,7 @@ bool InteractiveWindow::isVisible() const {
 
 glm::vec2 InteractiveWindow::getPosition() const {
     if (QThread::currentThread() != thread()) {
-        vec2 result;
+        glm::vec2 result;
         BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPosition", Q_RETURN_ARG(glm::vec2, result));
         return result;
     }
@@ -208,12 +212,13 @@ void InteractiveWindow::setPosition(const glm::vec2& position) {
 
     if (!_qmlWindow.isNull()) {
         _qmlWindow->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
+        QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowPositionForMode", Qt::DirectConnection);
     }
 }
 
 glm::vec2 InteractiveWindow::getSize() const {
     if (QThread::currentThread() != thread()) {
-        vec2 result;
+        glm::vec2 result;
         BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getSize", Q_RETURN_ARG(glm::vec2, result));
         return result;
     }
@@ -232,18 +237,55 @@ void InteractiveWindow::setSize(const glm::vec2& size) {
 
     if (!_qmlWindow.isNull()) {
         _qmlWindow->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
+        QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowSizeForMode", Qt::DirectConnection);
     }
 }
 
-QString InteractiveWindow::getMode() const {
+QString InteractiveWindow::getTitle() const {
     if (QThread::currentThread() != thread()) {
         QString result;
-        BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getMode", Q_RETURN_ARG(QString, result));
+        BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getTitle", Q_RETURN_ARG(QString, result));
         return result;
     }
 
     if (_qmlWindow.isNull()) {
         return QString();
     }
-    return _qmlWindow->property(WINDOW_MODE_TEXT_PROPERTY).toString();
+    return _qmlWindow->property(TITLE_PROPERTY).toString();
+}
+
+void InteractiveWindow::setTitle(const QString& title) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setTitle", Q_ARG(const QString&, title));
+        return;
+    }
+
+    if (!_qmlWindow.isNull()) {
+        _qmlWindow->setProperty(TITLE_PROPERTY, title);
+    }
+}
+
+int InteractiveWindow::getPresentationMode() const {
+    if (QThread::currentThread() != thread()) {
+        int result;
+        BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPresentationMode",
+            Q_RETURN_ARG(int, result));
+        return result;
+    }
+
+    if (_qmlWindow.isNull()) {
+        return Virtual;
+    }
+    return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt();
+}
+
+void InteractiveWindow::setPresentationMode(int presentationMode) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode));
+        return;
+    }
+
+    if (!_qmlWindow.isNull()) {
+        _qmlWindow->setProperty(PRESENTATION_MODE_PROPERTY, presentationMode);
+    }
 }
diff --git a/libraries/ui/src/InteractiveWindow.h b/libraries/ui/src/InteractiveWindow.h
index 1258ae6943..541ff2e140 100644
--- a/libraries/ui/src/InteractiveWindow.h
+++ b/libraries/ui/src/InteractiveWindow.h
@@ -19,33 +19,76 @@
 #include <QtScript/QScriptValue>
 #include <QQmlEngine>
 
+#include <glm/glm.hpp>
 #include <GLMHelpers.h>
 
 namespace InteractiveWindowEnums {
     Q_NAMESPACE
 
     enum InteractiveWindowFlags : uint8_t {
-        ForceNative = 1 << 0,
-        ForceVirtual = 1 << 1,
-        AlwaysOnTop = 1 << 2,
-        CloseButtonHides = 1 << 3
+        AlwaysOnTop = 1 << 0,
+        CloseButtonHides = 1 << 1
     };
     Q_ENUM_NS(InteractiveWindowFlags);
 
-    inline void declareQML() {
-        qmlRegisterUncreatableMetaObject(staticMetaObject, "InteractiveWindowFlags", 1, 0,
-            "InteractiveWindowFlags", "Error: enums only");
-    }
+    enum InteractiveWindowPresentationMode {
+        Virtual,
+        Native
+    };
+    Q_ENUM_NS(InteractiveWindowPresentationMode);
 }
 
 using namespace InteractiveWindowEnums;
 
+/**jsdoc
+ * @class InteractiveWindow
+ * @hideconstructor
+ *
+ * @hifi-interface
+ * @hifi-client-en
+ *
+ * @property {string} mode
+ */
 class InteractiveWindow : public QObject {
     Q_OBJECT
+
+    /**jsdoc
+     * title of the window
+     *
+     * @name InteractiveWindow#title
+     * @type string
+     * @default "InteractiveWindow"
+     */
+    Q_PROPERTY(QString title READ getTitle WRITE setTitle)
+
+    /**jsdoc
+     * window position on current desktop
+     *
+     * @name InteractiveWindow#position
+     * @type Vec2
+     */
     Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
+
+    /**jsdoc
+     * window size
+     *
+     * @name InteractiveWindow#size
+     * @type Vec2
+     */
     Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
+
+    /**jsdoc
+     * visibility of the window
+     *
+     * @name InteractiveWindow#visible
+     * @type boolean
+     * @default true
+     * @example
+     * // Toggle window visiblity;
+     * interactiveWindow.visible = !interactiveWindow.visible;
+     */
     Q_PROPERTY(bool visible READ isVisible WRITE setVisible)
-    Q_PROPERTY(QString mode READ getMode)
+    Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode)
 
 public:
     InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties);
@@ -54,6 +97,9 @@ public:
 
 private:
     // define property getters and setters as private to not expose them to the JS API
+    Q_INVOKABLE QString getTitle() const;
+    Q_INVOKABLE void setTitle(const QString& title);
+
     Q_INVOKABLE glm::vec2 getPosition() const;
     Q_INVOKABLE void setPosition(const glm::vec2& position);
 
@@ -63,7 +109,8 @@ private:
     Q_INVOKABLE void setVisible(bool visible);
     Q_INVOKABLE bool isVisible() const;
 
-    Q_INVOKABLE QString getMode() const;
+    Q_INVOKABLE void setPresentationMode(int presentationMode);
+    Q_INVOKABLE int getPresentationMode() const;
 
 public slots:
 
@@ -123,10 +170,10 @@ signals:
     void sizeChanged();
 
     /**jsdoc
-     * @function InteractiveWindow.modeChanged
+     * @function InteractiveWindow.presentationModeChanged
      * @returns {Signal}
      */
-    void modeChanged();
+    void presentationModeChanged();
 
     /**jsdoc
      * @function InteractiveWindow.titleChanged
@@ -134,6 +181,12 @@ signals:
      */
     void titleChanged();
 
+    /**jsdoc
+     * @function InteractiveWindow.closed
+     * @returns {Signal}
+     */
+    void closed();
+
     /**jsdoc
      * @function InteractiveWindow.fromQml
      * @param {object} message