diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b4ce53b92a..3e8a6ef92d 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed -set(OPTIONAL_EXTERNALS "LeapMotion") +#set(OPTIONAL_EXTERNALS "LeapMotion") if(WIN32) list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient") diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index b597016b5a..eb80f2a7f4 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -18,8 +18,6 @@ FocusScope { // The VR version of the primary menu property var rootMenu: Menu { objectName: "rootMenu" } - Component { id: messageDialogBuilder; MessageDialog { } } - QtObject { id: d readonly property int zBasisNormal: 0 @@ -158,16 +156,22 @@ FocusScope { } } - MenuMouseHandler { id: menuPopperUpper } - function raise(item) { d.raiseWindow(item); } + + Component { id: messageDialogBuilder; MessageDialog { } } function messageBox(properties) { return messageDialogBuilder.createObject(desktop, properties); } + Component { id: queryDialogBuilder; QueryDialog { } } + function queryBox(properties) { + return queryDialogBuilder.createObject(desktop, properties); + } + + MenuMouseHandler { id: menuPopperUpper } function popupMenu(point) { menuPopperUpper.popup(desktop, rootMenu.items, point); } @@ -213,7 +217,7 @@ FocusScope { function onWindowFocusChanged() { console.log("Focus item is " + offscreenWindow.activeFocusItem); var focusedItem = offscreenWindow.activeFocusItem ; - if (DebugQML && focusedItem) { + if (DebugQML && focusedItem && false) { var rect = desktop.mapFromItem(focusedItem, 0, 0, focusedItem.width, focusedItem.height); focusDebugger.visible = true focusDebugger.x = rect.x; diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 5b9a653f2e..35efd4d0d1 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -19,7 +19,6 @@ ModalWindow { signal selected(int button); function click(button) { - console.log("User clicked " + button) clickedButton = button; selected(button); destroy(); diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml new file mode 100644 index 0000000000..52fde34271 --- /dev/null +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -0,0 +1,114 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 as OriginalDialogs + +import "../controls" as VrControls +import "../styles" +import "../windows" + +ModalWindow { + id: root + HifiConstants { id: hifi } + implicitWidth: 640 + implicitHeight: 320 + visible: true + + signal selected(var result); + signal canceled(); + + property alias result: textResult.text + property alias placeholderText: textResult.placeholderText + property alias text: mainTextContainer.text + + Rectangle { + clip: true + anchors.fill: parent + radius: 4 + color: "white" + + QtObject { + id: d + readonly property real spacing: hifi.layout.spacing + readonly property real outerSpacing: hifi.layout.spacing * 2 + readonly property int minWidth: 480 + readonly property int maxWdith: 1280 + readonly property int minHeight: 120 + readonly property int maxHeight: 720 + + function resize() { + var targetWidth = mainTextContainer.width + d.spacing * 6 + var targetHeight = mainTextContainer.implicitHeight + textResult.height + d.spacing + buttons.height + root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth) + root.height = (targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight) + } + } + + Text { + id: mainTextContainer + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + wrapMode: Text.WordWrap + font { pointSize: 14; weight: Font.Bold } + anchors { left: parent.left; top: parent.top; margins: d.spacing } + } + + Item { + anchors { top: mainTextContainer.bottom; bottom: buttons.top; left: parent.left; right: parent.right; margins: d.spacing } + // FIXME make a text field type that can be bound to a history for autocompletion + TextField { + focus: true + id: textResult + anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } + } + } + + Flow { + id: buttons + focus: true + spacing: d.spacing + onHeightChanged: d.resize(); onWidthChanged: d.resize(); + layoutDirection: Qt.RightToLeft + anchors { bottom: parent.bottom; right: parent.right; margins: d.spacing; } + Button { action: acceptAction } + Button { action: cancelAction } + } + + Action { + id: cancelAction + text: qsTr("Cancel") + shortcut: Qt.Key_Escape + onTriggered: { + root.canceled(); + root.destroy(); + } + } + Action { + id: acceptAction + text: qsTr("OK") + shortcut: Qt.Key_Return + onTriggered: { + root.selected(root.result); + root.destroy(); + } + } + } + + Keys.onPressed: { + if (!visible) { + return + } + + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + cancelAction.trigger() + event.accepted = true; + break; + + case Qt.Key_Return: + case Qt.Key_Enter: + acceptAction.trigger() + event.accepted = true; + break; + } + } +} diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index d3b7434990..1b8404f17b 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -11,7 +11,7 @@ Frame { // FIXME needed? Rectangle { - anchors { margins: -iconSize; topMargin: -iconSize * (window.closable ? 2 : 1); } + anchors { margins: -iconSize; topMargin: -iconSize * ((window && window.closable) ? 2 : 1); } anchors.fill: parent; color: "#7f7f7f7f"; radius: 3; @@ -46,7 +46,7 @@ Frame { } } FontAwesome { - visible: window.closable + visible: window ? window.closable : false text: closeClickArea.containsMouse ? "\uf057" : "\uf05c" style: Text.Outline; styleColor: "white" diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index dfffde4ad4..4bcf3998f4 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -20,7 +20,7 @@ Item { Text { id: debugZ visible: DebugQML - text: "Z: " + window.z + text: window ? "Z: " + window.z : "" y: -height } @@ -41,21 +41,21 @@ Item { Rectangle { id: sizeOutline - width: window.width - height: window.height + width: window ? window.width : 0 + height: window ? window.height : 0 color: "#00000000" border.width: 4 radius: 10 - visible: !window.content.visible + visible: window ? !window.content.visible : false } MouseArea { id: sizeDrag width: iconSize height: iconSize - enabled: window.resizable - x: window.width - y: window.height + enabled: window ? window.resizable : false + x: window ? window.width : 0 + y: window ? window.height : 0 property vector2d pressOrigin property vector2d sizeOrigin property bool hid: false diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index eb45273ff4..32443e70e3 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -6,6 +6,8 @@ Window { id: root anchors.centerIn: parent modality: Qt.ApplicationModal + destroyOnCloseButton: true + destroyOnInvisible: true frame: ModalFrame{} } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ddc90886c9..22bc4effcb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4554,22 +4554,8 @@ void Application::loadDialog() { } void Application::loadScriptURLDialog() { - // To be migratd to QML - QInputDialog scriptURLDialog(getWindow()); - scriptURLDialog.setWindowTitle("Open and Run Script URL"); - scriptURLDialog.setLabelText("Script:"); - scriptURLDialog.setWindowFlags(Qt::Sheet); - const float DIALOG_RATIO_OF_WINDOW = 0.30f; - scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, - scriptURLDialog.size().height()); - - int dialogReturn = scriptURLDialog.exec(); - QString newScript; - if (dialogReturn == QDialog::Accepted) { - if (scriptURLDialog.textValue().size() > 0) { - // the user input a new hostname, use that - newScript = scriptURLDialog.textValue(); - } + auto newScript = OffscreenUi::getText(nullptr, "Open and Run Script", "Script URL"); + if (!newScript.isEmpty()) { DependencyManager::get()->loadScript(newScript); } } diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 70a07db664..4f7336e599 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -10,9 +10,10 @@ // #include "OffscreenUi.h" -#include -#include +#include #include +#include +#include #include #include @@ -132,47 +133,64 @@ void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function(button); - _finished = true; - disconnect(_messageBox); - } - +protected slots: void onDestroyed() { _finished = true; - disconnect(_messageBox); + disconnect(_dialog); } -private: +protected: + QQuickItem* const _dialog; bool _finished { false }; - QMessageBox::StandardButton _result { QMessageBox::StandardButton::NoButton }; - QQuickItem* const _messageBox; + QVariant _result; +}; + +class MessageBoxListener : public ModalDialogListener { + Q_OBJECT + + friend class OffscreenUi; + MessageBoxListener(QQuickItem* messageBox) : ModalDialogListener(messageBox) { + if (_finished) { + return; + } + connect(_dialog, SIGNAL(selected(int)), this, SLOT(onSelected(int))); + } + + virtual QMessageBox::StandardButton waitForButtonResult() { + ModalDialogListener::waitForResult(); + return static_cast(_result.toInt()); + } + +private slots: + void onSelected(int button) { + _result = button; + _finished = true; + disconnect(_dialog); + } }; QMessageBox::StandardButton OffscreenUi::messageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { @@ -204,7 +222,7 @@ QMessageBox::StandardButton OffscreenUi::messageBox(QMessageBox::Icon icon, cons return QMessageBox::StandardButton::NoButton; } - auto resultButton = MessageBoxListener(qvariant_cast(result)).waitForResult(); + QMessageBox::StandardButton resultButton = MessageBoxListener(qvariant_cast(result)).waitForButtonResult(); qDebug() << "Message box got a result of " << resultButton; return resultButton; } @@ -226,6 +244,66 @@ QMessageBox::StandardButton OffscreenUi::warning(const QString& title, const QSt return DependencyManager::get()->messageBox(QMessageBox::Icon::Critical, title, text, buttons, defaultButton); } + + +class InputDialogListener : public ModalDialogListener { + Q_OBJECT + + friend class OffscreenUi; + InputDialogListener(QQuickItem* queryBox) : ModalDialogListener(queryBox) { + if (_finished) { + return; + } + connect(_dialog, SIGNAL(selected(QVariant)), this, SLOT(onSelected(const QVariant&))); + } + +private slots: + void onSelected(const QVariant& result) { + _result = result; + _finished = true; + disconnect(_dialog); + } +}; + +// FIXME many input parameters currently ignored +QString OffscreenUi::getText(void* ignored, const QString & title, const QString & label, QLineEdit::EchoMode mode, const QString & text, bool * ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints) { + QVariant result = DependencyManager::get()->inputDialog(title, label, text).toString(); + if (ok && result.isValid()) { + *ok = true; + } + return result.toString(); +} + + +QVariant OffscreenUi::inputDialog(const QString& query, const QString& placeholderText, const QString& currentValue) { + if (QThread::currentThread() != thread()) { + QVariant result; + QMetaObject::invokeMethod(this, "queryBox", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, result), + Q_ARG(QString, query), + Q_ARG(QString, placeholderText), + Q_ARG(QString, currentValue)); + return result; + } + + QVariantMap map; + map.insert("text", query); + map.insert("placeholderText", placeholderText); + map.insert("result", currentValue); + QVariant result; + bool invokeResult = QMetaObject::invokeMethod(_desktop, "queryBox", + Q_RETURN_ARG(QVariant, result), + Q_ARG(QVariant, QVariant::fromValue(map))); + + if (!invokeResult) { + qWarning() << "Failed to create message box"; + return QVariant(); + } + + return InputDialogListener(qvariant_cast(result)).waitForResult(); +} + + bool OffscreenUi::navigationFocused() { return offscreenFlags->isNavigationFocused(); } diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index fd54bc6c2b..73307b38f7 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -13,9 +13,11 @@ #define hifi_OffscreenUi_h #include -#include +#include +#include +#include -#include +#include #include #include "OffscreenQmlElement.h" @@ -78,7 +80,18 @@ public: QMessageBox::StandardButtons buttons = QMessageBox::Ok, QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); - QMessageBox::StandardButton messageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton); + Q_INVOKABLE QMessageBox::StandardButton messageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton); + Q_INVOKABLE QVariant inputDialog(const QString& query, const QString& placeholderText = QString(), const QString& currentValue = QString()); + + // FIXME implement + static QVariant query(const QString& query, const QString& placeholderText = QString(), const QString& currentValue = QString()); + + // FIXME implement + // 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 QInputDialog::getText + static QString getText(void* ignored, const QString & title, const QString & label, QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0, Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone); private: QQuickItem* _desktop { nullptr }; diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index b6a6b47814..b343d2404e 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -72,10 +72,30 @@ ApplicationWindow { Button { text: "Show Error" onClicked: { - desktop.messageBox({ - text: "Diagnostic cycle will be complete in 30 seconds", - icon: OriginalDialogs.StandardIcon.Critical, - }); + var messageBox = desktop.messageBox({ + text: "Diagnostic cycle will be complete in 30 seconds", + icon: OriginalDialogs.StandardIcon.Critical, + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + Button { + text: "Show Query" + onClicked: { + var queryBox = desktop.queryBox({ + text: "Have you stopped beating your wife?", + placeholderText: "Are you sure?", + // icon: OriginalDialogs.StandardIcon.Critical, + }); + queryBox.selected.connect(function(result) { + console.log("User responded with " + result); + }); + + queryBox.canceled.connect(function() { + console.log("User cancelled query box "); + }) } } Button { @@ -118,7 +138,7 @@ ApplicationWindow { } } } -/* + Window { id: blue closable: true @@ -143,7 +163,7 @@ ApplicationWindow { Component.onDestruction: console.log("Blue destroyed") } } - + /* Window { id: green alwaysOnTop: true diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index 81974ffee8..584a724c14 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -100,7 +100,8 @@ DISTFILES += \ ../../interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml \ ../../interface/resources/qml/dialogs/fileDialog/FileTableView.qml \ ../../interface/resources/qml/hifi/dialogs/preferences/Avatar.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/AvatarBrowser.qml + ../../interface/resources/qml/hifi/dialogs/preferences/AvatarBrowser.qml \ + ../../interface/resources/qml/dialogs/QueryDialog.qml HEADERS += \ ../../libraries/ui/src/FileDialogHelper.h