From 0f670f94ec42fb591b21453fb4ad699264351980 Mon Sep 17 00:00:00 2001 From: Skycoder42 Date: Tue, 20 Feb 2018 13:24:12 +0100 Subject: [PATCH] added input dialog and url edit --- src/imports/mvvmquick/DialogPresenter.qml | 30 +++++ src/imports/mvvmquick/InputDialog.qml | 59 ++++++++++ src/imports/mvvmquick/MsgBox.qml | 132 ++++------------------ src/imports/mvvmquick/MsgBoxBase.qml | 110 ++++++++++++++++++ src/imports/mvvmquick/mvvmquick.pro | 4 +- src/imports/mvvmquick/plugins.qmltypes | 11 ++ src/imports/mvvmquick/qmldir | 2 + src/mvvmquick/UrlField.qml | 14 +++ src/mvvmquick/inputviewfactory.cpp | 22 +++- src/mvvmquick/mvvmquick.pro | 14 ++- src/mvvmquick/qpmx.json | 8 +- src/mvvmquick/qtmvvmquick_module.qrc | 1 + src/mvvmwidgets/widgetspresenter.cpp | 1 + 13 files changed, 286 insertions(+), 122 deletions(-) create mode 100644 src/imports/mvvmquick/InputDialog.qml create mode 100644 src/imports/mvvmquick/MsgBoxBase.qml create mode 100644 src/mvvmquick/UrlField.qml diff --git a/src/imports/mvvmquick/DialogPresenter.qml b/src/imports/mvvmquick/DialogPresenter.qml index 924b1bf..93629e9 100644 --- a/src/imports/mvvmquick/DialogPresenter.qml +++ b/src/imports/mvvmquick/DialogPresenter.qml @@ -11,6 +11,9 @@ QtObject { if(config.type == "msgbox") { createMsgBox(config, result) return true; + } else if(config.type == "input") { + createInput(config, result) + return true; } else return false; } @@ -43,6 +46,25 @@ QtObject { } } + property Component _inputComponent: Component { + InputDialog { + id: __input + + onClosed: { + var index = _popups.indexOf(__input); + if(index > -1) { + __input.destroy(); + _dialogPresenter._popups.splice(index, 1); + } + } + + Component.onCompleted: { + _popups.push(__input) + __input.open() + } + } + } + function createMsgBox(config, result) { var props = config.viewProperties; props["msgConfig"] = config; @@ -50,4 +72,12 @@ QtObject { var incubator = _msgBoxComponent.incubateObject(rootItem, props); return incubator.status !== Component.Error; } + + function createInput(config, result) { + var props = config.viewProperties; + props["msgConfig"] = config; + props["msgResult"] = result; + var incubator = _inputComponent.incubateObject(rootItem, props); + return incubator.status !== Component.Error; + } } diff --git a/src/imports/mvvmquick/InputDialog.qml b/src/imports/mvvmquick/InputDialog.qml new file mode 100644 index 0000000..ab42077 --- /dev/null +++ b/src/imports/mvvmquick/InputDialog.qml @@ -0,0 +1,59 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.3 +import de.skycoder42.QtMvvm.Quick 1.0 + +MsgBoxBase { + id: _inputDialog + + readonly property url inputUrl: QuickPresenter.inputViewFactory.getInputUrl(msgConfig.subType, msgConfig.viewProperties) + + ColumnLayout { + anchors.fill: parent + spacing: 14 + + Label { + id: _contentLabel + text: msgConfig.text.replace(/<\/p>/g, "


") //needed because qml does not put space between paragraphs... + visible: text != "" + Layout.fillWidth: true + wrapMode: Text.Wrap + onLinkActivated: Qt.openUrlExternally(link) + } + + ProgressBar { + id: _loadProgress + visible: _inputViewLoad.status == Loader.Loading + Layout.fillWidth: true + value: _inputViewLoad.progress + } + + Label { + id: _errorLabel + visible: _inputViewLoad.status == Loader.Error + Layout.fillWidth: true + text: qsTr("Failed to load input view for type: %1").arg(msgConfig.subType) + } + + Loader { + id: _inputViewLoad + asynchronous: true + clip: true + visible: _inputViewLoad.item + + Layout.preferredWidth: _inputViewLoad.item ? _inputViewLoad.item.implicitWidth : 0 + Layout.fillHeight: true + Layout.fillWidth: true + + onLoaded: msgResult.result = Qt.binding(function() { + return item.inputValue; + }) + } + } + + Component.onCompleted: { + var props = msgConfig.viewProperties; + props["inputValue"] = msgConfig.defaultValue; + _inputViewLoad.setSource(inputUrl, props); + } +} diff --git a/src/imports/mvvmquick/MsgBox.qml b/src/imports/mvvmquick/MsgBox.qml index 3a8a318..1f000cd 100644 --- a/src/imports/mvvmquick/MsgBox.qml +++ b/src/imports/mvvmquick/MsgBox.qml @@ -1,79 +1,30 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 -import QtQuick.Window 2.2 -import QtQuick.Layouts 1.3 -import de.skycoder42.QtMvvm.Core 1.0 -Dialog { +MsgBoxBase { id: _msgBox - property var msgConfig - property MessageResult msgResult - - property real extraHeight: 0 - property real baseWidth: 300 - - x: parent ? (parent.width - width) / 2 : 0 - y: parent ? deltaY() : 0 - width: parent ? Math.min(Math.max(implicitWidth, baseWidth), parent.width - 28) : implicitWidth - height: parent ? Math.min(implicitHeight, parent.height - 28) : implicitWidth - modal: true - focus: true - - function deltaY() { - var unscaled = Qt.inputMethod.keyboardRectangle.height / Screen.devicePixelRatio; - var availHeight = (parent.height + extraHeight) - unscaled - 28;//spacing - var rawDelta = (Math.max(0, availHeight - height) / 2); - return rawDelta + 14 - extraHeight;//spacing - } - - header: RowLayout { - spacing: 14 - - TintIcon { - id: icon - property int iconType - - Layout.preferredWidth: 24 - Layout.preferredHeight: 24 - Layout.margins: 24 - Layout.bottomMargin: 0 - Layout.rightMargin: 0 - visible: msgConfig.subType != "about" - source: { - var base = "image://svg/de/skycoder42/qtmvvm/quick/icons/ic_%1"; - switch(String(msgConfig.subType)) { - case "information": - base = base.arg("info"); - break; - case "question": - base = base.arg("help"); - break; - case "warning": - base = base.arg("warning"); - break; - case "critical": - base = base.arg("error"); - break; - case "about": - default: - return ""; - } - return base; - } - } - - Label { - text: msgConfig.title - visible: msgConfig.title - elide: Label.ElideRight - font.bold: true - font.pixelSize: 16 - Layout.fillWidth: true - Layout.margins: 24 - Layout.bottomMargin: 0 - Layout.leftMargin: icon.visible ? 0 : 24 + iconVisible: msgConfig.subType != "about" + iconSource: { + var base = "image://svg/de/skycoder42/qtmvvm/quick/icons/ic_%1"; + switch(String(msgConfig.subType)) { + case "information": + base = base.arg("info"); + break; + case "question": + base = base.arg("help"); + break; + case "warning": + base = base.arg("warning"); + break; + case "critical": + base = base.arg("error"); + break; + case "about": + default: + return ""; } + return base; } Label { @@ -84,45 +35,4 @@ Dialog { wrapMode: Text.Wrap onLinkActivated: Qt.openUrlExternally(link) } - - footer: DialogButtonBox { - id: _btnBox - - readonly property var _allBtns: [ - DialogButtonBox.NoButton, - DialogButtonBox.Ok, - DialogButtonBox.Save, - DialogButtonBox.SaveAll, - DialogButtonBox.Open, - DialogButtonBox.Yes, - DialogButtonBox.YesToAll, - DialogButtonBox.No, - DialogButtonBox.NoToAll, - DialogButtonBox.Abort, - DialogButtonBox.Retry, - DialogButtonBox.Ignore, - DialogButtonBox.Close, - DialogButtonBox.Cancel, - DialogButtonBox.Discard, - DialogButtonBox.Help, - DialogButtonBox.Apply, - DialogButtonBox.Reset, - DialogButtonBox.RestoreDefaults, - ] - - standardButtons: msgConfig.buttons - onStandardButtonsChanged: { - for(var key in msgConfig.buttonTexts) - standardButton(DialogButtonBox.Ok).text = msgConfig.buttonTexts[key] - } - - onClicked: { - if(msgResult) { - _allBtns.forEach(function(sBtn) { - if(button === standardButton(sBtn)) - msgResult.complete(sBtn) - }) - } - } - } } diff --git a/src/imports/mvvmquick/MsgBoxBase.qml b/src/imports/mvvmquick/MsgBoxBase.qml new file mode 100644 index 0000000..c0b4b42 --- /dev/null +++ b/src/imports/mvvmquick/MsgBoxBase.qml @@ -0,0 +1,110 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import QtQuick.Window 2.2 +import QtQuick.Layouts 1.3 +import de.skycoder42.QtMvvm.Core 1.0 + +Dialog { + id: _msgBoxBase + + property var msgConfig + property MessageResult msgResult + + property real extraHeight: 0 + property real baseWidth: 300 + + property alias iconVisible: _icon.visible + property alias iconSource: _icon.source + + x: parent ? (parent.width - width) / 2 : 0 + y: parent ? deltaY() : 0 + width: parent ? Math.min(Math.max(implicitWidth, baseWidth), parent.width - 28) : implicitWidth + height: parent ? Math.min(implicitHeight, parent.height - 28) : implicitWidth + modal: true + focus: true + + function deltaY() { + var unscaled = Qt.inputMethod.keyboardRectangle.height / Screen.devicePixelRatio; + var availHeight = (parent.height + extraHeight) - unscaled - 28;//spacing + var rawDelta = (Math.max(0, availHeight - height) / 2); + return rawDelta + 14 - extraHeight;//spacing + } + + header: RowLayout { + spacing: 14 + + TintIcon { + id: _icon + visible: false + + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + Layout.margins: 24 + Layout.bottomMargin: 0 + Layout.rightMargin: 0 + } + + Label { + id: _title + text: msgConfig.title + visible: msgConfig.title + elide: Label.ElideRight + font.bold: true + font.pixelSize: 16 + Layout.fillWidth: true + Layout.margins: 24 + Layout.bottomMargin: 0 + Layout.leftMargin: _icon.visible ? 0 : 24 + } + } + + footer: DialogButtonBox { + id: _btnBox + + readonly property var _allBtns: [ + DialogButtonBox.NoButton, + DialogButtonBox.Ok, + DialogButtonBox.Save, + DialogButtonBox.SaveAll, + DialogButtonBox.Open, + DialogButtonBox.Yes, + DialogButtonBox.YesToAll, + DialogButtonBox.No, + DialogButtonBox.NoToAll, + DialogButtonBox.Abort, + DialogButtonBox.Retry, + DialogButtonBox.Ignore, + DialogButtonBox.Close, + DialogButtonBox.Cancel, + DialogButtonBox.Discard, + DialogButtonBox.Help, + DialogButtonBox.Apply, + DialogButtonBox.Reset, + DialogButtonBox.RestoreDefaults, + ] + + standardButtons: msgConfig.buttons + onStandardButtonsChanged: { + for(var key in msgConfig.buttonTexts) + standardButton(DialogButtonBox.Ok).text = msgConfig.buttonTexts[key] + } + + onClicked: { + if(msgResult) { + _allBtns.forEach(function(sBtn) { + if(button === standardButton(sBtn)) { + msgResult.complete(sBtn); + msgResult = null; + } + }) + } + } + } + + onClosed: { + if(msgResult) { + msgResult.complete(MessageConfig.NoButton); + msgResult = null; + } + } +} diff --git a/src/imports/mvvmquick/mvvmquick.pro b/src/imports/mvvmquick/mvvmquick.pro index 0056c17..7b1ef5d 100644 --- a/src/imports/mvvmquick/mvvmquick.pro +++ b/src/imports/mvvmquick/mvvmquick.pro @@ -23,7 +23,9 @@ QML_FILES += \ PopupPresenter.qml \ DialogPresenter.qml \ TintIcon.qml \ - MsgBox.qml + MsgBoxBase.qml \ + MsgBox.qml \ + InputDialog.qml OTHER_FILES += qmldir diff --git a/src/imports/mvvmquick/plugins.qmltypes b/src/imports/mvvmquick/plugins.qmltypes index 5c4f600..be8da63 100644 --- a/src/imports/mvvmquick/plugins.qmltypes +++ b/src/imports/mvvmquick/plugins.qmltypes @@ -8,6 +8,17 @@ import QtQuick.tooling 1.2 Module { dependencies: ["QtQuick 2.8"] + Component { + name: "QUrlValidator" + prototype: "QValidator" + exports: ["de.skycoder42.QtMvvm.Quick.Private/UrlValidator 1.0"] + exportMetaObjectRevisions: [0] + Property { name: "allowedSchemes"; type: "QStringList" } + Method { + name: "setAllowedSchemes" + Parameter { name: "allowedSchemes"; type: "QStringList" } + } + } Component { name: "QtMvvm::InputViewFactory" prototype: "QObject" diff --git a/src/imports/mvvmquick/qmldir b/src/imports/mvvmquick/qmldir index a56f8a0..6238a4a 100644 --- a/src/imports/mvvmquick/qmldir +++ b/src/imports/mvvmquick/qmldir @@ -4,7 +4,9 @@ classname QtMvvmQuickDeclarativeModule typeinfo plugins.qmltypes internal TintIcon TintIcon.qml +internal MsgBoxBase MsgBoxBase.qml internal MsgBox MsgBox.qml +internal InputDialog InputDialog.qml PresenterProgress 1.0 PresenterProgress.qml PresentingStackView 1.0 PresentingStackView.qml diff --git a/src/mvvmquick/UrlField.qml b/src/mvvmquick/UrlField.qml new file mode 100644 index 0000000..0a4058a --- /dev/null +++ b/src/mvvmquick/UrlField.qml @@ -0,0 +1,14 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import de.skycoder42.QtMvvm.Quick.Private 1.0 + +TextField { + id: _edit + property alias inputValue: _edit.text + property alias allowedSchemes: _validator.allowedSchemes + + selectByMouse: true + validator: UrlValidator { + id: _validator + } +} diff --git a/src/mvvmquick/inputviewfactory.cpp b/src/mvvmquick/inputviewfactory.cpp index 079d3cd..2cb74ad 100644 --- a/src/mvvmquick/inputviewfactory.cpp +++ b/src/mvvmquick/inputviewfactory.cpp @@ -1,18 +1,34 @@ #include "inputviewfactory.h" #include "inputviewfactory_p.h" +#include #include +#include + #include + +#include using namespace QtMvvm; -static void initResources() +namespace { + +void initPrivateQml() +{ + qmlRegisterType("de.skycoder42.QtMvvm.Quick.Private", 1, 0, "UrlValidator"); +} + +void initResources() { #ifdef QT_STATIC + initPrivateQml(); Q_INIT_RESOURCE(qtmvvmquick_module); #endif } +} +Q_COREAPP_STARTUP_FUNCTION(initPrivateQml) + InputViewFactory::InputViewFactory() : QObject(nullptr), d(new InputViewFactoryPrivate()) @@ -53,8 +69,8 @@ QUrl InputViewFactory::getInputUrl(const QByteArray &type, const QVariantMap &vi // return QUrl(); else if(type == QMetaType::typeName(QMetaType::QFont)) return QStringLiteral("qrc:/de/skycoder42/qtmvvm/quick/inputs/FontEdit.qml"); -// else if(type == QMetaType::typeName(QMetaType::QUrl) || type == "url") -// return QStringLiteral("qrc:/de/skycoder42/qtmvvm/quick/inputs/UrlField.qml"); + else if(type == QMetaType::typeName(QMetaType::QUrl) || type == "url") + return QStringLiteral("qrc:/de/skycoder42/qtmvvm/quick/inputs/UrlField.qml"); else if(type == "selection" || type == "list") return QStringLiteral("qrc:/de/skycoder42/qtmvvm/quick/inputs/ListEdit.qml"); else { diff --git a/src/mvvmquick/mvvmquick.pro b/src/mvvmquick/mvvmquick.pro index 33f4365..da87d72 100644 --- a/src/mvvmquick/mvvmquick.pro +++ b/src/mvvmquick/mvvmquick.pro @@ -1,17 +1,20 @@ TARGET = QtMvvmQuick -QT = core gui widgets mvvmcore mvvmcore-private +QT = core gui qml quick mvvmcore mvvmcore-private HEADERS += \ qtmvvmquick_global.h \ quickpresenter.h \ quickpresenter_p.h \ - inputviewfactory.h \ - inputviewfactory_p.h + inputviewfactory.h \ + inputviewfactory_p.h SOURCES += \ quickpresenter.cpp \ - inputviewfactory.cpp + inputviewfactory.cpp + +RESOURCES += \ + qtmvvmquick_module.qrc TRANSLATIONS += \ translations/qtmvvmquick_de.ts \ @@ -39,5 +42,4 @@ else: include($$OUT_PWD/qpmx_generated.pri) qpmx_ts_target.files -= $$OUT_PWD/$$QPMX_WORKINGDIR/qtmvvmquick_template.qm qpmx_ts_target.files += translations/qtmvvmquick_template.ts -RESOURCES += \ - qtmvvmquick_module.qrc +mingw: LIBS_PRIVATE += -lQt5Gui -lQt5Core diff --git a/src/mvvmquick/qpmx.json b/src/mvvmquick/qpmx.json index 461fe91..7fa89ac 100644 --- a/src/mvvmquick/qpmx.json +++ b/src/mvvmquick/qpmx.json @@ -1,5 +1,11 @@ { - "dependencies": [], + "dependencies": [ + { + "package": "de.skycoder42.qurlvalidator", + "provider": "qpm", + "version": "1.1.0" + } + ], "license": { "file": "", "name": "" diff --git a/src/mvvmquick/qtmvvmquick_module.qrc b/src/mvvmquick/qtmvvmquick_module.qrc index d574450..21a3618 100644 --- a/src/mvvmquick/qtmvvmquick_module.qrc +++ b/src/mvvmquick/qtmvvmquick_module.qrc @@ -6,5 +6,6 @@ CheckBox.qml DoubleSpinBox.qml FontEdit.qml + UrlField.qml diff --git a/src/mvvmwidgets/widgetspresenter.cpp b/src/mvvmwidgets/widgetspresenter.cpp index f3c361a..aca17c5 100644 --- a/src/mvvmwidgets/widgetspresenter.cpp +++ b/src/mvvmwidgets/widgetspresenter.cpp @@ -221,6 +221,7 @@ void WidgetsPresenter::presentMessageBox(const MessageConfig &config, QPointer(static_cast(config.buttons())); //is ok, as the buttons are the same