diff --git a/examples/galleryapp/resources.qrc b/examples/galleryapp/resources.qrc --- a/examples/galleryapp/resources.qrc +++ b/examples/galleryapp/resources.qrc @@ -18,6 +18,7 @@ ../gallerydata/contents/ui/gallery/ColorsGallery.qml ../gallerydata/contents/ui/gallery/MetricsGallery.qml ../gallerydata/contents/ui/gallery/LayersGallery.qml + ../gallerydata/contents/ui/gallery/FormLayoutGallery.qml ../gallerydata/contents/ui/ExampleApp.qml ../gallerydata/contents/ui/gallery/ColorSetGallery.qml ../gallerydata/contents/ui/DesktopExampleApp.qml diff --git a/examples/gallerydata/contents/ui/MainPage.qml b/examples/gallerydata/contents/ui/MainPage.qml --- a/examples/gallerydata/contents/ui/MainPage.qml +++ b/examples/gallerydata/contents/ui/MainPage.qml @@ -99,6 +99,10 @@ component: "TextField" } ListElement { + text: "Form Layout" + component: "FormLayout" + } + ListElement { text: "Multiple Columns" component: "MultipleColumns" } diff --git a/examples/gallerydata/contents/ui/gallery/FormLayoutGallery.qml b/examples/gallerydata/contents/ui/gallery/FormLayoutGallery.qml new file mode 100644 --- /dev/null +++ b/examples/gallerydata/contents/ui/gallery/FormLayoutGallery.qml @@ -0,0 +1,80 @@ + + +import QtQuick 2.6 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 +import org.kde.kirigami 2.3 as Kirigami + +Kirigami.ScrollablePage { + title: "Form Layout" + + Kirigami.FormLayout { + width: 500 + height: 500 + TextField { + id: uh + Kirigami.FormData.label: "Label:" + } + TextField { + id: uh2 + } + TextField { + id: uh3 + Kirigami.FormData.label:"Longer label:" + } + Kirigami.Separator { + Kirigami.FormData.isSection: true + } + TextField { + Kirigami.FormData.label: "After separator:" + } + ComboBox { + Kirigami.FormData.label: "Combo:" + model: ["First", "Second", "Third"] + } + CheckBox { + checked: true + text: "Option" + } + Kirigami.Separator { + Kirigami.FormData.isSection: true + Kirigami.FormData.label: "Section title" + } + TextField { + Kirigami.FormData.label: "Label:" + } + Item { + width:1 + height:1 + Kirigami.FormData.isSection: true + } + TextField { + Kirigami.FormData.label: "Section without line:" + } + TextField { + } + Item { + width:1 + height:1 + Kirigami.FormData.isSection: true + Kirigami.FormData.label: "Section with title without line" + } + TextField { + Kirigami.FormData.label: "Title:" + } + ColumnLayout { + Layout.rowSpan: 3 + Kirigami.FormData.label: "Label for radios:" + RadioButton { + checked: true + text: "One" + } + RadioButton { + text: "Two" + } + RadioButton { + text: "Three" + } + } + } +} diff --git a/examples/gallerydata/contents/ui/gallery/TextFieldGallery.qml b/examples/gallerydata/contents/ui/gallery/TextFieldGallery.qml --- a/examples/gallerydata/contents/ui/gallery/TextFieldGallery.qml +++ b/examples/gallerydata/contents/ui/gallery/TextFieldGallery.qml @@ -20,49 +20,43 @@ import QtQuick 2.0 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 -import org.kde.kirigami 2.2 +import org.kde.kirigami 2.3 as Kirigami -ScrollablePage { +Kirigami.ScrollablePage { id: page Layout.fillWidth: true implicitWidth: applicationWindow().width title: "Text fields" ColumnLayout { - objectName: "pollo" - width: page.width - spacing: Units.smallSpacing - - Controls.Label { - text: "Placeholder text:" - } - Controls.TextField { - placeholderText: "Search..." - Layout.alignment: Qt.AlignHCenter - } - Controls.Label { - text: "Disabled field:" - } - Controls.TextField { - text: "Disabled" - enabled: false - Layout.alignment: Qt.AlignHCenter - } - Controls.Label { - text: "Password:" - } - Controls.TextField { - echoMode: TextInput.Password - Layout.alignment: Qt.AlignHCenter - } - Controls.Label { - text: "Numbers:" - } - Controls.TextField { - inputMask: "99999999" - inputMethodHints: Qt.ImhDigitsOnly + Kirigami.FormLayout { Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + + width: page.width + spacing: Units.smallSpacing + + Controls.TextField { + placeholderText: "Search..." + Kirigami.FormData.label: "Placeholder text:" + } + Controls.TextField { + text: "Disabled" + enabled: false + Kirigami.FormData.label: "Disabled field:" + } + Controls.TextField { + echoMode: TextInput.Password + Kirigami.FormData.label: "Password:" + } + + Controls.TextField { + inputMask: "99999999" + inputMethodHints: Qt.ImhDigitsOnly + Kirigami.FormData.label: "Numbers:" + } } + Controls.Label { text: "Text area:" } @@ -81,8 +75,8 @@ id: field anchors.fill: parent text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent eu nisl ac nibh malesuada pretium ut sit amet libero. Nulla libero arcu, pharetra a dignissim nec, iaculis sit amet metus. Suspendisse quis justo efficitur, pharetra dui maximus, aliquam dolor. Vestibulum vel imperdiet turpis. Mauris ut leo mauris. Praesent ut libero sollicitudin, tincidunt nisi a, efficitur erat. Curabitur lacinia leo et tempor aliquam." - Layout.minimumWidth: Units.gridUnit * 12 - Layout.minimumHeight: Units.gridUnit * 12 + Layout.minimumWidth: Kirigami.Units.gridUnit * 12 + Layout.minimumHeight: Kirigami.Units.gridUnit * 12 wrapMode: Controls.TextArea.WordWrap } } diff --git a/kirigami.qrc b/kirigami.qrc --- a/kirigami.qrc +++ b/kirigami.qrc @@ -44,6 +44,7 @@ src/controls/Label.qml src/controls/BasicListItem.qml src/controls/AbstractApplicationHeader.qml + src/controls/FormLayout.qml src/styles/Plasma/Theme.qml src/styles/Plasma/Units.qml src/styles/Plasma/Icon.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ enums.cpp desktopicon.cpp settings.cpp + formlayoutattached.cpp ${kirigami_QM_LOADER} ${KIRIGAMI_STATIC_FILES} ) diff --git a/src/controls/FormLayout.qml b/src/controls/FormLayout.qml new file mode 100644 --- /dev/null +++ b/src/controls/FormLayout.qml @@ -0,0 +1,94 @@ + + +import QtQuick 2.6 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 +import org.kde.kirigami 2.3 as Kirigami + +Control { + id: root + implicitWidth: lay.implicitWidth + implicitHeight: lay.implicitHeight + Layout.preferredHeight: lay.implicitHeight + property bool wideMode: applicationWindow() ? applicationWindow().wideScreen : width > Kirigami.Units.gridUnit * 15 + + GridLayout { + id: lay + columns: root.wideMode ? 2 : 1 + rowSpacing: Kirigami.Units.smallSpacing + columnSpacing: Kirigami.Units.smallSpacing + anchors { + left: parent.left + top: parent.top + right: parent.right + } + } + default property list __items + on__ItemsChanged: { + for (var i = 0; i < __items.length; ++i) { + var item = __items[i]; + + if (item.parent && item.parent.parent == lay) { + continue; + } + + var itemContainer = itemComponent.createObject(root, {"item": item}) + item.parent = itemContainer; + item.anchors.fill = itemContainer; + //if section, label goes after the separator + if (item.Kirigami.FormData.isSection) { + //put an extra spacer + var placeHolder = placeHolderComponent.createObject(lay); + placeHolder.Layout.colSpan = 2; + placeHolder.parent = lay; + itemContainer.parent = lay; + } + + var buddy = buddyComponent.createObject(lay, {"formData": item.Kirigami.FormData}) + buddy.parent = lay; + + + itemContainer.parent = lay; + //if section, wee need another placeholder + if (item.Kirigami.FormData.isSection) { + var placeHolder = placeHolderComponent.createObject(lay); + placeHolder.parent = lay; + } + } + } + Component.onCompleted: __itemsChanged() + Component { + id: itemComponent + Item { + property var item + implicitWidth: item.implicitWidth + Layout.preferredHeight: Math.max(item.Layout.preferredHeight, item.implicitHeight) + + Layout.alignment: (root.wideMode ? Qt.AlignLeft : Qt.AlignHCenter) | Qt.AlignVCenter + Layout.fillWidth: item.Kirigami.FormData.isSection + Layout.columnSpan: item.Kirigami.FormData.isSection ? lay.columns : 1 + } + } + Component { + id: placeHolderComponent + Item { + width: Kirigami.Units.smallSpacing + height: Kirigami.Units.smallSpacing + } + } + Component { + id: buddyComponent + Label { + property var formData + width: 1 + text: formData.label + + font.pointSize: formData.isSection ? Kirigami.Theme.defaultFont.pointSize * 1.2 : Kirigami.Theme.defaultFont.pointSize + font.weight: formData.isSection ? Font.Light : Font.Normal + + Layout.preferredHeight: formData.label.length > 0 ? implicitHeight : Kirigami.Units.smallSpacing + Layout.alignment: (root.wideMode ? Qt.AlignRight : Qt.AlignLeft) | (formData.buddyFor.height > height * 2 ? Qt.AlignTop : Qt.AlignVCenter) + Layout.topMargin: formData.buddyFor.height > height * 2 ? Kirigami.Units.smallSpacing : 0 + } + } +} diff --git a/src/formlayoutattached.h b/src/formlayoutattached.h new file mode 100644 --- /dev/null +++ b/src/formlayoutattached.h @@ -0,0 +1,65 @@ +/* +* Copyright (C) 2017 by Marco Martin +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Library General Public License as +* published by the Free Software Foundation; either version 2, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Library General Public License for more details +* +* You should have received a copy of the GNU Library General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef FORMLAYOUTATTACHED_H +#define FORMLAYOUTATTACHED_H + +#include +#include + +class QQuickItem; + +class FormLayoutAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) + Q_PROPERTY(bool isSection READ isSection WRITE setIsSection NOTIFY isSectionChanged) + Q_PROPERTY(QQuickItem *buddyFor READ buddyFor WRITE setBuddyFor NOTIFY buddyForChanged) + +public: + + explicit FormLayoutAttached(QObject *parent = 0); + ~FormLayoutAttached(); + + void setLabel(const QString &text); + QString label() const; + + void setIsSection(bool section); + bool isSection() const; + + void setBuddyFor(QQuickItem *item); + QQuickItem *buddyFor() const; + + //QML attached property + static FormLayoutAttached *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void labelChanged(); + void isSectionChanged(); + void buddyForChanged(); + +private: + QString m_label; + QPointer m_buddyFor; + bool m_isSection = false; +}; + +QML_DECLARE_TYPEINFO(FormLayoutAttached, QML_HAS_ATTACHED_PROPERTIES) + +#endif // FORMLAYOUTATTACHED_H diff --git a/src/formlayoutattached.cpp b/src/formlayoutattached.cpp new file mode 100644 --- /dev/null +++ b/src/formlayoutattached.cpp @@ -0,0 +1,84 @@ +/* +* Copyright (C) 2017 by Marco Martin +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Library General Public License as +* published by the Free Software Foundation; either version 2, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Library General Public License for more details +* +* You should have received a copy of the GNU Library General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "formlayoutattached.h" +#include +#include + +FormLayoutAttached::FormLayoutAttached(QObject *parent) + : QObject(parent) +{ + m_buddyFor = qobject_cast(parent); +} + +FormLayoutAttached::~FormLayoutAttached() +{ +} + +void FormLayoutAttached::setLabel(const QString &text) +{ + if (m_label == text) { + return; + } + + m_label = text; + emit labelChanged(); +} + +QString FormLayoutAttached::label() const +{ + return m_label; +} + +void FormLayoutAttached::setIsSection(bool section) +{ + if (m_isSection == section) { + return; + } + + m_isSection = section; + emit isSectionChanged(); +} + +bool FormLayoutAttached::isSection() const +{ + return m_isSection; +} + +void FormLayoutAttached::setBuddyFor(QQuickItem *item) +{ + if (m_buddyFor == item) { + return; + } + + m_buddyFor = item; + emit buddyForChanged(); +} + +QQuickItem *FormLayoutAttached::buddyFor() const +{ + return m_buddyFor; +} + +FormLayoutAttached *FormLayoutAttached::qmlAttachedProperties(QObject *object) +{ + return new FormLayoutAttached(object); +} + +#include "moc_formlayoutattached.cpp" diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -23,6 +23,7 @@ #include "enums.h" #include "desktopicon.h" #include "settings.h" +#include "formlayoutattached.h" #include #include @@ -88,9 +89,9 @@ qmlRegisterUncreatableType(uri, 2, 0, "ApplicationHeaderStyle", "Cannot create objects of type ApplicationHeaderStyle"); + //old legacy retrocompatible Theme qmlRegisterSingletonType(componentUrl(QStringLiteral("Theme.qml")), uri, 2, 0, "Theme"); - //Theme changed from a singleton to an attached property - qmlRegisterUncreatableType(uri, 2, 2, "Theme", "Cannot create objects of type Theme, use it as an attached poperty"); + qmlRegisterSingletonType(componentUrl(QStringLiteral("Units.qml")), uri, 2, 0, "Units"); qmlRegisterType(componentUrl(QStringLiteral("Action.qml")), uri, 2, 0, "Action"); @@ -135,6 +136,14 @@ qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationItem.qml")), uri, 2, 1, "AbstractApplicationItem"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationItem.qml")), uri, 2, 1, "ApplicationItem"); + //2.2 + //Theme changed from a singleton to an attached property + qmlRegisterUncreatableType(uri, 2, 2, "Theme", "Cannot create objects of type Theme, use it as an attached poperty"); + + //2.3 + qmlRegisterType(componentUrl(QStringLiteral("FormLayout.qml")), uri, 2, 3, "FormLayout"); + qmlRegisterUncreatableType(uri, 2, 3, "FormData", "Cannot create objects of type FormData, use it as an attached poperty"); + qmlProtectModule(uri, 2); }