diff --git a/src/imports/mvvmcore/plugins.qmltypes b/src/imports/mvvmcore/plugins.qmltypes index aef4d35..75edfad 100644 --- a/src/imports/mvvmcore/plugins.qmltypes +++ b/src/imports/mvvmcore/plugins.qmltypes @@ -8,6 +8,84 @@ import QtQuick.tooling 1.2 Module { dependencies: ["QtQuick 2.8"] + Component { + name: "QtMvvm::MessageConfig" + exports: ["de.skycoder42.QtMvvm.Core/MessageConfig 1.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + Enum { + name: "StandardButtons" + values: { + "NoButton": 0, + "Ok": 1024, + "Save": 2048, + "SaveAll": 4096, + "Open": 8192, + "Yes": 16384, + "YesToAll": 32768, + "No": 65536, + "NoToAll": 131072, + "Abort": 262144, + "Retry": 524288, + "Ignore": 1048576, + "Close": 2097152, + "Cancel": 4194304, + "Discard": 8388608, + "Help": 16777216, + "Apply": 33554432, + "Reset": 67108864, + "RestoreDefaults": 134217728 + } + } + Property { name: "type"; type: "QByteArray" } + Property { name: "subType"; type: "QByteArray" } + Property { name: "title"; type: "string" } + Property { name: "text"; type: "string" } + Property { name: "buttons"; type: "StandardButtons" } + Property { name: "buttonTexts"; type: "QVariantMap" } + Property { name: "defaultValue"; type: "QVariant" } + Property { name: "viewProperties"; type: "QVariantMap" } + } + Component { + name: "QtMvvm::MessageResult" + prototype: "QObject" + exports: ["de.skycoder42.QtMvvm.Core/MessageResult 1.0"] + isCreatable: false + exportMetaObjectRevisions: [0] + Property { name: "result"; type: "QVariant" } + Property { name: "autoDelete"; type: "bool" } + Signal { + name: "dialogDone" + Parameter { name: "result"; type: "QtMvvm::MessageConfig::StandardButton" } + } + Signal { + name: "autoDeleteChanged" + Parameter { name: "autoDelete"; type: "bool" } + } + Method { name: "discardMessage" } + Method { + name: "setResult" + Parameter { name: "result"; type: "QVariant" } + } + Method { + name: "setAutoDelete" + Parameter { name: "autoDelete"; type: "bool" } + } + Method { + name: "setCloseTarget" + Parameter { name: "closeObject"; type: "QObject"; isPointer: true } + Parameter { name: "closeMethod"; type: "QMetaMethod" } + } + Method { + name: "complete" + Parameter { name: "result"; type: "QtMvvm::MessageConfig::StandardButton" } + } + Method { + name: "complete" + Parameter { name: "result"; type: "QtMvvm::MessageConfig::StandardButton" } + Parameter { name: "resultValue"; type: "QVariant" } + } + } Component { name: "QtMvvm::QQmlMvvmBinding" prototype: "QObject" diff --git a/src/imports/mvvmcore/qtmvvmcore_plugin.cpp b/src/imports/mvvmcore/qtmvvmcore_plugin.cpp index 68b2fbf..7708762 100644 --- a/src/imports/mvvmcore/qtmvvmcore_plugin.cpp +++ b/src/imports/mvvmcore/qtmvvmcore_plugin.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "qqmlmvvmbinding.h" #include "qqmlmvvmmessage.h" @@ -21,8 +22,12 @@ void QtMvvmCoreDeclarativeModule::registerTypes(const char *uri) Q_ASSERT(qstrcmp(uri, "de.skycoder42.QtMvvm.Core") == 0); //Version 1.0 - qmlRegisterUncreatableType(uri, 1, 0, "ViewModel", tr("ViewModels cannot be created from QML")); + qmlRegisterUncreatableType(uri, 1, 0, "MessageConfig", QStringLiteral("Q_GADGETS cannot be created from QML")); + qmlRegisterUncreatableType(uri, 1, 0, "MessageResult", QStringLiteral("Message results must be passed from C++")); + qmlRegisterUncreatableType(uri, 1, 0, "ViewModel", QStringLiteral("ViewModels cannot be created from QML")); + qmlRegisterType(uri, 1, 0, "MvvmBinding"); + qmlRegisterSingletonType(uri, 1, 0, "Message", createMessageSingleton); // Check to make shure no module update is forgotten diff --git a/src/imports/mvvmquick/DialogPresenter.qml b/src/imports/mvvmquick/DialogPresenter.qml new file mode 100644 index 0000000..74c7f5e --- /dev/null +++ b/src/imports/mvvmquick/DialogPresenter.qml @@ -0,0 +1,32 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import de.skycoder42.QtMvvm.Core 1.0 + +QtObject { + id: _dialogPresenter + + property Item rootItem: null + + function showDialog(config, result) { + if(config.type == "msgbox") { + createMsgBox(config, result) + return true; + } else + return false; + } + + property Component _msgBoxComponent: Component { + MsgBox { + id: __msgBox + Component.onCompleted: __msgBox.open() + } + } + + function createMsgBox(config, result) { + var props = config.viewProperties; + props["msgConfig"] = config; + props["msgResult"] = result; + var incubator = _msgBoxComponent.incubateObject(rootItem, props); + return incubator.status !== Component.Error; + } +} diff --git a/src/imports/mvvmquick/MsgBox.qml b/src/imports/mvvmquick/MsgBox.qml new file mode 100644 index 0000000..55d9d85 --- /dev/null +++ b/src/imports/mvvmquick/MsgBox.qml @@ -0,0 +1,128 @@ +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: _msgBox + + property var msgConfig + property MessageResult msgResult + + property real extraHeight: 0 + + x: parent ? (parent.width - width) / 2 : 0 + y: parent ? deltaY() : 0 + width: parent ? Math.min(Math.max(implicitWidth, 300), parent.width - 28) : implicitWidth + height: parent ? Math.min(implicitHeight, parent.height - 28) : implicitWidth + modal: true + focus: true + closePolicy: Popup.CloseOnEscape + + 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(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 + } + } + + Label { + id: _contentLabel + text: msgConfig.text.replace(/<\/p>/g, "


") //needed because qml does not put space between paragraphs... + visible: text != "" + anchors.fill: parent + 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/QtMvvmApp.qml b/src/imports/mvvmquick/QtMvvmApp.qml index b846a80..563d3cb 100644 --- a/src/imports/mvvmquick/QtMvvmApp.qml +++ b/src/imports/mvvmquick/QtMvvmApp.qml @@ -13,13 +13,18 @@ ApplicationWindow { z: _rootStack.empty ? 10 : -10 } + PresentingStackView { + id: _rootStack + anchors.fill: parent + } + PopupPresenter { id: _rootPopup } - PresentingStackView { - id: _rootStack - anchors.fill: parent + DialogPresenter { + id: _dialogs + rootItem: _root.contentItem } function presentDrawerContent(item) { @@ -34,6 +39,10 @@ ApplicationWindow { return _rootPopup.presentPopup(contentItem, popup); } + function showDialog(config, result) { + return _dialogs.showDialog(config, result); + } + Component.onCompleted: QuickPresenter.qmlPresenter = _root onClosing: { diff --git a/src/imports/mvvmquick/TintIcon.qml b/src/imports/mvvmquick/TintIcon.qml new file mode 100644 index 0000000..c7d7575 --- /dev/null +++ b/src/imports/mvvmquick/TintIcon.qml @@ -0,0 +1,39 @@ +import QtQuick 2.10 +import QtQuick.Controls.Material 2.3 +import QtQuick.Controls.Universal 2.3 +import QtGraphicalEffects 1.0 +import de.skycoder42.QtMvvm.Quick 1.0 + +Item { + id: _tintIcon + + property size iconSize: Qt.size(24, 24) + property alias tintColor: _overlay.color + property alias source: _image.source + + Image { + id: _image + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + horizontalAlignment: Image.AlignHCenter + verticalAlignment: Image.AlignVCenter + width: iconSize.width + height: iconSize.height + sourceSize: iconSize + visible: false + } + + ColorOverlay { + id: _overlay + anchors.fill: _image + source: _image + color: { + if(QuickPresenter.currentStyle === "Material") + return Material.foreground; + else if(QuickPresenter.currentStyle === "Universal") + return Universal.foreground; + else + return "black"; + } + } +} diff --git a/src/imports/mvvmquick/mvvmquick.pro b/src/imports/mvvmquick/mvvmquick.pro index 3fae484..1ce5f77 100644 --- a/src/imports/mvvmquick/mvvmquick.pro +++ b/src/imports/mvvmquick/mvvmquick.pro @@ -18,7 +18,10 @@ QML_FILES += \ QtMvvmApp.qml \ PresentingStackView.qml \ PresenterProgress.qml \ - PopupPresenter.qml + PopupPresenter.qml \ + DialogPresenter.qml \ + TintIcon.qml \ + MsgBox.qml OTHER_FILES += qmldir diff --git a/src/imports/mvvmquick/qmldir b/src/imports/mvvmquick/qmldir index 8299951..a56f8a0 100644 --- a/src/imports/mvvmquick/qmldir +++ b/src/imports/mvvmquick/qmldir @@ -3,8 +3,12 @@ plugin declarative_mvvmquick classname QtMvvmQuickDeclarativeModule typeinfo plugins.qmltypes +internal TintIcon TintIcon.qml +internal MsgBox MsgBox.qml + PresenterProgress 1.0 PresenterProgress.qml PresentingStackView 1.0 PresentingStackView.qml PopupPresenter 1.0 PopupPresenter.qml +DialogPresenter 1.0 DialogPresenter.qml QtMvvmApp 1.0 QtMvvmApp.qml diff --git a/src/imports/mvvmquick/qqmlquickpresenter.cpp b/src/imports/mvvmquick/qqmlquickpresenter.cpp index e13adf4..25edc03 100644 --- a/src/imports/mvvmquick/qqmlquickpresenter.cpp +++ b/src/imports/mvvmquick/qqmlquickpresenter.cpp @@ -60,6 +60,25 @@ void QQmlQuickPresenter::present(ViewModel *viewModel, const QVariantHash ¶m } } +void QQmlQuickPresenter::showDialog(const MessageConfig &config, MessageResult *result) +{ + if(!_qmlPresenter) { + qmlWarning(this).space() << "No QML-Presenter registered! Unable to present dialog of type" //TODO use via define EVERYWHERE!!! + << config.type(); + return; + } + + QVariant res = false; + QMetaObject::invokeMethod(_qmlPresenter, "showDialog", Qt::DirectConnection, + Q_RETURN_ARG(QVariant, res), + Q_ARG(QVariant, QVariant::fromValue(config)), + Q_ARG(QVariant, QVariant::fromValue(result))); + if(!res.toBool()) { + qmlWarning(this).space() << "Failed to present dialog of type" + << config.type(); + } +} + void QQmlQuickPresenter::statusChanged(QQmlComponent::Status status) { auto component = qobject_cast(sender()); @@ -94,15 +113,15 @@ void QQmlQuickPresenter::statusChanged(QQmlComponent::Status status) void QQmlQuickPresenter::addObject(QQmlComponent *component, ViewModel *viewModel, const QVariantHash ¶ms, QPointer parent) { if(!_qmlPresenter) { - qCritical() << "No QML-Presenter registered! Unable to present control of type" - << viewModel->metaObject()->className(); + qmlWarning(this).space() << "No QML-Presenter registered! Unable to present viewModel of type" + << viewModel->metaObject()->className(); return; } //create the view item, set initial stuff and them complete creation auto item = component->beginCreate(_engine->rootContext()); if(!item) { - qmlWarning(this) << "Unable to create quick view from the loaded component" + qmlWarning(this).space() << "Unable to create quick view from the loaded component" << component->url(); return; } @@ -122,7 +141,7 @@ void QQmlQuickPresenter::addObject(QQmlComponent *component, ViewModel *viewMode if(!item->parent()) QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership); } else { - qmlWarning(this) << "Failed to present item for viewModel of type" + qmlWarning(this).space() << "Failed to present item for viewModel of type" << viewModel->metaObject()->className(); item->deleteLater(); } diff --git a/src/imports/mvvmquick/qqmlquickpresenter.h b/src/imports/mvvmquick/qqmlquickpresenter.h index 025d9f8..e940028 100644 --- a/src/imports/mvvmquick/qqmlquickpresenter.h +++ b/src/imports/mvvmquick/qqmlquickpresenter.h @@ -12,6 +12,7 @@ #include #include +#include namespace QtMvvm { @@ -40,6 +41,7 @@ Q_SIGNALS: private Q_SLOTS: void present(QtMvvm::ViewModel *viewModel, const QVariantHash ¶ms, const QUrl &viewUrl, QPointer parent); + void showDialog(const QtMvvm::MessageConfig &config, QtMvvm::MessageResult *result); void statusChanged(QQmlComponent::Status status); private: diff --git a/src/mvvmcore/message.cpp b/src/mvvmcore/message.cpp index 8966582..3d18eb1 100644 --- a/src/mvvmcore/message.cpp +++ b/src/mvvmcore/message.cpp @@ -158,6 +158,22 @@ void MessageConfig::resetButtons() d->buttonTexts.clear(); } +QVariantMap MessageConfig::buttonTextsMap() const +{ + QVariantMap map; + for(auto it = d->buttonTexts.constBegin(); it != d->buttonTexts.constEnd(); it++) + map.insert(QString::number(it.key()), it.value()); + return map; +} + +void MessageConfig::setButtonTextsMap(const QVariantMap &buttonTexts) +{ + QHash map; + for(auto it = buttonTexts.constBegin(); it != buttonTexts.constEnd(); it++) + map.insert(static_cast(it.key().toInt()), it.value().toString()); + d->buttonTexts = map; +} + MessageResult::MessageResult() : diff --git a/src/mvvmcore/message.h b/src/mvvmcore/message.h index e143e0a..21f8605 100644 --- a/src/mvvmcore/message.h +++ b/src/mvvmcore/message.h @@ -25,7 +25,7 @@ class Q_MVVMCORE_EXPORT MessageConfig Q_PROPERTY(QString title READ title WRITE setTitle) Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(StandardButtons buttons READ buttons WRITE setButtons RESET resetButtons) - Q_PROPERTY(QHash buttonTexts READ buttonTexts WRITE setButtonTexts RESET resetButtons) + Q_PROPERTY(QVariantMap buttonTexts READ buttonTextsMap WRITE setButtonTextsMap RESET resetButtons) Q_PROPERTY(QVariant defaultValue READ defaultValue WRITE setDefaultValue) Q_PROPERTY(QVariantMap viewProperties READ viewProperties WRITE setViewProperties) @@ -103,6 +103,9 @@ public: private: QSharedDataPointer d; + + QVariantMap buttonTextsMap() const; + void setButtonTextsMap(const QVariantMap &buttonTexts); }; class MessageResultPrivate; diff --git a/src/mvvmquick/quickpresenter.cpp b/src/mvvmquick/quickpresenter.cpp index a5478c1..58941c3 100644 --- a/src/mvvmquick/quickpresenter.cpp +++ b/src/mvvmquick/quickpresenter.cpp @@ -46,7 +46,12 @@ void QuickPresenter::present(QtMvvm::ViewModel *viewModel, const QVariantHash &p void QuickPresenter::showDialog(const QtMvvm::MessageConfig &config, QtMvvm::MessageResult *result) { - qDebug(Q_FUNC_INFO); + if(d->qmlPresenter) { + QMetaObject::invokeMethod(d->qmlPresenter, "showDialog", + Q_ARG(QtMvvm::MessageConfig, config), + Q_ARG(QtMvvm::MessageResult*, result)); + } else + throw PresenterException("QML presenter not ready - cannot present yet"); } bool QuickPresenter::presentToQml(QObject *qmlPresenter, QObject *viewObject)