From 54918cdcc51217ffee9c0d80cdd4e5b7ea1060d1 Mon Sep 17 00:00:00 2001 From: Shyamnath Premnadh Date: Fri, 20 Oct 2023 14:47:47 +0200 Subject: PySide Examples: Add Contactslist example - Also works for Android Pick-to: 6.5 Task-number: PYSIDE-2206 Change-Id: Ib41b004a343c64a355187c9ef1780a8da4bd0553 Reviewed-by: Friedemann Kleint (cherry picked from commit e7e46d0516e079c20c77da12e1cb63d0973fa648) Reviewed-by: Qt Cherry-pick Bot --- .../contactslist/Contact/ContactDelegate.ui.qml | 82 +++++++++++++++ .../contactslist/Contact/ContactDialog.qml | 45 ++++++++ .../contactslist/Contact/ContactForm.ui.qml | 72 +++++++++++++ .../contactslist/Contact/ContactList.qml | 70 +++++++++++++ .../contactslist/Contact/ContactView.ui.qml | 36 +++++++ .../contactslist/Contact/SectionDelegate.ui.qml | 17 +++ examples/quickcontrols/contactslist/Contact/qmldir | 7 ++ .../contactslist/contactlist.pyproject | 10 ++ .../quickcontrols/contactslist/contactmodel.py | 116 +++++++++++++++++++++ .../contactslist/doc/contactslist.rst | 13 +++ .../doc/qtquickcontrols-contactlist.png | Bin 0 -> 23581 bytes examples/quickcontrols/contactslist/main.py | 28 +++++ 12 files changed, 496 insertions(+) create mode 100644 examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml create mode 100644 examples/quickcontrols/contactslist/Contact/ContactDialog.qml create mode 100644 examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml create mode 100644 examples/quickcontrols/contactslist/Contact/ContactList.qml create mode 100644 examples/quickcontrols/contactslist/Contact/ContactView.ui.qml create mode 100644 examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml create mode 100644 examples/quickcontrols/contactslist/Contact/qmldir create mode 100644 examples/quickcontrols/contactslist/contactlist.pyproject create mode 100644 examples/quickcontrols/contactslist/contactmodel.py create mode 100644 examples/quickcontrols/contactslist/doc/contactslist.rst create mode 100644 examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png create mode 100644 examples/quickcontrols/contactslist/main.py (limited to 'examples') diff --git a/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml new file mode 100644 index 000000000..affcccc3e --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml @@ -0,0 +1,82 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +ItemDelegate { + id: delegate + + checkable: true + + contentItem: ColumnLayout { + spacing: 10 + + Label { + text: fullName + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + + GridLayout { + id: grid + visible: false + + columns: 2 + rowSpacing: 10 + columnSpacing: 10 + + Label { + text: qsTr("Address:") + Layout.leftMargin: 60 + } + + Label { + text: address + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + + Label { + text: qsTr("City:") + Layout.leftMargin: 60 + } + + Label { + text: city + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + + Label { + text: qsTr("Number:") + Layout.leftMargin: 60 + } + + Label { + text: number + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + } + } + + states: [ + State { + name: "expanded" + when: delegate.checked + + PropertyChanges { + // TODO: When Qt Design Studio supports generalized grouped properties, change to: + // grid.visible: true + target: grid + visible: true + } + } + ] +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactDialog.qml b/examples/quickcontrols/contactslist/Contact/ContactDialog.qml new file mode 100644 index 000000000..d906f00e6 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactDialog.qml @@ -0,0 +1,45 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +Dialog { + id: dialog + + signal finished(string fullName, string address, string city, string number) + + function createContact() { + form.fullName.clear(); + form.address.clear(); + form.city.clear(); + form.number.clear(); + + dialog.title = qsTr("Add Contact"); + dialog.open(); + } + + function editContact(contact) { + form.fullName.text = contact.fullName; + form.address.text = contact.address; + form.city.text = contact.city; + form.number.text = contact.number; + + dialog.title = qsTr("Edit Contact"); + dialog.open(); + } + + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + + focus: true + modal: true + title: qsTr("Add Contact") + standardButtons: Dialog.Ok | Dialog.Cancel + + contentItem: ContactForm { + id: form + } + + onAccepted: finished(form.fullName.text, form.address.text, form.city.text, form.number.text) +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml new file mode 100644 index 000000000..56c918619 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml @@ -0,0 +1,72 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +GridLayout { + id: grid + property alias fullName: fullName + property alias address: address + property alias city: city + property alias number: number + property int minimumInputSize: 120 + property string placeholderText: qsTr("") + + rows: 4 + columns: 2 + + Label { + text: qsTr("Full Name") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: fullName + focus: true + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } + + Label { + text: qsTr("Address") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: address + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } + + Label { + text: qsTr("City") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: city + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } + + Label { + text: qsTr("Number") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: number + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactList.qml b/examples/quickcontrols/contactslist/Contact/ContactList.qml new file mode 100644 index 000000000..0b7af32b5 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactList.qml @@ -0,0 +1,70 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + + property int currentContact: -1 + + width: 320 + height: 480 + visible: true + title: qsTr("Contact List") + + ContactDialog { + id: contactDialog + onFinished: function(fullName, address, city, number) { + if (currentContact == -1) + contactView.model.append(fullName, address, city, number) + else + contactView.model.set(currentContact, fullName, address, city, number) + } + } + + Menu { + id: contactMenu + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + modal: true + + Label { + padding: 10 + font.bold: true + width: parent.width + horizontalAlignment: Qt.AlignHCenter + text: currentContact >= 0 ? contactView.model.get(currentContact).fullName : "" + } + MenuItem { + text: qsTr("Edit...") + onTriggered: contactDialog.editContact(contactView.model.get(currentContact)) + } + MenuItem { + text: qsTr("Remove") + onTriggered: contactView.model.remove(currentContact) + } + } + + ContactView { + id: contactView + anchors.fill: parent + onPressAndHold: { + currentContact = index + contactMenu.open() + } + } + + RoundButton { + text: qsTr("+") + highlighted: true + anchors.margins: 10 + anchors.right: parent.right + anchors.bottom: parent.bottom + onClicked: { + currentContact = -1 + contactDialog.createContact() + } + } +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml new file mode 100644 index 000000000..3b82b681e --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml @@ -0,0 +1,36 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import Backend + +ListView { + id: listView + + signal pressAndHold(int index) + + width: 320 + height: 480 + + focus: true + boundsBehavior: Flickable.StopAtBounds + + section.property: "fullName" + section.criteria: ViewSection.FirstCharacter + section.delegate: SectionDelegate { + width: listView.width + } + + delegate: ContactDelegate { + id: delegate + width: listView.width + onPressAndHold: listView.pressAndHold(index) + } + + model: ContactModel { + id: contactModel + } + + ScrollBar.vertical: ScrollBar { } +} diff --git a/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml b/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml new file mode 100644 index 000000000..3a62409a8 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml @@ -0,0 +1,17 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ToolBar { + id: background + + Label { + id: label + text: section + anchors.fill: parent + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } +} diff --git a/examples/quickcontrols/contactslist/Contact/qmldir b/examples/quickcontrols/contactslist/Contact/qmldir new file mode 100644 index 000000000..339d45a1d --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/qmldir @@ -0,0 +1,7 @@ +module Contact +ContactList 1.0 ContactList.qml +ContactDialog 1.0 ContactDialog.qml +ContactDelegate 1.0 ContactDelegate.ui.qml +ContactForm 1.0 ContactForm.ui.qml +ContactView 1.0 ContactView.ui.qml +SectionDelegate 1.0 SectionDelegate.ui.qml diff --git a/examples/quickcontrols/contactslist/contactlist.pyproject b/examples/quickcontrols/contactslist/contactlist.pyproject new file mode 100644 index 000000000..75b0bd693 --- /dev/null +++ b/examples/quickcontrols/contactslist/contactlist.pyproject @@ -0,0 +1,10 @@ +{ + "files": ["main.py", + "contactmodel.py", + "Contact/ContactDialog.qml", + "Contact/ContactDelegate.ui.qml", + "Contact/ContactForm.ui.qml", + "Contact/ContactList.qml", + "Contact/ContactView.ui.qml", + "Contact/SectionDelegate.ui.qml"] +} diff --git a/examples/quickcontrols/contactslist/contactmodel.py b/examples/quickcontrols/contactslist/contactmodel.py new file mode 100644 index 000000000..313a58305 --- /dev/null +++ b/examples/quickcontrols/contactslist/contactmodel.py @@ -0,0 +1,116 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import bisect +from dataclasses import dataclass +from enum import IntEnum + +from PySide6.QtCore import (QAbstractListModel, QEnum, Qt, QModelIndex, Slot, + QByteArray) +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "Backend" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class ContactModel(QAbstractListModel): + + @QEnum + class ContactRole(IntEnum): + FullNameRole = Qt.DisplayRole + AddressRole = Qt.UserRole + CityRole = Qt.UserRole + 1 + NumberRole = Qt.UserRole + 2 + + @dataclass + class Contact: + fullName: str + address: str + city: str + number: str + + def __init__(self, parent=None) -> None: + super().__init__(parent) + self.m_contacts = [] + self.m_contacts.append(self.Contact("Angel Hogan", "Chapel St. 368 ", "Clearwater", + "0311 1823993")) + self.m_contacts.append(self.Contact("Felicia Patton", "Annadale Lane 2", "Knoxville", + "0368 1244494")) + self.m_contacts.append(self.Contact("Grant Crawford", "Windsor Drive 34", "Riverdale", + "0351 7826892")) + self.m_contacts.append(self.Contact("Gretchen Little", "Sunset Drive 348", "Virginia Beach", + "0343 1234991")) + self.m_contacts.append(self.Contact("Geoffrey Richards", "University Lane 54", "Trussville", + "0423 2144944")) + self.m_contacts.append(self.Contact("Henrietta Chavez", "Via Volto San Luca 3", + "Piobesi Torinese", "0399 2826994")) + self.m_contacts.append(self.Contact("Harvey Chandler", "North Squaw Creek 11", + "Madisonville", "0343 1244492")) + self.m_contacts.append(self.Contact("Miguel Gomez", "Wild Rose Street 13", "Trussville" , + "0343 9826996")) + self.m_contacts.append(self.Contact("Norma Rodriguez", " Glen Eagles Street 53", + "Buffalo", "0241 5826596")) + self.m_contacts.append(self.Contact("Shelia Ramirez", "East Miller Ave 68", "Pickerington", + "0346 4844556")) + self.m_contacts.append(self.Contact("Stephanie Moss", "Piazza Trieste e Trento 77", + "Roata Chiusani", "0363 0510490")) + + def rowCount(self, parent=QModelIndex()): + return len(self.m_contacts) + + def data(self, index: QModelIndex, role: int): + row = index.row() + if row < self.rowCount(): + if role == ContactModel.ContactRole.FullNameRole: + return self.m_contacts[row].fullName + elif role == ContactModel.ContactRole.AddressRole: + return self.m_contacts[row].address + elif role == ContactModel.ContactRole.CityRole: + return self.m_contacts[row].city + elif role == ContactModel.ContactRole.NumberRole: + return self.m_contacts[row].number + + def roleNames(self): + default = super().roleNames() + default[ContactModel.ContactRole.FullNameRole] = QByteArray(b"fullName") + default[ContactModel.ContactRole.AddressRole] = QByteArray(b"address") + default[ContactModel.ContactRole.CityRole] = QByteArray(b"city") + default[ContactModel.ContactRole.NumberRole] = QByteArray(b"number") + return default + + @Slot(int) + def get(self, row: int): + contact = self.m_contacts[row] + return {"fullName": contact.fullName, "address": contact.address, + "city": contact.city, "number": contact.number} + + @Slot(str, str, str, str) + def append(self, full_name: str, address: str, city: str, number: str): + contact = self.Contact(full_name, address, city, number) + contact_names = [contact.fullName for contact in self.m_contacts] + index = bisect.bisect(contact_names, contact.fullName) + self.beginInsertRows(QModelIndex(), index, index) + self.m_contacts.insert(index, contact) + self.endInsertRows() + + @Slot(int, str, str, str, str) + def set(self, row: int, full_name: str, address: str, city: str, number: str): + if row < 0 or row >= len(self.m_contacts): + return + + self.m_contacts[row] = self.Contact(full_name, address, city, number) + self.dataChanged(self.index(row, 0), self.index(row, 0), + [ContactModel.ContactRole.FullNameRole, + ContactModel.ContactRole.AddressRole, + ContactModel.ContactRole.CityRole, + ContactModel.ContactRole.NumberRole]) + + @Slot(int) + def remove(self, row): + if row < 0 or row >= len(self.m_contacts): + return + + self.beginRemoveRows(QModelIndex(), row, row) + del self.m_contacts[row] + self.endRemoveRows() diff --git a/examples/quickcontrols/contactslist/doc/contactslist.rst b/examples/quickcontrols/contactslist/doc/contactslist.rst new file mode 100644 index 000000000..918e23760 --- /dev/null +++ b/examples/quickcontrols/contactslist/doc/contactslist.rst @@ -0,0 +1,13 @@ +Qt Quick Controls - Contact List +================================ + +A QML app using Qt Quick Controls and a Python class that implements a simple +contact list. This example can also be deployed to Android using +**pyside6-android-deploy** + +A PySide6 application that demonstrates the analogous example in Qt +`ContactsList `_ + +.. image:: qtquickcontrols-contactlist.png + :width: 400 + :alt: ContactList Screenshot diff --git a/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png b/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png new file mode 100644 index 000000000..9f1c30654 Binary files /dev/null and b/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png differ diff --git a/examples/quickcontrols/contactslist/main.py b/examples/quickcontrols/contactslist/main.py new file mode 100644 index 000000000..58b139cac --- /dev/null +++ b/examples/quickcontrols/contactslist/main.py @@ -0,0 +1,28 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +""" +PySide6 port of Qt Quick Controls Contact List example from Qt v6.x +""" +import sys +from pathlib import Path +from PySide6.QtCore import QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + +from contactmodel import ContactModel + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + app.setOrganizationName("QtProject") + app.setApplicationName("ContactsList") + engine = QQmlApplicationEngine() + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("Contact", "ContactList") + + if not engine.rootObjects(): + sys.exit(-1) + + del engine + sys.exit(app.exec()) -- cgit v1.2.3