From 5ff199cc3e0f8ecfb2e37c39a0f1a1614fe32238 Mon Sep 17 00:00:00 2001 From: James Graham Date: Sat, 16 Mar 2024 09:28:30 +0000 Subject: [PATCH] Itinerary Component Move the itinerary model representation to it's own component and instantiate from MessageComponentModel. This starts to lay some groundwork for previewing other files. --- src/CMakeLists.txt | 1 + src/enums/messagecomponenttype.h | 1 + src/models/itinerarymodel.cpp | 19 +---- src/models/itinerarymodel.h | 15 +--- src/models/messagecontentmodel.cpp | 34 +++++++++ src/models/messagecontentmodel.h | 5 ++ src/qml/FileComponent.qml | 90 +---------------------- src/qml/ItineraryComponent.qml | 109 ++++++++++++++++++++++++++++ src/qml/MessageComponentChooser.qml | 7 ++ 9 files changed, 161 insertions(+), 120 deletions(-) create mode 100644 src/qml/ItineraryComponent.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 53d873c24..c0595499c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -338,6 +338,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN qml/StateKeys.qml qml/CodeComponent.qml qml/QuoteComponent.qml + qml/ItineraryComponent.qml RESOURCES qml/confetti.png qml/glowdot.png diff --git a/src/enums/messagecomponenttype.h b/src/enums/messagecomponenttype.h index fb86ed280..b9a6722a7 100644 --- a/src/enums/messagecomponenttype.h +++ b/src/enums/messagecomponenttype.h @@ -40,6 +40,7 @@ public: Code, /**< A code section. */ Quote, /**< A quote section. */ File, /**< A message that is a file. */ + Itinerary, /**< A preview for a file that can integrate with KDE itinerary.. */ Poll, /**< The initial event for a poll. */ Location, /**< A location event. */ LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */ diff --git a/src/models/itinerarymodel.cpp b/src/models/itinerarymodel.cpp index 8dd32b602..1a8870b94 100644 --- a/src/models/itinerarymodel.cpp +++ b/src/models/itinerarymodel.cpp @@ -3,6 +3,7 @@ #include "itinerarymodel.h" +#include #include #include "config-neochat.h" @@ -16,20 +17,6 @@ ItineraryModel::ItineraryModel(QObject *parent) { } -void ItineraryModel::setConnection(NeoChatConnection *connection) -{ - if (m_connection == connection) { - return; - } - m_connection = connection; - Q_EMIT connectionChanged(); -} - -NeoChatConnection *ItineraryModel::connection() const -{ - return m_connection; -} - QVariant ItineraryModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { @@ -133,11 +120,7 @@ QString ItineraryModel::path() const void ItineraryModel::setPath(const QString &path) { - if (path == m_path) { - return; - } m_path = path; - Q_EMIT pathChanged(); loadData(); } diff --git a/src/models/itinerarymodel.h b/src/models/itinerarymodel.h index 6501c96dc..b5f1c5892 100644 --- a/src/models/itinerarymodel.h +++ b/src/models/itinerarymodel.h @@ -4,19 +4,16 @@ #pragma once #include +#include #include #include #include -#include "neochatconnection.h" - class ItineraryModel : public QAbstractListModel { Q_OBJECT QML_ELEMENT - - Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged) - Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + QML_UNCREATABLE("") public: enum Roles { @@ -37,9 +34,6 @@ public: Q_ENUM(Roles) explicit ItineraryModel(QObject *parent = nullptr); - void setConnection(NeoChatConnection *connection); - NeoChatConnection *connection() const; - QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = {}) const override; @@ -50,12 +44,7 @@ public: Q_INVOKABLE void sendToItinerary(); -Q_SIGNALS: - void connectionChanged(); - void pathChanged(); - private: - QPointer m_connection; QJsonArray m_data; QString m_path; void loadData(); diff --git a/src/models/messagecontentmodel.cpp b/src/models/messagecontentmodel.cpp index d8dca4134..55bc1adf2 100644 --- a/src/models/messagecontentmodel.cpp +++ b/src/models/messagecontentmodel.cpp @@ -64,11 +64,13 @@ MessageContentModel::MessageContentModel(const Quotient::RoomEvent *event, NeoCh }); connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) { if (m_event != nullptr && eventId == m_event->id()) { + updateComponents(); Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); } }); connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) { if (m_event != nullptr && eventId == m_event->id()) { + updateComponents(); Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); } }); @@ -152,6 +154,9 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const return QVariant::fromValue(m_room->fileTransferInfo(event->id())); } } + if (role == ItineraryModelRole) { + return QVariant::fromValue(m_itineraryModel); + } if (role == LatitudeRole) { return eventHandler.getLatitude(); } @@ -209,6 +214,7 @@ QHash MessageContentModel::roleNames() const roles[AuthorRole] = "author"; roles[MediaInfoRole] = "mediaInfo"; roles[FileTransferInfoRole] = "fileTransferInfo"; + roles[ItineraryModelRole] = "itineraryModel"; roles[LatitudeRole] = "latitude"; roles[LongitudeRole] = "longitude"; roles[AssetRole] = "asset"; @@ -247,6 +253,12 @@ void MessageContentModel::updateComponents(bool isEditing) const auto event = eventCast(m_event); auto body = EventHandler::rawMessageBody(*event); m_components.append(TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced())); + } else if (eventHandler.messageComponentType() == MessageComponentType::File) { + m_components += MessageComponent{MessageComponentType::File, QString(), {}}; + updateItineraryModel(); + if (m_itineraryModel != nullptr) { + m_components += MessageComponent{MessageComponentType::Itinerary, QString(), {}}; + } } else { m_components += MessageComponent{eventHandler.messageComponentType(), QString(), {}}; } @@ -262,3 +274,25 @@ void MessageContentModel::updateComponents(bool isEditing) endResetModel(); } + +void MessageContentModel::updateItineraryModel() +{ + if (m_room == nullptr || m_event == nullptr) { + return; + } + + if (auto event = eventCast(m_event)) { + if (event->hasFileContent()) { + auto filePath = m_room->fileTransferInfo(event->id()).localPath; + if (filePath.isEmpty() && m_itineraryModel != nullptr) { + delete m_itineraryModel; + m_itineraryModel = nullptr; + } else if (!filePath.isEmpty()) { + if (m_itineraryModel == nullptr) { + m_itineraryModel = new ItineraryModel(this); + } + m_itineraryModel->setPath(filePath.toString()); + } + } + } +} diff --git a/src/models/messagecontentmodel.h b/src/models/messagecontentmodel.h index 7c22dd9d7..156809d5a 100644 --- a/src/models/messagecontentmodel.h +++ b/src/models/messagecontentmodel.h @@ -8,6 +8,7 @@ #include "enums/messagecomponenttype.h" #include "eventhandler.h" +#include "itinerarymodel.h" #include "linkpreviewer.h" #include "neochatroom.h" @@ -45,6 +46,7 @@ public: AuthorRole, /**< The author of the event. */ MediaInfoRole, /**< The media info for the event. */ FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */ + ItineraryModelRole, /**< The itinerary model for a file. */ LatitudeRole, /**< Latitude for a location event. */ LongitudeRole, /**< Longitude for a location event. */ AssetRole, /**< Type of location event, e.g. self pin of the user location. */ @@ -92,4 +94,7 @@ private: void updateComponents(bool isEditing = false); LinkPreviewer *m_linkPreviewer = nullptr; + ItineraryModel *m_itineraryModel = nullptr; + + void updateItineraryModel(); }; diff --git a/src/qml/FileComponent.qml b/src/qml/FileComponent.qml index e9fd964d2..23a60aef2 100644 --- a/src/qml/FileComponent.qml +++ b/src/qml/FileComponent.qml @@ -59,7 +59,6 @@ ColumnLayout { */ readonly property bool downloaded: root.fileTransferInfo && root.fileTransferInfo.completed onDownloadedChanged: { - itineraryModel.path = root.fileTransferInfo.localPath; if (autoOpenFile) { openSavedFile(); } @@ -145,15 +144,6 @@ ColumnLayout { QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download") onClicked: root.room.cancelFileTransfer(root.eventId) } - }, - State { - name: "raw" - when: true - - PropertyChanges { - target: downloadButton - onClicked: root.saveFileAs() - } } ] @@ -196,6 +186,7 @@ ColumnLayout { QQC2.Button { id: downloadButton icon.name: "download" + onClicked: root.saveFileAs() QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download") QQC2.ToolTip.visible: hovered @@ -220,83 +211,4 @@ ColumnLayout { } } } - Repeater { - id: itinerary - model: ItineraryModel { - id: itineraryModel - connection: root.room.connection - } - delegate: DelegateChooser { - role: "type" - DelegateChoice { - roleValue: "TrainReservation" - delegate: ColumnLayout { - Kirigami.Separator { - Layout.fillWidth: true - } - RowLayout { - QQC2.Label { - text: model.name - } - QQC2.Label { - text: model.coach ? i18n("Coach: %1, Seat: %2", model.coach, model.seat) : "" - visible: model.coach - opacity: 0.7 - } - } - RowLayout { - Layout.fillWidth: true - ColumnLayout { - QQC2.Label { - text: model.departureStation + (model.departurePlatform ? (" [" + model.departurePlatform + "]") : "") - } - QQC2.Label { - text: model.departureTime - opacity: 0.7 - } - } - Item { - Layout.fillWidth: true - } - ColumnLayout { - QQC2.Label { - text: model.arrivalStation + (model.arrivalPlatform ? (" [" + model.arrivalPlatform + "]") : "") - } - QQC2.Label { - text: model.arrivalTime - opacity: 0.7 - Layout.alignment: Qt.AlignRight - } - } - } - } - } - DelegateChoice { - roleValue: "LodgingReservation" - delegate: ColumnLayout { - Kirigami.Separator { - Layout.fillWidth: true - } - QQC2.Label { - text: model.name - } - QQC2.Label { - text: i18nc(" - ", "%1 - %2", model.startTime, model.endTime) - } - QQC2.Label { - text: model.address - } - } - } - } - } - QQC2.Button { - icon.name: "map-globe" - text: i18nc("@action", "Send to KDE Itinerary") - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.text: text - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - onClicked: itineraryModel.sendToItinerary() - visible: itinerary.count > 0 - } } diff --git a/src/qml/ItineraryComponent.qml b/src/qml/ItineraryComponent.qml new file mode 100644 index 000000000..e13b4fcf3 --- /dev/null +++ b/src/qml/ItineraryComponent.qml @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: 2024 Tobias Fella +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import Qt.labs.qmlmodels + +import org.kde.kirigami as Kirigami + +/** + * @brief A component to show a preview of a file that can integrate with KDE itinerary. + */ +ColumnLayout { + id: root + + /** + * @brief A model with the itinerary preview of the file. + */ + required property var itineraryModel + + /** + * @brief The maximum width that the bubble's content can be. + */ + property real maxContentWidth: -1 + + Layout.fillWidth: true + Layout.maximumWidth: root.maxContentWidth + spacing: Kirigami.Units.largeSpacing + + Repeater { + id: itinerary + model: root.itineraryModel + onModelChanged: console.warn(itinerary.count) + delegate: DelegateChooser { + role: "type" + DelegateChoice { + roleValue: "TrainReservation" + delegate: ColumnLayout { + Kirigami.Separator { + Layout.fillWidth: true + } + RowLayout { + QQC2.Label { + text: model.name + } + QQC2.Label { + text: model.coach ? i18n("Coach: %1, Seat: %2", model.coach, model.seat) : "" + visible: model.coach + opacity: 0.7 + } + } + RowLayout { + Layout.fillWidth: true + ColumnLayout { + QQC2.Label { + text: model.departureStation + (model.departurePlatform ? (" [" + model.departurePlatform + "]") : "") + } + QQC2.Label { + text: model.departureTime + opacity: 0.7 + } + } + Item { + Layout.fillWidth: true + } + ColumnLayout { + QQC2.Label { + text: model.arrivalStation + (model.arrivalPlatform ? (" [" + model.arrivalPlatform + "]") : "") + } + QQC2.Label { + text: model.arrivalTime + opacity: 0.7 + Layout.alignment: Qt.AlignRight + } + } + } + } + } + DelegateChoice { + roleValue: "LodgingReservation" + delegate: ColumnLayout { + Kirigami.Separator { + Layout.fillWidth: true + } + QQC2.Label { + text: model.name + } + QQC2.Label { + text: i18nc(" - ", "%1 - %2", model.startTime, model.endTime) + } + QQC2.Label { + text: model.address + } + } + } + } + } + QQC2.Button { + icon.name: "map-globe" + text: i18nc("@action", "Send to KDE Itinerary") + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.text: text + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + onClicked: itineraryModel.sendToItinerary() + visible: itinerary.count > 0 + } +} diff --git a/src/qml/MessageComponentChooser.qml b/src/qml/MessageComponentChooser.qml index a58db64f9..996523642 100644 --- a/src/qml/MessageComponentChooser.qml +++ b/src/qml/MessageComponentChooser.qml @@ -117,6 +117,13 @@ DelegateChooser { } } + DelegateChoice { + roleValue: MessageComponentType.Itinerary + delegate: ItineraryComponent { + maxContentWidth: root.maxContentWidth + } + } + DelegateChoice { roleValue: MessageComponentType.Poll delegate: PollComponent {