File indexing completed on 2024-12-08 07:33:44
0001 // SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org> 0002 // SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org> 0003 // SPDX-License-Identifier: GPL-2.0-or-later 0004 0005 #include "livelocationsmodel.h" 0006 0007 #include <Quotient/events/roommessageevent.h> 0008 0009 #include <QDebug> 0010 0011 #include <cmath> 0012 0013 using namespace Quotient; 0014 0015 bool operator<(const LiveLocationData &lhs, const LiveLocationData &rhs) 0016 { 0017 return lhs.eventId < rhs.eventId; 0018 } 0019 0020 LiveLocationsModel::LiveLocationsModel(QObject *parent) 0021 : QAbstractListModel(parent) 0022 { 0023 connect( 0024 this, 0025 &LiveLocationsModel::roomChanged, 0026 this, 0027 [this]() { 0028 for (const auto &event : m_room->messageEvents()) { 0029 addEvent(event.get()); 0030 } 0031 connect(m_room, &NeoChatRoom::aboutToAddHistoricalMessages, this, [this](const auto &events) { 0032 for (const auto &event : events) { 0033 addEvent(event.get()); 0034 } 0035 }); 0036 connect(m_room, &NeoChatRoom::aboutToAddNewMessages, this, [this](const auto &events) { 0037 for (const auto &event : events) { 0038 addEvent(event.get()); 0039 } 0040 }); 0041 }, 0042 Qt::QueuedConnection); // deferred so we are sure the eventId filter is set 0043 0044 connect(this, &LiveLocationsModel::dataChanged, this, &LiveLocationsModel::boundingBoxChanged); 0045 connect(this, &LiveLocationsModel::rowsInserted, this, &LiveLocationsModel::boundingBoxChanged); 0046 } 0047 0048 int LiveLocationsModel::rowCount(const QModelIndex &parent) const 0049 { 0050 if (parent.isValid()) { 0051 return 0; 0052 } 0053 return m_locations.size(); 0054 } 0055 0056 QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const 0057 { 0058 if (!checkIndex(index)) { 0059 return {}; 0060 } 0061 0062 const auto &data = m_locations.at(index.row()); 0063 switch (roleName) { 0064 case LatitudeRole: { 0065 const auto geoUri = data.beacon["org.matrix.msc3488.location"_ls].toObject()["uri"_ls].toString(); 0066 if (geoUri.isEmpty()) { 0067 return {}; 0068 } 0069 const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[0]; 0070 return latitude.toFloat(); 0071 } 0072 case LongitudeRole: { 0073 const auto geoUri = data.beacon["org.matrix.msc3488.location"_ls].toObject()["uri"_ls].toString(); 0074 if (geoUri.isEmpty()) { 0075 return {}; 0076 } 0077 const auto longitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[1]; 0078 return longitude.toFloat(); 0079 } 0080 case AssetRole: 0081 return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString(); 0082 case AuthorRole: 0083 return m_room->getUser(data.senderId); 0084 case IsLiveRole: { 0085 if (!data.beaconInfo["live"_ls].toBool()) { 0086 return false; 0087 } 0088 // TODO Qt6: port to toInteger(), timestamps are in ms since epoch, ie. 64 bit values 0089 const auto lastTs = std::max(data.beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble(), data.beacon.value("org.matrix.msc3488.ts"_ls).toDouble()); 0090 const auto timeout = data.beaconInfo.value("timeout"_ls).toDouble(600000); 0091 return lastTs + timeout >= QDateTime::currentDateTime().toMSecsSinceEpoch(); 0092 } 0093 case HeadingRole: { 0094 bool success = false; 0095 const auto heading = data.beacon["org.matrix.msc3488.location"_ls].toObject()["org.kde.itinerary.heading"_ls].toString().toDouble(&success); 0096 return success ? heading : NAN; 0097 } 0098 } 0099 0100 return {}; 0101 } 0102 0103 QHash<int, QByteArray> LiveLocationsModel::roleNames() const 0104 { 0105 auto r = QAbstractListModel::roleNames(); 0106 r.insert(LatitudeRole, "latitude"); 0107 r.insert(LongitudeRole, "longitude"); 0108 r.insert(AssetRole, "asset"); 0109 r.insert(AuthorRole, "author"); 0110 r.insert(IsLiveRole, "isLive"); 0111 r.insert(HeadingRole, "heading"); 0112 return r; 0113 } 0114 0115 QRectF LiveLocationsModel::boundingBox() const 0116 { 0117 QRectF bbox(QPointF(180.0, 90.0), QPointF(-180.0, -90.0)); 0118 for (auto i = 0; i < rowCount(); ++i) { 0119 const auto lat = data(index(i, 0), LatitudeRole).toDouble(); 0120 const auto lon = data(index(i, 0), LongitudeRole).toDouble(); 0121 0122 bbox.setLeft(std::min(bbox.left(), lon)); 0123 bbox.setRight(std::max(bbox.right(), lon)); 0124 bbox.setTop(std::min(bbox.top(), lat)); 0125 bbox.setBottom(std::max(bbox.bottom(), lat)); 0126 } 0127 return bbox; 0128 } 0129 0130 void LiveLocationsModel::addEvent(const Quotient::RoomEvent *event) 0131 { 0132 if (event->isStateEvent() && event->matrixType() == "org.matrix.msc3672.beacon_info"_ls) { 0133 LiveLocationData data; 0134 data.senderId = event->senderId(); 0135 data.beaconInfo = event->contentJson(); 0136 if (event->contentJson()["live"_ls].toBool()) { 0137 data.eventId = event->id(); 0138 } else { 0139 data.eventId = event->fullJson()["replaces_state"_ls].toString(); 0140 } 0141 updateLocationData(std::move(data)); 0142 } 0143 if (event->matrixType() == "org.matrix.msc3672.beacon"_ls) { 0144 LiveLocationData data; 0145 data.eventId = event->contentJson()["m.relates_to"_ls].toObject()["event_id"_ls].toString(); 0146 data.senderId = event->senderId(); 0147 data.beacon = event->contentJson(); 0148 updateLocationData(std::move(data)); 0149 } 0150 } 0151 0152 void LiveLocationsModel::updateLocationData(LiveLocationData &&data) 0153 { 0154 if (!m_eventId.isEmpty() && data.eventId != m_eventId) { 0155 return; 0156 } 0157 0158 auto it = std::lower_bound(m_locations.begin(), m_locations.end(), data); 0159 if (it == m_locations.end() || it->eventId != data.eventId) { 0160 const auto row = std::distance(m_locations.begin(), it); 0161 beginInsertRows({}, row, row); 0162 m_locations.insert(it, std::move(data)); 0163 endInsertRows(); 0164 return; 0165 } 0166 0167 const auto idx = index(std::distance(m_locations.begin(), it), 0); 0168 0169 // TODO Qt6: port to toInteger(), timestamps are in ms since epoch, ie. 64 bit values 0170 if (it->beacon.isEmpty() || it->beacon.value("org.matrix.msc3488.ts"_ls).toDouble() < data.beacon.value("org.matrix.msc3488.ts"_ls).toDouble()) { 0171 it->beacon = std::move(data.beacon); 0172 } 0173 if (it->beaconInfo.isEmpty() 0174 || it->beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble() < data.beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble()) { 0175 it->beaconInfo = std::move(data.beaconInfo); 0176 } 0177 0178 Q_EMIT dataChanged(idx, idx); 0179 } 0180 0181 #include "moc_livelocationsmodel.cpp"