MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 95.0% 19 / 0 / 20
Functions: 95.0% 19 / 0 / 20
Branches: 100.0% 2 / 0 / 2

presentation/QuickContactMap.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QDeadlineTimer>
4 #include <QHash>
5 #include <QObject>
6 #include <QPointF>
7 #include <QSizeF>
8 #include <QStringList>
9 #include <QSet>
10 #include <QThreadPool>
11 #include <QVariantList>
12 #include <vector>
13
14 #include "data/ContactPlaceLookup.h"
15 #include "data/MapClusterer.h"
16 #include "data/MapProjection.h"
17 #include "presentation/PlaceLookupSource.h"
18
19 class MapMarkerModel;
20 class MapTileSource;
21 class QTimer;
22
23 // The contacts' world map for QML (Sprint 103 S103.4.1–S103.4.4, D-11,
24 // interaction.md §6; `backend.pim.contactMap`).
25 //
26 // The places come from the derived card_places rows that have a position
27 // (pinned, from the card or placed by the offline gazetteer). On activate()
28 // the pending rows are placed on a worker in slices (ContactPlaceIndexer,
29 // positions never leave the computer) and the places are read again.
30 //
31 // The viewport (centre, zoom, canvas size) lives here; QML draws `tiles`
32 // (image://maptile, Web Mercator slippy tiles at the nearest integer zoom,
33 // scaled to the fractional zoom) and `markers` (clusters of MapClusterer for
34 // the current zoom, only those inside the canvas, west → east). Every
35 // position is in canvas pixels.
36 class QuickContactMap final : public QObject {
37 168 Q_OBJECT
38 Q_PROPERTY(double zoom READ zoom NOTIFY viewportChanged)
39 // [{source ("z/x/y"), x, y, size}]
40 Q_PROPERTY(QVariantList tiles READ tiles NOTIFY viewportChanged)
41 // [{id, x, y, count, key, keys, name, initials, avatarColor, hasPhoto,
42 // kind, place, sameSpot, approximate}] — a single person has count 1 and
43 // their key; `approximate`: placed from the address by the offline
44 // gazetteer (not pinned, no position on the card).
45 Q_PROPERTY(QVariantList markers READ markers NOTIFY markersChanged)
46 // F103-14: the same markers as a model with stable rows per cluster (QML).
47 Q_PROPERTY(QObject *markerModel READ markerModel CONSTANT)
48 Q_PROPERTY(int placedPeople READ placedPeople NOTIFY placesChanged)
49 Q_PROPERTY(int unplacedPeople READ unplacedPeople NOTIFY placesChanged)
50 Q_PROPERTY(bool placing READ placing NOTIFY placingChanged)
51 // Place kinds shown (View ▾): "home", "work", "other" (addresses), "met", "birth".
52 Q_PROPERTY(QStringList kinds READ kinds WRITE setKinds NOTIFY placesChanged)
53 // S103.4.1: "list", "map" or "graph" in the contact area, remembered (QSettings contacts/viewMode).
54 Q_PROPERTY(QString viewMode READ viewMode WRITE setViewMode NOTIFY viewModeChanged)
55 // S103.4.7: the accessible "List of places" instead of the canvas.
56 Q_PROPERTY(bool placesList READ placesList WRITE setPlacesList NOTIFY viewModeChanged)
57 // S103.4.10 (D-11): online tiles, opt-in in Settings › Privacy (QSettings
58 // contacts/mapTiles, contacts/mapTileServer). With them the map zooms to
59 // street level; the outline stays below as the fallback.
60 Q_PROPERTY(bool onlineTiles READ onlineTiles WRITE setOnlineTiles NOTIFY tileSettingsChanged)
61 // F103-24: [{id, provider, name}] of resources/contacts/map-styles.json;
62 // the chosen one (QSettings contacts/mapTileStyle) or "custom" (the tileServer).
63 Q_PROPERTY(QVariantList tileStyles READ tileStyles CONSTANT)
64 Q_PROPERTY(QString tileStyle READ tileStyle WRITE setTileStyle NOTIFY tileSettingsChanged)
65 // [{id, name, keyUrl, needsKey, hasKey}] of the catalog's providers; the
66 // provider of the chosen style ("custom" for an own server).
67 Q_PROPERTY(QVariantList tileProviders READ tileProviders NOTIFY tileSettingsChanged)
68 Q_PROPERTY(QString tileProvider READ tileProvider NOTIFY tileSettingsChanged)
69 // The chosen style's provider needs an API key and none is stored: no
70 // tile is fetched (keyless requests get watermark tiles), the map says why.
71 Q_PROPERTY(bool tileKeyMissing READ tileKeyMissing NOTIFY tileSettingsChanged)
72 // The server in use; setting one chooses "custom", "" the default style.
73 Q_PROPERTY(QString tileServer READ tileServer WRITE setTileServer NOTIFY tileSettingsChanged)
74 // F103-25: the tile cache on disk ("12.3 MB"), read by refreshTileCacheSize().
75 Q_PROPERTY(QString tileCacheSize READ tileCacheSize NOTIFY tileCacheChanged)
76 // S103.1.9 (D-10): online lookup of street addresses, opt-in (QSettings
77 // contacts/placeLookup, contacts/placeLookupServer). With it on, the
78 // addresses the offline gazetteer placed only roughly are looked up in the
79 // background (1 per second) while the map is in use; `lookupExample` is the
80 // request for a sample address (the consent text shows what is sent).
81 Q_PROPERTY(bool onlineLookup READ onlineLookup WRITE setOnlineLookup NOTIFY lookupSettingsChanged)
82 Q_PROPERTY(QString lookupServer READ lookupServer WRITE setLookupServer NOTIFY lookupSettingsChanged)
83 Q_PROPERTY(QString lookupServerHost READ lookupServerHost NOTIFY lookupSettingsChanged)
84 Q_PROPERTY(QString lookupExample READ lookupExample NOTIFY lookupSettingsChanged)
85 Q_PROPERTY(int lookupPending READ lookupPending NOTIFY lookupPendingChanged)
86 Q_PROPERTY(QString attribution READ attribution NOTIFY tileSettingsChanged)
87 Q_PROPERTY(bool tilesUnavailable READ tilesUnavailable NOTIFY tilesUnavailableChanged)
88 // [{source (file URL), x, y, size}]: the cached online tiles of the view.
89 Q_PROPERTY(QVariantList onlineTileList READ onlineTileList NOTIFY viewportChanged)
90 // F103-23: the layers as models with stable rows per tile (QML), so a pan
91 // or zoom moves the same images instead of reloading them. `tileModel`
92 // also holds the level above (seamless level changes); `onlineTileModel`
93 // also holds stand-ins for tiles not yet loaded: the nearest cached
94 // ancestor's part (clip) or cached children, below the real tiles (z).
95 // Rows: {id, source, x, y, size, z, clip: [x, y, w, h] or [], fade}.
96 Q_PROPERTY(QObject *tileModel READ tileModel CONSTANT)
97 Q_PROPERTY(QObject *onlineTileModel READ onlineTileModel CONSTANT)
98
99 public:
100 // F103-23: tiles are requested once the view rested this long (a zoom
101 // gesture no longer starts and aborts the requests of every level).
102 static constexpr int WantDelayMs = 150;
103 explicit QuickContactMap(QString databasePath, QObject *parent = nullptr);
104 ~QuickContactMap() override;
105
106 32 double zoom() const { return m_viewport.zoom; }
107 7 QVariantList tiles() const { return m_tiles; }
108 17 QVariantList markers() const { return m_markers; }
109 QObject *markerModel() const;
110 63 int placedPeople() const { return m_placedPeople; }
111 141 int unplacedPeople() const { return m_unplacedPeople; }
112 115 bool placing() const { return m_placing; }
113 32 QStringList kinds() const { return m_kinds; }
114 void setKinds(const QStringList &kinds);
115 QString viewMode() const;
116 void setViewMode(const QString &mode);
117 25 bool placesList() const { return m_placesList; }
118 void setPlacesList(bool on);
119 // S103.4.7 (interaction.md §6.8): the shown places as a tree —
120 // [{country, count, cities: [{city, count, people: [{key, name, kind}]}]}],
121 // countries and cities by count, then name.
122 Q_INVOKABLE QVariantList placesTree() const;
123 81 bool onlineTiles() const { return m_onlineTiles; }
124 void setOnlineTiles(bool on);
125 QVariantList tileStyles() const;
126 47 QString tileStyle() const { return m_tileStyle; }
127 void setTileStyle(const QString &id);
128 QVariantList tileProviders() const;
129 QString tileProvider() const;
130 bool tileKeyMissing() const;
131 // A provider's API key (QSettings contacts/mapTileKeys/<provider>; local,
132 // never synced, never logged); "" removes it.
133 Q_INVOKABLE QString tileKey(const QString &provider) const;
134 Q_INVOKABLE void setTileKey(const QString &provider, const QString &key);
135 QString tileServer() const;
136 void setTileServer(const QString &templateUrl);
137 36 QString tileCacheSize() const { return m_tileCacheSize; }
138 Q_INVOKABLE void refreshTileCacheSize();
139 52 bool onlineLookup() const { return m_onlineLookup; }
140 void setOnlineLookup(bool on);
141 QString lookupServer() const;
142 void setLookupServer(const QString &server);
143 QString lookupServerHost() const;
144 QString lookupExample() const;
145 int lookupPending() const;
146 // "" when the server address can be used, else why not (settings field).
147 Q_INVOKABLE QString checkLookupServer(const QString &server) const;
148 // *Look up exactly…* on a card (also with the setting off, after the
149 // user confirmed): one request for the address at `index`; the answer
150 // comes as addressLookedUp. False when the card has no such address.
151 Q_INVOKABLE bool lookUpAddress(const QString &key, int index);
152 4 PlaceLookupSource *lookupSource() const { return m_lookupSource; } // tests
153 QObject *tileModel() const;
154 QObject *onlineTileModel() const;
155 QString attribution() const;
156 ✗ bool tilesUnavailable() const { return m_tilesUnavailable; }
157
2/2
✓ Branch 2 → 3 taken 47 times.
✓ Branch 2 → 4 taken 2 times.
49 QVariantList onlineTileList() const { return m_onlineTiles ? m_onlineTileList : QVariantList{}; }
158 // "" when the server address can be used, else why not (settings field).
159 Q_INVOKABLE QString checkTileServer(const QString &templateUrl) const;
160 Q_INVOKABLE void clearTileCache();
161 // Tests (network spy, G20).
162 83 MapTileSource *tileSource() const { return m_tileSource; }
163
164 // The map became visible: place pending rows, then read the places.
165 Q_INVOKABLE void activate();
166 // S106-312: the map left the screen — background address lookups stop
167 // (they run only while the map is in use) and reload() stays cheap.
168 Q_INVOKABLE void deactivate();
169 1 bool active() const { return m_active; } // tests
170 // S106-283: an address whose lookup failed is not asked again for this
171 // long; a background batch without any answer pauses the lookups as long.
172 static constexpr int LookupRetryMs = 10 * 60 * 1000;
173 1 void setLookupRetryMs(int ms) { m_lookupRetryMs = ms; } // tests
174 // After contact writes or a sync (cheap when the map is hidden).
175 Q_INVOKABLE void reload();
176 Q_INVOKABLE void setSize(double width, double height);
177 Q_INVOKABLE void panBy(double dx, double dy);
178 Q_INVOKABLE void zoomAt(double steps, double x, double y);
179 // `0`: every shown place in view.
180 Q_INVOKABLE void fitAll();
181 // §6.5: when none of the person's places is in view, pan (not zoom) to
182 // the first one (selection from the list, graph or card).
183 Q_INVOKABLE void showKey(const QString &key);
184 // Pin mode (S103.4.5): canvas point → {latitude, longitude}; centre on a
185 // position at `zoom` (0 keeps the zoom).
186 Q_INVOKABLE QVariantMap toLatLon(double x, double y) const;
187 Q_INVOKABLE void centerOn(double latitude, double longitude, double zoom = 0);
188 // S103.6.7 `map <place>`: centre on a place of the offline gazetteer
189 // (city 9, region 6, country 4); false when it is unknown.
190 Q_INVOKABLE bool showPlace(const QString &text);
191 // S103.4.6 (I-7): the people with a placed address, meeting or birthplace
192 // within about 50 km of a place text (an event's location): {place, count}
193 // — {} when the place is unknown offline or only a country; the me card
194 // and positions known only at country level are not counted.
195 Q_INVOKABLE QVariantMap nearby(const QString &text) const;
196 // Canvas position of a world point (pin mode, labels): {x, y}.
197 Q_INVOKABLE QVariantMap toScreen(double latitude, double longitude) const;
198
199 // The current viewport (tests, the graph's "show on map").
200 1 MapProjection::Viewport viewport() const { return m_viewport; }
201
202 signals:
203 void viewportChanged();
204 void markersChanged();
205 void placesChanged();
206 void placingChanged();
207 void viewModeChanged();
208 void tileSettingsChanged();
209 void tilesUnavailableChanged();
210 void tileCacheChanged();
211 void lookupSettingsChanged();
212 void lookupPendingChanged();
213 // A card's *Look up exactly…* was answered: found (placeName) or not
214 // found, or `failed` (offline, refused).
215 void addressLookedUp(const QString &key, bool found, bool failed, const QString &placeName);
216
217 private:
218 struct Place {
219 QString key;
220 QString name;
221 QString initials;
222 int avatarColor = 0;
223 bool hasPhoto = false;
224 QString kind;
225 QString label; // locality or place text
226 QString country;
227 bool approximate = false;
228 int person = 0; // index of the person (one per key), for fast de-duplication
229 QPointF world;
230 };
231 void readPlaces();
232 void placeOnWorker();
233 void updateTiles();
234 void updateOnlineTiles();
235 void applyTileStyle();
236 void initLookup();
237 void queueLookups();
238 void stopBackgroundLookups();
239 void lookupAnswered(const QString &id, const PlaceLookupSource::Result &result);
240 void tileSettingsApplied();
241 int onlineMaxZoom() const;
242 void requestTiles();
243 void updateMarkers();
244 void rebuildClusterInput();
245 const QStringList &clusterKeys(int cluster);
246 void setViewport(const MapProjection::Viewport &viewport);
247
248 QString m_databasePath;
249 QThreadPool m_pool;
250 MapProjection::Viewport m_viewport;
251 QList<Place> m_places;
252 int m_personCount = 0;
253 std::vector<int> m_seenAt; // per person: the last cluster that counted them
254 // Built once per read: the clusterer's input (id = index into m_places)
255 // and, per zoom, its clusters — a pan only moves and filters them.
256 QList<MapClusterer::Place> m_clusterInput;
257 double m_clusterZoom = -1;
258 QList<MapClusterer::Cluster> m_clusters;
259 QHash<int, QStringList> m_clusterKeys; // the people of a visible cluster, once per zoom
260 QStringList m_kinds{QStringLiteral("home"), QStringLiteral("work"), QStringLiteral("other"),
261 QStringLiteral("met"), QStringLiteral("birth")};
262 QVariantList m_tiles;
263 QVariantList m_markers;
264 MapMarkerModel *m_markerModel = nullptr;
265 int m_placedPeople = 0;
266 int m_unplacedPeople = 0;
267 bool m_placing = false;
268 bool m_active = false;
269 bool m_fitted = false;
270 bool m_placesList = false;
271 MapTileSource *m_tileSource = nullptr;
272 bool m_onlineTiles = false;
273 bool m_tilesUnavailable = false;
274 bool m_tileRefreshQueued = false;
275 QVariantList m_onlineTileList;
276 MapMarkerModel *m_tileModel = nullptr;
277 MapMarkerModel *m_onlineTileModel = nullptr;
278 QString m_tileStyle;
279 QString m_tileCacheSize;
280 QSet<QString> m_standIns; // tiles shown through a stand-in last time (no fade-in then)
281 QHash<QString, bool> m_tileFade; // online rows shown last time → their fade
282 QStringList m_wantedTiles; // centre first, sent after the view rests (requestTiles)
283 QTimer *m_wantTimer = nullptr;
284 PlaceLookupSource *m_lookupSource = nullptr;
285 bool m_onlineLookup = false;
286 struct LookupRequest {
287 ContactPlaceLookup::Address address;
288 QString cardKey; // "" for the background lookups
289 };
290 QHash<QString, LookupRequest> m_lookups; // request id → its address
291 QHash<QString, QDeadlineTimer> m_lookupFailed; // address cache key → when it may be asked again
292 QDeadlineTimer m_lookupPause; // expired: background lookups may run
293 bool m_batchAnswered = false; // the running background batch got an answer
294 int m_lookupRetryMs = LookupRetryMs;
295 QTimer *m_lookupReread = nullptr; // one map re-read after a burst of answers
296 };
297