KOSMIndoorMap

floorlevelchangemodel.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "floorlevelchangemodel.h"
8
9#include "loader/levelparser_p.h"
10#include <KOSMIndoorMap/MapData>
11
12#include <KLocalizedString>
13
14#include <QDebug>
15
16using namespace KOSMIndoorMap;
17
18FloorLevelChangeModel::FloorLevelChangeModel(QObject *parent)
19 : QAbstractListModel(parent)
20{
21}
22
23FloorLevelChangeModel::~FloorLevelChangeModel() = default;
24
25int FloorLevelChangeModel::rowCount(const QModelIndex &parent) const
26{
27 if (parent.isValid()) {
28 return 0;
29 }
30 return m_levels.size();
31}
32
33QVariant FloorLevelChangeModel::data(const QModelIndex &index, int role) const
34{
35 if (!index.isValid()) {
36 return {};
37 }
38
39 switch (role) {
40 case Qt::DisplayRole:
41 return m_levels[index.row()].name();
42 case FloorLevelRole:
43 return m_levels[index.row()].numericLevel();
44 case CurrentFloorRole:
45 return m_levels[index.row()].numericLevel() == m_currentFloorLevel;
46 }
47 return {};
48}
49
50QHash<int, QByteArray> FloorLevelChangeModel::roleNames() const
51{
53 n.insert(NameRole, "name");
54 n.insert(FloorLevelRole, "floorLevel");
55 n.insert(CurrentFloorRole, "isCurrentFloor");
56 return n;
57}
58
60{
61 return m_currentFloorLevel;
62}
63
65{
66 const auto it = std::find_if(m_levels.begin(), m_levels.end(), [this](const auto &level) { return level.numericLevel() == m_currentFloorLevel; });
67 return it != m_levels.end() ? (int)std::distance(m_levels.begin(), it) : -1;
68}
69
70void FloorLevelChangeModel::setCurrentFloorLevel(int level)
71{
72 if (m_currentFloorLevel == level) {
73 return;
74 }
75 m_currentFloorLevel = level;
76 if (!m_levels.empty()) {
77 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0));
78 }
79 Q_EMIT contentChanged();
80}
81
82FloorLevelModel* FloorLevelChangeModel::floorLevelModel() const
83{
84 return m_floorLevelModel;
85}
86
87void FloorLevelChangeModel::setFloorLevelModel(FloorLevelModel *floorLevelModel)
88{
89 if (m_floorLevelModel == floorLevelModel) {
90 return;
91 }
92
93 if (m_floorLevelModel) {
94 disconnect(m_floorLevelModel, &FloorLevelModel::modelAboutToBeReset, this, nullptr);
95 }
96
97 m_floorLevelModel = floorLevelModel;
98 connect(m_floorLevelModel, &FloorLevelModel::modelAboutToBeReset, this, [this]() {
100 m_element = {};
101 m_levels.clear();
103 });
104 Q_EMIT contentChanged();
105}
106
107OSMElement FloorLevelChangeModel::element() const
108{
109 return OSMElement(m_element);
110}
111
112void FloorLevelChangeModel::setElement(const OSMElement &element)
113{
114 if (m_element == element.element()) {
115 return;
116 }
117
119 m_element = element.element();
120 m_levels.clear();
121
122 if (isLevelChangeElement(m_element)) {
123
124 // elevators are sometimes also tagged with building:level tags instead of level/repeat_on, so handle that as well
125 const auto buildingLevels = m_element.tagValue("building:levels").toUInt();
126 if (buildingLevels > 0) {
127 const auto buildingMinLevel = m_element.tagValue("building:min_level", "level").toUInt();
128 for (auto i = buildingMinLevel; i < buildingLevels; ++i) {
129 appendFullFloorLevel(i * 10);
130 }
131 }
132 const auto buildingUndergroundLevel = m_element.tagValue("building:levels:underground").toUInt();
133 for (auto i = buildingUndergroundLevel; i > 0; --i) {
134 appendFullFloorLevel(-i * 10);
135 }
136
137 LevelParser::parse(m_element.tagValue("level", "repeat_on"), m_element, [this](int level, OSM::Element e) {
138 Q_UNUSED(e);
139 appendFloorLevel(level);
140
141 });
142 std::sort(m_levels.begin(), m_levels.end());
143 m_levels.erase(std::unique(m_levels.begin(), m_levels.end()), m_levels.end());
144 }
145
147 Q_EMIT contentChanged();
148}
149
150bool FloorLevelChangeModel::isLevelChangeElement(OSM::Element element) const
151{
152 return !element.tagValue("highway").isEmpty()
153 || !element.tagValue("elevator").isEmpty()
154 || !element.tagValue("stairwell").isEmpty()
155 || element.tagValue("building:part") == "elevator"
156 || element.tagValue("building") == "elevator"
157 || element.tagValue("room") == "elevator"
158 || element.tagValue("levelpart") == "elevator_platform"
159 || (!element.tagValue("indoor").isEmpty() && element.tagValue("stairs") == "yes")
160 || element.tagValue("room") == "stairs";
161}
162
163void FloorLevelChangeModel::appendFloorLevel(int level)
164{
165 MapLevel ml(level);
166 if (ml.isFullLevel()) {
167 appendFullFloorLevel(level);
168 } else {
169 appendFullFloorLevel(ml.fullLevelBelow());
170 appendFullFloorLevel(ml.fullLevelAbove());
171 }
172}
173
174void FloorLevelChangeModel::appendFullFloorLevel(int level)
175{
176 if (!m_floorLevelModel) {
177 m_levels.push_back(MapLevel(level));
178 } else {
179 const auto row = m_floorLevelModel->rowForLevel(level);
180 if (row >= 0) {
181 const auto idx = m_floorLevelModel->index(row, 0);
182 m_levels.push_back(m_floorLevelModel->data(idx, FloorLevelModel::MapLevelRole).value<MapLevel>());
183 }
184 }
185}
186
188{
189 if (m_levels.size() != 2) {
190 return false;
191 }
192 return m_levels[0].numericLevel() == m_currentFloorLevel || m_levels[1].numericLevel() == m_currentFloorLevel;
193}
194
196{
197 if (m_levels.size() != 2) {
198 return 0;
199 }
200 return m_levels[0].numericLevel() == m_currentFloorLevel ? m_levels[1].numericLevel() : m_levels[0].numericLevel();
201}
202
203QString FloorLevelChangeModel::destinationLevelName() const
204{
205 if (m_levels.size() != 2) {
206 return {};
207 }
208 return m_levels[0].numericLevel() == m_currentFloorLevel ? m_levels[1].name() : m_levels[0].name();
209}
210
212{
213 return m_levels.size() > 1;
214}
215
216QString FloorLevelChangeModel::title() const
217{
218 if (m_element.tagValue("highway") == "elevator"
219 || !m_element.tagValue("elevator").isEmpty()
220 || m_element.tagValue("building:part") == "elevator"
221 || m_element.tagValue("building") == "elevator"
222 || m_element.tagValue("room") == "elevator"
223 || m_element.tagValue("levelpart") == "elevator_platform")
224 {
225 return i18n("Elevator");
226 }
227
228 if (!m_element.tagValue("stairwell").isEmpty()
229 || m_element.tagValue("stairs") == "yes"
230 || m_element.tagValue("room") == "stairs")
231 {
232 return i18n("Staircase");
233 }
234
235 if (m_levels.size() > 2) {
236 qWarning() << "Unknown floor level change element type:" << m_element.url();
237 }
238 return {};
239}
240
241#include "moc_floorlevelchangemodel.cpp"
int destinationLevel
The destination level for a single level change.
QString title
Human-readable title of the thing enabling a floor level change here.
int currentFloorLevelRow
The model row representing the current floor level.
bool hasSingleLevelChange
The current element changes to a single other floor, ie.
int currentFloorLevel
The current floor level.
bool hasMultipleLevelChanges
The current element changes to multiple levels based on users choice, ie.
QML wrapper around an OSM element.
Definition osmelement.h:21
QString i18n(const char *text, const TYPE &arg...)
OSM-based multi-floor indoor maps for buildings.
QStringView level(QStringView ifopt)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
void modelAboutToBeReset()
virtual QModelIndex parent(const QModelIndex &index) const const=0
virtual QHash< int, QByteArray > roleNames() const const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
DisplayRole
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:54:41 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.