Adding OffscreenUI API for QInputDialog functionality

This commit is contained in:
Brad Davis 2016-01-22 17:29:52 -08:00
parent 27fdb523cb
commit 858bbbf987
12 changed files with 283 additions and 66 deletions

View file

@ -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")

View file

@ -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;

View file

@ -19,7 +19,6 @@ ModalWindow {
signal selected(int button);
function click(button) {
console.log("User clicked " + button)
clickedButton = button;
selected(button);
destroy();

View file

@ -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;
}
}
}

View file

@ -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"

View file

@ -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

View file

@ -6,6 +6,8 @@ Window {
id: root
anchors.centerIn: parent
modality: Qt.ApplicationModal
destroyOnCloseButton: true
destroyOnInvisible: true
frame: ModalFrame{}
}

View file

@ -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<ScriptEngines>()->loadScript(newScript);
}
}

View file

@ -10,9 +10,10 @@
//
#include "OffscreenUi.h"
#include <QtQml/QtQml>
#include <QtQuick/QQuickWindow>
#include <QtCore/QVariant>
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickWindow>
#include <QtQml/QtQml>
#include <AbstractUriHandler.h>
#include <AccountManager.h>
@ -132,47 +133,64 @@ void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<voi
}
}
class MessageBoxListener : public QObject {
class ModalDialogListener : public QObject {
Q_OBJECT
friend class OffscreenUi;
MessageBoxListener(QQuickItem* messageBox) : _messageBox(messageBox) {
if (!_messageBox) {
protected:
ModalDialogListener(QQuickItem* dialog) : _dialog(dialog) {
if (!dialog) {
_finished = true;
return;
}
connect(_messageBox, SIGNAL(selected(int)), this, SLOT(onSelected(int)));
connect(_messageBox, SIGNAL(destroyed()), this, SLOT(onDestroyed()));
}
connect(_dialog, SIGNAL(destroyed()), this, SLOT(onDestroyed()));
}
~MessageBoxListener() {
disconnect(_messageBox);
~ModalDialogListener() {
disconnect(_dialog);
}
QMessageBox::StandardButton waitForResult() {
virtual QVariant waitForResult() {
while (!_finished) {
QCoreApplication::processEvents();
}
return _result;
}
private slots:
void onSelected(int button) {
_result = static_cast<QMessageBox::StandardButton>(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<QMessageBox::StandardButton>(_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<QQuickItem*>(result)).waitForResult();
QMessageBox::StandardButton resultButton = MessageBoxListener(qvariant_cast<QQuickItem*>(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<OffscreenUi>()->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<OffscreenUi>()->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<QQuickItem*>(result)).waitForResult();
}
bool OffscreenUi::navigationFocused() {
return offscreenFlags->isNavigationFocused();
}

View file

@ -13,9 +13,11 @@
#define hifi_OffscreenUi_h
#include <QtCore/QVariant>
#include <gl/OffscreenQmlSurface.h>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QInputDialog>
#include <QMessageBox>
#include <gl/OffscreenQmlSurface.h>
#include <DependencyManager.h>
#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 };

View file

@ -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

View file

@ -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