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: 93.4% 895 / 0 / 958
Functions: 95.4% 62 / 0 / 65
Branches: 78.0% 1292 / 0 / 1656

presentation/QuickContactGraph.cpp
Line Branch Exec Source
1 #include "QuickContactGraph.h"
2 #include "GraphLinesItem.h"
3
4 #include "data/ContactIndex.h"
5 #include "data/ContactQueries.h"
6 #include "data/ContactStore.h"
7 #include "data/RelationGraphFamilies.h"
8 #include "data/RelationGraphLayout.h"
9 #include "data/RelationGraphStar.h"
10 #include "data/RelationGraphTree.h"
11 #include "data/RelationIndex.h"
12 #include "data/RelationInference.h"
13 #include "data/RelationKinship.h"
14 #include "data/RelationPath.h"
15 #include "data/RelationVocabulary.h"
16
17 #include <QElapsedTimer>
18 #include <QLineF>
19 #include <QMap>
20 #include <QLoggingCategory>
21 #include <QRectF>
22 #include <QSettings>
23 #include <QSqlQuery>
24 #include <algorithm>
25 #include <cmath>
26 #include <functional>
27
28
4/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 8 taken 516 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
519 Q_LOGGING_CATEGORY(lcGraph, "mailjd.contacts.graph")
29
30 1603 QuickContactGraph::QuickContactGraph(QString databasePath, QObject *parent)
31
1/1
✓ Branch 38 → 39 taken 1603 times.
1603 : QObject(parent), m_databasePath(std::move(databasePath)) {
32
1/1
✓ Branch 39 → 40 taken 1603 times.
1603 GraphLinesItem::registerTypes(); // MailJD.Graph draws this graph (S105.4.2)
33
4/4
✓ Branch 40 → 41 taken 1603 times.
✓ Branch 45 → 46 taken 1603 times.
✓ Branch 46 → 47 taken 1603 times.
✓ Branch 47 → 48 taken 1603 times.
3206 setHiddenCategories(QSettings().value(QStringLiteral("contacts/graphHidden")).toStringList());
34
3/3
✓ Branch 53 → 54 taken 1603 times.
✓ Branch 59 → 60 taken 1603 times.
✓ Branch 60 → 61 taken 1603 times.
3206 m_suggestions = QSettings().value(QStringLiteral("contacts/graphSuggestions"), false).toBool();
35
3/3
✓ Branch 66 → 67 taken 1603 times.
✓ Branch 72 → 73 taken 1603 times.
✓ Branch 73 → 74 taken 1603 times.
3206 m_familyTree = QSettings().value(QStringLiteral("contacts/graphFamilyTree"), false).toBool();
36
3/3
✓ Branch 79 → 80 taken 1603 times.
✓ Branch 85 → 86 taken 1603 times.
✓ Branch 86 → 87 taken 1603 times.
3206 m_legendOpen = QSettings().value(QStringLiteral("contacts/graphLegendOpen"), false).toBool();
37
3/3
✓ Branch 92 → 93 taken 1603 times.
✓ Branch 98 → 99 taken 1603 times.
✓ Branch 99 → 100 taken 1603 times.
3206 m_kinshipLimit = qMax(0, QSettings().value(QStringLiteral("contacts/graphKinship"), 0).toInt());
38
1/1
✓ Branch 106 → 107 taken 1603 times.
1603 m_pool.setMaxThreadCount(1);
39 1603 }
40
41 143 QString QuickContactGraph::meKey() const {
42
3/3
✓ Branch 2 → 3 taken 143 times.
✓ Branch 7 → 8 taken 143 times.
✓ Branch 8 → 9 taken 143 times.
286 return QSettings().value(QStringLiteral("contacts/meCard")).toString();
43 }
44
45 2613 qint64 QuickContactGraph::cardIdOf(const QString &key) const { return m_cardIds.value(key, 0); }
46
47 // People of the books in *All contacts* and every resolved edge.
48 327 void QuickContactGraph::ensureInput() {
49
2/2
✓ Branch 2 → 3 taken 280 times.
✓ Branch 2 → 4 taken 47 times.
327 if (m_loaded)
50 280 return;
51 47 m_loaded = true;
52
3/4
✓ Branch 4 → 5 taken 7 times.
✓ Branch 4 → 7 taken 40 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 7 times.
47 if (m_suggestions && m_suggestionRun == 0)
53 ✗ computeSuggestions(); // S103.5.8: the first answer of this session
54 47 QElapsedTimer timer;
55 47 timer.start();
56 47 m_input = {};
57 47 m_personIndex.clear();
58 47 m_cardIds.clear();
59 47 m_avatars.clear();
60
1/1
✓ Branch 13 → 14 taken 47 times.
47 ContactStore store;
61
4/7
✓ Branch 15 → 16 taken 47 times.
✗ Branch 15 → 18 not taken.
✓ Branch 16 → 17 taken 47 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 47 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 47 times.
47 if (m_databasePath.isEmpty() || !store.open(m_databasePath))
62 ✗ return;
63
1/1
✓ Branch 22 → 23 taken 47 times.
47 QSqlDatabase db = store.database();
64
1/1
✓ Branch 23 → 24 taken 47 times.
47 const QString me = meKey();
65
1/1
✓ Branch 24 → 25 taken 47 times.
47 QSqlQuery q(db);
66
1/1
✓ Branch 25 → 26 taken 47 times.
47 q.setForwardOnly(true);
67
2/3
✓ Branch 29 → 30 taken 47 times.
✓ Branch 32 → 33 taken 47 times.
✗ Branch 32 → 118 not taken.
47 if (q.exec(QStringLiteral("SELECT c.id, c.book_id, c.uid, c.display_name, c.organization, c.has_photo,"
68 " COALESCE(t.gender, ''), COALESCE(c.birthday, ''), COALESCE(c.anniversary, '')"
69 " FROM cards c JOIN address_books b ON b.id = c.book_id"
70 " LEFT JOIN card_traits t ON t.card_id = c.id"
71 " WHERE b.show_in_all = 1 AND c.kind <> 'group'"))) {
72
3/3
✓ Branch 116 → 117 taken 3252 times.
✓ Branch 117 → 34 taken 3205 times.
✓ Branch 117 → 118 taken 47 times.
3252 while (q.next()) {
73 3205 RelationGraph::Person person;
74
2/2
✓ Branch 34 → 35 taken 3205 times.
✓ Branch 35 → 36 taken 3205 times.
3205 person.cardId = q.value(0).toLongLong();
75
5/5
✓ Branch 37 → 38 taken 3205 times.
✓ Branch 38 → 39 taken 3205 times.
✓ Branch 39 → 40 taken 3205 times.
✓ Branch 40 → 41 taken 3205 times.
✓ Branch 41 → 42 taken 3205 times.
3205 person.key = ContactQueries::cardKey(q.value(1).toLongLong(), q.value(2).toString());
76
2/2
✓ Branch 47 → 48 taken 3205 times.
✓ Branch 48 → 49 taken 3205 times.
3205 person.name = q.value(3).toString();
77
2/2
✓ Branch 52 → 53 taken 3205 times.
✓ Branch 53 → 54 taken 3205 times.
3205 person.organization = q.value(4).toString();
78
2/2
✓ Branch 57 → 58 taken 3205 times.
✓ Branch 58 → 59 taken 3205 times.
3205 person.gender = q.value(6).toString();
79
2/2
✓ Branch 62 → 63 taken 3205 times.
✓ Branch 63 → 64 taken 3205 times.
3205 person.birthday = q.value(7).toString();
80
2/2
✓ Branch 67 → 68 taken 3205 times.
✓ Branch 68 → 69 taken 3205 times.
3205 person.anniversary = q.value(8).toString();
81 3205 person.me = person.key == me;
82
1/1
✓ Branch 74 → 75 taken 3205 times.
3205 m_personIndex.insert(person.cardId, int(m_input.people.size()));
83
1/1
✓ Branch 75 → 76 taken 3205 times.
3205 m_cardIds.insert(person.key, person.cardId);
84
1/1
✓ Branch 76 → 77 taken 3205 times.
3205 ContactCard named;
85 3205 named.formattedName = person.name;
86
4/6
✓ Branch 81 → 82 taken 3205 times.
✓ Branch 97 → 98 taken 3205 times.
✓ Branch 100 → 101 taken 9615 times.
✓ Branch 100 → 102 taken 3205 times.
✗ Branch 283 → 284 not taken.
✗ Branch 283 → 285 not taken.
19230 m_avatars.insert(person.cardId, {{QStringLiteral("initials"), ContactIndex::initials(named)},
87
3/3
✓ Branch 86 → 87 taken 3205 times.
✓ Branch 87 → 88 taken 3205 times.
✓ Branch 88 → 89 taken 3205 times.
6410 {QStringLiteral("avatarColor"), ContactIndex::avatarColorIndex(q.value(2).toString())},
88
2/2
✓ Branch 93 → 94 taken 3205 times.
✓ Branch 94 → 95 taken 3205 times.
6410 {QStringLiteral("hasPhoto"), q.value(5).toBool()}});
89
1/1
✓ Branch 112 → 113 taken 3205 times.
3205 m_input.people.append(person);
90 3205 }
91 }
92
1/1
✓ Branch 118 → 119 taken 47 times.
47 QSqlQuery groups(db);
93
1/1
✓ Branch 119 → 120 taken 47 times.
47 groups.setForwardOnly(true);
94
2/3
✓ Branch 123 → 124 taken 47 times.
✓ Branch 126 → 127 taken 47 times.
✗ Branch 126 → 142 not taken.
47 if (groups.exec(QStringLiteral("SELECT card_id, name FROM card_groups")))
95
3/3
✓ Branch 140 → 141 taken 286 times.
✓ Branch 141 → 128 taken 239 times.
✓ Branch 141 → 142 taken 47 times.
286 while (groups.next())
96
3/4
✓ Branch 128 → 129 taken 239 times.
✓ Branch 129 → 130 taken 239 times.
✓ Branch 132 → 133 taken 239 times.
✗ Branch 132 → 140 not taken.
239 if (const int index = m_personIndex.value(groups.value(0).toLongLong(), -1); index >= 0)
97
4/4
✓ Branch 133 → 134 taken 239 times.
✓ Branch 134 → 135 taken 239 times.
✓ Branch 135 → 136 taken 239 times.
✓ Branch 136 → 137 taken 239 times.
239 m_input.people[index].groups.append(groups.value(1).toString());
98
5/5
✓ Branch 142 → 143 taken 47 times.
✓ Branch 143 → 144 taken 47 times.
✓ Branch 144 → 145 taken 47 times.
✓ Branch 155 → 146 taken 1043 times.
✓ Branch 155 → 156 taken 47 times.
1090 for (const RelationIndex::Edge &edge : RelationIndex::allEdges(db))
99 1090 m_input.edges.append({edge.fromCardId, edge.toCardId, edge.role, edge.type, edge.label, edge.sourceIndex, edge.toName});
100 // S106-273: overview positions of people no longer in the book go (one
101 // entry per contact ever shown used to stay for the session).
102
1/1
✓ Branch 157 → 158 taken 47 times.
47 m_overviewPositions.removeIf([this](const QHash<QString, QPointF>::iterator &it) {
103
3/5
✓ Branch 5 → 6 taken 124 times.
✓ Branch 6 → 7 taken 124 times.
✗ Branch 6 → 11 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 124 times.
124 return it.key().contains(QLatin1Char('/')) && !m_cardIds.contains(it.key());
104 });
105
2/2
✓ Branch 158 → 159 taken 7 times.
✓ Branch 158 → 170 taken 40 times.
47 if (m_suggestions) // S103.5.8: the last answer stays until the worker has a new one
106
2/2
✓ Branch 168 → 160 taken 7 times.
✓ Branch 168 → 169 taken 7 times.
14 for (auto it = m_suggestionCounts.cbegin(); it != m_suggestionCounts.cend(); ++it)
107
1/1
✓ Branch 164 → 165 taken 7 times.
7 m_input.suggestions.append({it.key().first, it.key().second, it.value().first + it.value().second});
108
1/1
✓ Branch 170 → 171 taken 47 times.
47 RelationGraph::Families::addNamedFamilies(&m_input); // F103-21
109 // F103-31: relationships that follow from the stored ones, once per pair.
110 47 QList<RelationInference::Fact> facts;
111
2/2
✓ Branch 190 → 174 taken 1043 times.
✓ Branch 190 → 191 taken 47 times.
1090 for (const RelationGraph::StoredEdge &edge : std::as_const(m_input.edges))
112
7/8
✓ Branch 177 → 178 taken 938 times.
✓ Branch 177 → 182 taken 105 times.
✓ Branch 178 → 179 taken 918 times.
✓ Branch 178 → 182 taken 20 times.
✓ Branch 180 → 181 taken 918 times.
✗ Branch 180 → 182 not taken.
✓ Branch 183 → 184 taken 918 times.
✓ Branch 183 → 188 taken 125 times.
1043 if (edge.role == QLatin1String("relation") && edge.toCardId > 0 && !RelationGraph::Families::isNamedFamily(edge.toCardId))
113
1/1
✓ Branch 185 → 186 taken 918 times.
918 facts.append({edge.fromCardId, edge.toCardId, edge.type});
114
5/5
✓ Branch 191 → 192 taken 47 times.
✓ Branch 192 → 193 taken 47 times.
✓ Branch 193 → 194 taken 47 times.
✓ Branch 207 → 195 taken 1116 times.
✓ Branch 207 → 208 taken 47 times.
1163 for (const RelationInference::Inferred &found : RelationInference::infer(facts))
115
2/2
✓ Branch 196 → 197 taken 558 times.
✓ Branch 196 → 205 taken 558 times.
1116 if (found.holder < found.other)
116 605 m_input.edges.append({found.holder, found.other, QStringLiteral("inferred"), found.type, QString(), -1, QString()});
117
9/9
✓ Branch 209 → 210 taken 47 times.
✓ Branch 213 → 214 taken 47 times.
✓ Branch 214 → 215 taken 47 times.
✓ Branch 216 → 217 taken 47 times.
✓ Branch 217 → 218 taken 47 times.
✓ Branch 219 → 220 taken 47 times.
✓ Branch 220 → 221 taken 47 times.
✓ Branch 227 → 211 taken 47 times.
✓ Branch 227 → 228 taken 47 times.
94 qCInfo(lcGraph) << "Graph input:" << m_input.people.size() << "people," << m_input.edges.size() << "edges in"
118
2/2
✓ Branch 222 → 223 taken 47 times.
✓ Branch 223 → 224 taken 47 times.
47 << timer.elapsed() << "ms";
119 15944 }
120
121 85 void QuickContactGraph::reload() {
122 85 m_loaded = false;
123
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 84 times.
85 if (m_suggestions)
124 1 computeSuggestions(); // cards or mails changed
125
5/6
✓ Branch 5 → 6 taken 74 times.
✓ Branch 5 → 8 taken 11 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 74 times.
✓ Branch 10 → 11 taken 11 times.
✓ Branch 10 → 12 taken 74 times.
85 if (!m_centerKey.isEmpty() || !m_nodes.isEmpty())
126 11 rebuild();
127 85 }
128
129 // F103-06: the graph starts from me — the me card, else the card with one of
130 // the account addresses (the "Is this you?" card).
131 35 QString QuickContactGraph::defaultCenter() const {
132
1/1
✓ Branch 2 → 3 taken 35 times.
35 const QString me = meKey();
133
2/2
✓ Branch 4 → 5 taken 9 times.
✓ Branch 4 → 7 taken 26 times.
35 if (m_cardIds.contains(me))
134 9 return me;
135
3/3
✓ Branch 8 → 9 taken 25 times.
✓ Branch 8 → 10 taken 1 time.
✓ Branch 9 → 11 taken 25 times.
26 const QString suggested = m_meFallback ? m_meFallback() : QString();
136
2/2
✓ Branch 12 → 13 taken 6 times.
✓ Branch 12 → 14 taken 20 times.
26 return m_cardIds.contains(suggested) ? suggested : QString();
137 35 }
138
139 206 void QuickContactGraph::show(const QString &key, bool recenter) {
140
1/1
✓ Branch 2 → 3 taken 206 times.
206 ensureInput();
141
2/2
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 19 taken 199 times.
206 if (m_overview) {
142 // In the overview a click only selects (a person off-screen is brought
143 // into view, so the list finds people in a large graph); Enter/double
144 // click centres the person.
145
5/6
✓ Branch 4 → 5 taken 6 times.
✓ Branch 4 → 7 taken 1 time.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 12 taken 6 times.
7 if (!recenter || !m_cardIds.contains(key)) {
146
1/1
✓ Branch 10 → 11 taken 1 time.
1 reveal(key);
147 144 return;
148 }
149 6 m_overview = false;
150
1/1
✓ Branch 13 → 14 taken 6 times.
6 m_history.removeAll(QLatin1String(kOverviewKey));
151
2/2
✓ Branch 15 → 16 taken 6 times.
✓ Branch 16 → 17 taken 6 times.
6 m_history.append(QLatin1String(kOverviewKey));
152 }
153 205 QString next = key;
154
3/4
✓ Branch 21 → 22 taken 169 times.
✓ Branch 21 → 25 taken 36 times.
✓ Branch 23 → 24 taken 169 times.
✗ Branch 23 → 25 not taken.
205 const bool hasCenter = !m_centerKey.isEmpty() && m_cardIds.contains(m_centerKey);
155
11/13
✓ Branch 26 → 27 taken 25 times.
✓ Branch 26 → 32 taken 180 times.
✓ Branch 27 → 28 taken 17 times.
✓ Branch 27 → 32 taken 8 times.
✓ Branch 28 → 29 taken 17 times.
✓ Branch 30 → 31 taken 6 times.
✓ Branch 30 → 32 taken 11 times.
✓ Branch 33 → 34 taken 17 times.
✓ Branch 33 → 35 taken 188 times.
✓ Branch 35 → 36 taken 6 times.
✓ Branch 35 → 40 taken 199 times.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
205 if (!recenter && !hasCenter && !defaultCenter().isEmpty()) {
156 // Opening the graph: me, not the contact that happens to be selected.
157
1/1
✓ Branch 36 → 37 taken 6 times.
6 next = defaultCenter();
158
5/6
✓ Branch 41 → 42 taken 190 times.
✓ Branch 41 → 44 taken 9 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 190 times.
✓ Branch 46 → 47 taken 9 times.
✓ Branch 46 → 53 taken 190 times.
199 } else if (next.isEmpty() || !m_cardIds.contains(next)) {
159 // §7.2: the previous centre, else me.
160
2/3
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 9 times.
✓ Branch 49 → 50 taken 9 times.
9 next = hasCenter ? m_centerKey : defaultCenter();
161
4/4
✓ Branch 53 → 54 taken 10 times.
✓ Branch 53 → 66 taken 180 times.
✓ Branch 54 → 55 taken 8 times.
✓ Branch 54 → 66 taken 2 times.
190 } else if (!recenter && hasCenter) {
162 // A plain selection keeps the centre when the person is already shown.
163
2/2
✓ Branch 64 → 58 taken 39 times.
✓ Branch 64 → 65 taken 2 times.
41 for (const RelationGraph::Node &node : std::as_const(m_graph.nodes))
164
2/2
✓ Branch 60 → 61 taken 6 times.
✓ Branch 60 → 62 taken 33 times.
39 if (node.key == next)
165 6 return;
166 }
167
6/6
✓ Branch 67 → 68 taken 146 times.
✓ Branch 67 → 71 taken 53 times.
✓ Branch 69 → 70 taken 137 times.
✓ Branch 69 → 71 taken 9 times.
✓ Branch 72 → 73 taken 137 times.
✓ Branch 72 → 74 taken 62 times.
199 if (next == m_centerKey && !m_nodes.isEmpty())
168 137 return;
169
5/6
✓ Branch 75 → 76 taken 26 times.
✓ Branch 75 → 79 taken 36 times.
✓ Branch 77 → 78 taken 26 times.
✗ Branch 77 → 79 not taken.
✓ Branch 80 → 81 taken 26 times.
✓ Branch 80 → 87 taken 36 times.
62 if (!m_centerKey.isEmpty() && next != m_centerKey) {
170
1/1
✓ Branch 81 → 82 taken 26 times.
26 m_history.removeAll(next);
171
1/1
✓ Branch 82 → 83 taken 26 times.
26 m_history.append(m_centerKey);
172
2/2
✓ Branch 86 → 84 taken 9 times.
✓ Branch 86 → 87 taken 26 times.
35 while (m_history.size() > 4)
173 9 m_history.removeFirst();
174 }
175 62 m_centerKey = next;
176
1/1
✓ Branch 88 → 89 taken 62 times.
62 clearPath();
177
1/1
✓ Branch 89 → 90 taken 62 times.
62 rebuild();
178 205 }
179
180 4 void QuickContactGraph::back() { backTo(int(m_history.size()) - 1); }
181
182 7 void QuickContactGraph::backTo(int index) {
183
5/6
✓ Branch 2 → 3 taken 7 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 6 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 6 times.
7 if (index < 0 || index >= m_history.size())
184 1 return;
185
8/8
✓ Branch 9 → 10 taken 6 times.
✓ Branch 13 → 14 taken 6 times.
✓ Branch 14 → 15 taken 6 times.
✓ Branch 15 → 16 taken 6 times.
✓ Branch 16 → 17 taken 6 times.
✓ Branch 18 → 19 taken 6 times.
✓ Branch 22 → 11 taken 6 times.
✓ Branch 22 → 23 taken 6 times.
12 qCInfo(lcGraph) << "Back to history entry" << index << "of" << m_history.size();
186 6 const QString previous = m_history.at(index);
187
1/1
✓ Branch 25 → 26 taken 6 times.
6 m_history = m_history.mid(0, index);
188 6 m_overview = previous == QLatin1String(kOverviewKey);
189
2/2
✓ Branch 30 → 31 taken 3 times.
✓ Branch 30 → 32 taken 3 times.
6 m_centerKey = m_overview ? QString() : previous;
190
1/1
✓ Branch 35 → 36 taken 6 times.
6 clearPath();
191
1/1
✓ Branch 36 → 37 taken 6 times.
6 rebuild();
192 6 }
193
194 namespace {
195
196 struct Stroke {
197 const char *style;
198 double width;
199 };
200
201 // What a line means (DESIGN.md "Relationship graph"): family lines are
202 // solid — descent, partners and siblings strongest, the wider family
203 // thinner; friends dashed (acquaintances short dashes), work dash-dotted (a
204 // manager above a team member as an arrow), other thin and solid;
205 // introductions and suggestions dotted, derived layers thin. The dash
206 // patterns are ContactGraphEdges.dashOf.
207 // The wedding year on a partner line (the rings, ContactGraphEdges): the
208 // anniversary year of the card that stores the line, else of the partner;
209 // 0 without one, for former partners (the anniversary is that of the
210 // present marriage) and for every other line.
211 6398 int weddingYear(const RelationGraph::Edge &edge, const RelationGraph::Person *holder,
212 const RelationGraph::Person *target) {
213
4/4
✓ Branch 4 → 5 taken 3350 times.
✓ Branch 4 → 8 taken 3048 times.
✓ Branch 7 → 8 taken 2898 times.
✓ Branch 7 → 12 taken 452 times.
12344 if ((edge.kind != QLatin1String("relation") && edge.kind != QLatin1String("inferred")) ||
214
7/9
✓ Branch 9 → 10 taken 5946 times.
✓ Branch 11 → 12 taken 5494 times.
✓ Branch 11 → 13 taken 452 times.
✓ Branch 14 → 15 taken 5946 times.
✓ Branch 14 → 16 taken 452 times.
✓ Branch 16 → 17 taken 5946 times.
✓ Branch 16 → 18 taken 452 times.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
12344 RelationVocabulary::canonical(edge.type) != QLatin1String("spouse"))
215 5946 return 0;
216
2/2
✓ Branch 42 → 20 taken 676 times.
✓ Branch 42 → 43 taken 80 times.
756 for (const RelationGraph::Person *person : {holder, target}) {
217
1/2
✓ Branch 20 → 21 taken 676 times.
✗ Branch 20 → 22 not taken.
676 const QString date = person ? person->anniversary : QString();
218
6/7
✓ Branch 24 → 25 taken 372 times.
✓ Branch 24 → 29 taken 304 times.
✓ Branch 26 → 27 taken 372 times.
✓ Branch 27 → 28 taken 372 times.
✗ Branch 27 → 29 not taken.
✓ Branch 30 → 31 taken 372 times.
✓ Branch 30 → 36 taken 304 times.
676 if (date.size() >= 4 && !date.startsWith(QLatin1String("--")))
219
2/2
✓ Branch 31 → 32 taken 372 times.
✓ Branch 32 → 33 taken 372 times.
372 return date.left(4).toInt();
220 676 }
221 80 return 0;
222 }
223
224 // The texts on the lines (ContactGraphLineLabels): the number of lines on
225 // each bundle's trunk (its centre) and a couple's wedding year above their
226 // rings (bottom centre) — only where the rings have room between the two
227 // avatars and not far out in Everyone, as ContactGraphEdges.bond() draws
228 // them. `edges` are the projected lines (canvas coordinates); `avatarRadius`
229 // gives a node's drawn avatar radius. [{kind, x, y, text, lines}]
230 324 QVariantList lineLabelsOf(const QVariantList &edges, bool yearsShown, const std::function<double(int)> &avatarRadius) {
231 324 constexpr double RingSize = 20; // ContactGraphEdges.bond() at full detail
232 324 QVariantList out;
233 324 QHash<QString, int> trunks; // rounded trunk ends → index in out
234
2/2
✓ Branch 391 → 3 taken 3199 times.
✓ Branch 391 → 392 taken 324 times.
3523 for (int i = 0; i < edges.size(); ++i) {
235
1/1
✓ Branch 4 → 5 taken 3199 times.
3199 const QVariantMap edge = edges.at(i).toMap();
236
2/2
✓ Branch 9 → 10 taken 3199 times.
✓ Branch 10 → 11 taken 3199 times.
6398 const QString shape = edge.value(QStringLiteral("shape")).toString();
237
14/29
✓ Branch 16 → 17 taken 3199 times.
✓ Branch 21 → 22 taken 3199 times.
✓ Branch 22 → 23 taken 3199 times.
✓ Branch 23 → 24 taken 3168 times.
✓ Branch 23 → 27 taken 31 times.
✓ Branch 26 → 27 taken 946 times.
✓ Branch 26 → 28 taken 2222 times.
✓ Branch 29 → 30 taken 3199 times.
✗ Branch 29 → 31 not taken.
✓ Branch 31 → 32 taken 3199 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 3199 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 3199 times.
✗ Branch 35 → 37 not taken.
✓ Branch 37 → 38 taken 3199 times.
✗ Branch 37 → 39 not taken.
✓ Branch 39 → 40 taken 977 times.
✓ Branch 39 → 41 taken 2222 times.
✗ Branch 408 → 409 not taken.
✗ Branch 408 → 410 not taken.
✗ Branch 412 → 413 not taken.
✗ Branch 412 → 414 not taken.
✗ Branch 416 → 417 not taken.
✗ Branch 416 → 418 not taken.
✗ Branch 420 → 421 not taken.
✗ Branch 420 → 422 not taken.
✗ Branch 424 → 425 not taken.
✗ Branch 424 → 426 not taken.
6398 if (edge.value(QStringLiteral("kind")) == QLatin1String("member") || shape == QLatin1String("none"))
238 977 continue;
239
2/2
✓ Branch 45 → 46 taken 2222 times.
✓ Branch 46 → 47 taken 2222 times.
4444 const QVariantList p = edge.value(QStringLiteral("points")).toList();
240
16/26
✓ Branch 53 → 54 taken 16 times.
✓ Branch 53 → 64 taken 2206 times.
✓ Branch 55 → 56 taken 16 times.
✗ Branch 55 → 64 not taken.
✓ Branch 60 → 61 taken 16 times.
✓ Branch 61 → 62 taken 16 times.
✓ Branch 62 → 63 taken 16 times.
✗ Branch 62 → 64 not taken.
✓ Branch 65 → 66 taken 16 times.
✓ Branch 65 → 67 taken 2206 times.
✓ Branch 67 → 68 taken 16 times.
✓ Branch 67 → 69 taken 2206 times.
✓ Branch 69 → 70 taken 16 times.
✓ Branch 69 → 71 taken 2206 times.
✓ Branch 71 → 72 taken 16 times.
✓ Branch 71 → 73 taken 2206 times.
✓ Branch 73 → 74 taken 16 times.
✓ Branch 73 → 190 taken 2206 times.
✗ Branch 441 → 442 not taken.
✗ Branch 441 → 443 not taken.
✗ Branch 445 → 446 not taken.
✗ Branch 445 → 447 not taken.
✗ Branch 449 → 450 not taken.
✗ Branch 449 → 451 not taken.
✗ Branch 453 → 454 not taken.
✗ Branch 453 → 455 not taken.
2238 if (shape == QLatin1String("bundle") && p.size() == 12 && edge.value(QStringLiteral("bundle")).toInt() >= 2) {
241 16 QStringList ends;
242
2/2
✓ Branch 81 → 75 taken 64 times.
✓ Branch 81 → 82 taken 16 times.
80 for (int k = 4; k <= 7; ++k)
243
3/3
✓ Branch 76 → 77 taken 64 times.
✓ Branch 77 → 78 taken 64 times.
✓ Branch 78 → 79 taken 64 times.
64 ends << QString::number(std::lround(p.at(k).toDouble()));
244
1/1
✓ Branch 84 → 85 taken 16 times.
16 const QString key = ends.join(QLatin1Char(','));
245
2/2
✓ Branch 86 → 87 taken 8 times.
✓ Branch 86 → 155 taken 8 times.
16 if (!trunks.contains(key)) {
246
1/1
✓ Branch 88 → 89 taken 8 times.
8 trunks.insert(key, int(out.size()));
247
1/1
✓ Branch 130 → 131 taken 8 times.
32 out.append(QVariantMap{{QStringLiteral("kind"), QStringLiteral("count")},
248
2/2
✓ Branch 100 → 101 taken 8 times.
✓ Branch 102 → 103 taken 8 times.
16 {QStringLiteral("x"), (p.at(4).toDouble() + p.at(6).toDouble()) / 2},
249
2/2
✓ Branch 108 → 109 taken 8 times.
✓ Branch 110 → 111 taken 8 times.
16 {QStringLiteral("y"), (p.at(5).toDouble() + p.at(7).toDouble()) / 2},
250
3/3
✓ Branch 119 → 120 taken 8 times.
✓ Branch 120 → 121 taken 8 times.
✓ Branch 121 → 122 taken 8 times.
24 {QStringLiteral("text"), QString::number(edge.value(QStringLiteral("bundle")).toInt())},
251
2/4
✓ Branch 134 → 135 taken 40 times.
✓ Branch 134 → 136 taken 8 times.
✗ Branch 469 → 470 not taken.
✗ Branch 469 → 471 not taken.
64 {QStringLiteral("lines"), QVariantList()}});
252 }
253
1/1
✓ Branch 157 → 158 taken 16 times.
16 QVariantMap entry = out.at(trunks.value(key)).toMap();
254
4/4
✓ Branch 162 → 163 taken 16 times.
✓ Branch 163 → 164 taken 16 times.
✓ Branch 165 → 166 taken 16 times.
✓ Branch 170 → 171 taken 16 times.
48 entry[QStringLiteral("lines")] = entry.value(QStringLiteral("lines")).toList() << i;
255
1/1
✓ Branch 183 → 184 taken 16 times.
16 out[trunks.value(key)] = entry;
256 16 }
257
2/2
✓ Branch 194 → 195 taken 2222 times.
✓ Branch 195 → 196 taken 2222 times.
4444 const int year = edge.value(QStringLiteral("year")).toInt();
258
3/4
✓ Branch 200 → 201 taken 2222 times.
✗ Branch 200 → 202 not taken.
✓ Branch 201 → 202 taken 2036 times.
✓ Branch 201 → 203 taken 186 times.
2222 if (!yearsShown || year <= 0)
259 2036 continue;
260 // The point halfway along the drawn route and the route's length.
261
2/2
✓ Branch 207 → 208 taken 186 times.
✓ Branch 208 → 209 taken 186 times.
372 QVariantList path = edge.value(QStringLiteral("path")).toList();
262
1/2
✗ Branch 214 → 215 not taken.
✓ Branch 214 → 253 taken 186 times.
186 if (path.size() < 4)
263 ✗ path = {edge.value(QStringLiteral("x1")), edge.value(QStringLiteral("y1")), edge.value(QStringLiteral("x2")),
264 ✗ edge.value(QStringLiteral("y2"))};
265 186 double total = 0;
266
2/2
✓ Branch 264 → 254 taken 186 times.
✓ Branch 264 → 265 taken 186 times.
372 for (qsizetype k = 2; k + 1 < path.size(); k += 2)
267
8/8
✓ Branch 254 → 255 taken 186 times.
✓ Branch 255 → 256 taken 186 times.
✓ Branch 256 → 257 taken 186 times.
✓ Branch 257 → 258 taken 186 times.
✓ Branch 258 → 259 taken 186 times.
✓ Branch 259 → 260 taken 186 times.
✓ Branch 260 → 261 taken 186 times.
✓ Branch 261 → 262 taken 186 times.
186 total += std::hypot(path[k].toDouble() - path[k - 2].toDouble(), path[k + 1].toDouble() - path[k - 1].toDouble());
268
3/3
✓ Branch 269 → 270 taken 186 times.
✓ Branch 270 → 271 taken 186 times.
✓ Branch 271 → 272 taken 186 times.
558 const double room = total - avatarRadius(edge.value(QStringLiteral("from")).toInt()) -
269
3/3
✓ Branch 276 → 277 taken 186 times.
✓ Branch 277 → 278 taken 186 times.
✓ Branch 278 → 279 taken 186 times.
372 avatarRadius(edge.value(QStringLiteral("to")).toInt());
270
2/2
✓ Branch 287 → 288 taken 48 times.
✓ Branch 287 → 289 taken 138 times.
186 if (room < RingSize + 10)
271 48 continue;
272
4/4
✓ Branch 289 → 290 taken 138 times.
✓ Branch 290 → 291 taken 138 times.
✓ Branch 291 → 292 taken 138 times.
✓ Branch 292 → 293 taken 138 times.
138 QPointF middle(path[0].toDouble(), path[1].toDouble());
273 138 double left = total / 2;
274
1/2
✓ Branch 317 → 295 taken 138 times.
✗ Branch 317 → 318 not taken.
138 for (qsizetype k = 2; k + 1 < path.size(); k += 2) {
275
8/8
✓ Branch 295 → 296 taken 138 times.
✓ Branch 296 → 297 taken 138 times.
✓ Branch 297 → 298 taken 138 times.
✓ Branch 298 → 299 taken 138 times.
✓ Branch 300 → 301 taken 138 times.
✓ Branch 301 → 302 taken 138 times.
✓ Branch 302 → 303 taken 138 times.
✓ Branch 303 → 304 taken 138 times.
138 const QPointF a(path[k - 2].toDouble(), path[k - 1].toDouble()), b(path[k].toDouble(), path[k + 1].toDouble());
276 138 const double step = std::hypot(b.x() - a.x(), b.y() - a.y());
277
2/4
✓ Branch 309 → 310 taken 138 times.
✗ Branch 309 → 315 not taken.
✓ Branch 310 → 311 taken 138 times.
✗ Branch 310 → 315 not taken.
138 if (step >= left && step > 0) {
278 138 middle = a + (b - a) * (left / step);
279 138 break;
280 }
281 ✗ left -= step;
282 }
283
1/1
✓ Branch 348 → 349 taken 138 times.
552 out.append(QVariantMap{{QStringLiteral("kind"), QStringLiteral("year")},
284 276 {QStringLiteral("x"), middle.x()},
285 276 {QStringLiteral("y"), middle.y() - RingSize * 0.3 - 3},
286
1/1
✓ Branch 338 → 339 taken 138 times.
276 {QStringLiteral("text"), QString::number(year)},
287
4/8
✓ Branch 352 → 353 taken 690 times.
✓ Branch 352 → 354 taken 138 times.
✓ Branch 356 → 357 taken 138 times.
✓ Branch 356 → 358 taken 138 times.
✗ Branch 674 → 675 not taken.
✗ Branch 674 → 676 not taken.
✗ Branch 680 → 681 not taken.
✗ Branch 680 → 682 not taken.
1104 {QStringLiteral("lines"), QVariantList{i}}});
288 8392 }
289 324 return out;
290 1200 }
291
292 3199 Stroke strokeOf(const RelationGraph::Edge &edge) {
293
2/2
✓ Branch 4 → 5 taken 188 times.
✓ Branch 4 → 6 taken 3011 times.
3199 if (edge.kind == QLatin1String("via"))
294 188 return {"via", 1.5};
295
2/2
✓ Branch 8 → 9 taken 7 times.
✓ Branch 8 → 10 taken 3004 times.
3011 if (edge.kind == QLatin1String("suggestion"))
296 7 return {"suggestion", 1.5};
297
6/6
✓ Branch 12 → 13 taken 1480 times.
✓ Branch 12 → 17 taken 1524 times.
✓ Branch 15 → 16 taken 31 times.
✓ Branch 15 → 17 taken 1449 times.
✓ Branch 18 → 19 taken 31 times.
✓ Branch 18 → 20 taken 2973 times.
3004 if (edge.kind != QLatin1String("relation") && edge.kind != QLatin1String("inferred"))
298 31 return {"layer", 1};
299
1/1
✓ Branch 20 → 21 taken 2973 times.
2973 const QString type = RelationVocabulary::canonical(edge.type);
300
3/3
✓ Branch 21 → 22 taken 2973 times.
✓ Branch 22 → 23 taken 1347 times.
✓ Branch 22 → 24 taken 1626 times.
2973 if (RelationGraph::isDescent(type))
301 1347 return {"descent", 2.5};
302
2/2
✓ Branch 26 → 27 taken 226 times.
✓ Branch 26 → 28 taken 1400 times.
1626 if (type == QLatin1String("spouse"))
303 226 return {"partner", 2};
304
2/2
✓ Branch 30 → 31 taken 16 times.
✓ Branch 30 → 32 taken 1384 times.
1400 if (type == QLatin1String("ex-partner"))
305 16 return {"expartner", 1.5};
306
2/2
✓ Branch 34 → 35 taken 339 times.
✓ Branch 34 → 36 taken 1045 times.
1384 if (type == QLatin1String("sibling"))
307 339 return {"sibling", 2};
308
2/2
✓ Branch 38 → 39 taken 723 times.
✓ Branch 38 → 40 taken 322 times.
1045 if (edge.category == QLatin1String("family"))
309 723 return {"kin", 1.5};
310
2/2
✓ Branch 42 → 43 taken 111 times.
✓ Branch 42 → 44 taken 211 times.
322 if (type == QLatin1String("friend"))
311 111 return {"friend", 2};
312
2/2
✓ Branch 46 → 47 taken 7 times.
✓ Branch 46 → 48 taken 204 times.
211 if (type == QLatin1String("acquaintance"))
313 7 return {"acquaintance", 1.2};
314
5/6
✓ Branch 50 → 51 taken 204 times.
✗ Branch 50 → 54 not taken.
✓ Branch 53 → 54 taken 74 times.
✓ Branch 53 → 55 taken 130 times.
✓ Branch 56 → 57 taken 74 times.
✓ Branch 56 → 58 taken 130 times.
204 if (type == QLatin1String("x-manager") || type == QLatin1String("x-report"))
315 74 return {"hierarchy", 2};
316
2/2
✓ Branch 60 → 61 taken 55 times.
✓ Branch 60 → 62 taken 75 times.
130 return {edge.category == QLatin1String("work") ? "work" : "other", 1.5};
317 2973 }
318
319 } // namespace
320
321 // The node an arrowhead points at, else -1: the younger generation (F103-32),
322 // the team member under a manager, the person someone introduced.
323 3199 int QuickContactGraph::arrowTarget(const RelationGraph::Edge &edge) const {
324
2/2
✓ Branch 4 → 5 taken 188 times.
✓ Branch 4 → 6 taken 3011 times.
3199 if (edge.kind == QLatin1String("via"))
325 188 return edge.from; // "from was introduced by to"
326
6/6
✓ Branch 8 → 9 taken 1487 times.
✓ Branch 8 → 13 taken 1524 times.
✓ Branch 11 → 12 taken 38 times.
✓ Branch 11 → 13 taken 1449 times.
✓ Branch 14 → 15 taken 38 times.
✓ Branch 14 → 16 taken 2973 times.
3011 if (edge.kind != QLatin1String("relation") && edge.kind != QLatin1String("inferred"))
327 38 return -1;
328
1/1
✓ Branch 16 → 17 taken 2973 times.
2973 const QString type = RelationVocabulary::canonical(edge.type);
329
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 2973 times.
2973 if (type == QLatin1String("x-manager"))
330 ✗ return edge.from; // "to is from's manager"
331
2/2
✓ Branch 23 → 24 taken 74 times.
✓ Branch 23 → 25 taken 2899 times.
2973 if (type == QLatin1String("x-report"))
332 74 return edge.to;
333 2899 int step = 0;
334
8/10
✓ Branch 25 → 26 taken 2899 times.
✓ Branch 26 → 27 taken 1347 times.
✓ Branch 26 → 30 taken 1552 times.
✓ Branch 27 → 28 taken 1347 times.
✓ Branch 28 → 29 taken 1347 times.
✗ Branch 28 → 30 not taken.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 1347 times.
✓ Branch 32 → 33 taken 1552 times.
✓ Branch 32 → 34 taken 1347 times.
2899 if (!RelationGraph::isDescent(type) || !RelationGraph::generationStep(type, &step) || step == 0)
335 1552 return -1;
336
2/2
✓ Branch 34 → 35 taken 1224 times.
✓ Branch 34 → 36 taken 123 times.
1347 return step > 0 ? edge.from : edge.to; // "to is from's parent": from is younger
337 2973 }
338
339 // The word under a person's name: what they are to the centre ("Uncle"),
340 // stored or following from others; "" in the overview and for the centre.
341 2600 QString QuickContactGraph::roleOf(int index) const {
342 2600 const RelationGraph::Node &node = m_graph.nodes.at(index);
343
5/6
✓ Branch 3 → 4 taken 2455 times.
✓ Branch 3 → 6 taken 145 times.
✓ Branch 4 → 5 taken 2188 times.
✓ Branch 4 → 6 taken 267 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2188 times.
2600 if (m_overview || node.center || node.cardId <= 0)
344 412 return {};
345 2188 const qint64 centre = cardIdOf(m_centerKey);
346 2188 QString role;
347
2/2
✓ Branch 59 → 10 taken 85464 times.
✓ Branch 59 → 60 taken 949 times.
86413 for (const RelationGraph::StoredEdge &edge : m_input.edges) {
348
4/4
✓ Branch 11 → 12 taken 9861 times.
✓ Branch 11 → 14 taken 75603 times.
✓ Branch 12 → 13 taken 1309 times.
✓ Branch 12 → 14 taken 8552 times.
85464 const bool out = edge.fromCardId == centre && edge.toCardId == node.cardId;
349
4/4
✓ Branch 15 → 16 taken 5257 times.
✓ Branch 15 → 18 taken 80207 times.
✓ Branch 16 → 17 taken 871 times.
✓ Branch 16 → 18 taken 4386 times.
85464 const bool in = edge.toCardId == centre && edge.fromCardId == node.cardId;
350
4/4
✓ Branch 19 → 20 taken 84155 times.
✓ Branch 19 → 22 taken 1309 times.
✓ Branch 20 → 21 taken 83284 times.
✓ Branch 20 → 22 taken 871 times.
85464 if (!out && !in)
351 83284 continue;
352
5/6
✓ Branch 24 → 25 taken 128 times.
✓ Branch 24 → 28 taken 2052 times.
✓ Branch 26 → 27 taken 128 times.
✗ Branch 26 → 28 not taken.
✓ Branch 29 → 30 taken 128 times.
✓ Branch 29 → 36 taken 2052 times.
2180 if (edge.role == QLatin1String("via") && role.isEmpty())
353 //: Under a person in the graph: they introduced the person in the middle ("Introducer"), or were introduced by them.
354
4/4
✓ Branch 30 → 31 taken 62 times.
✓ Branch 30 → 32 taken 66 times.
✓ Branch 31 → 33 taken 62 times.
✓ Branch 32 → 33 taken 66 times.
128 role = out ? tr("Introducer") : tr("Introduced");
355
5/8
✓ Branch 38 → 39 taken 813 times.
✓ Branch 38 → 44 taken 1239 times.
✓ Branch 41 → 42 taken 813 times.
✗ Branch 41 → 45 not taken.
✓ Branch 43 → 44 taken 813 times.
✗ Branch 43 → 45 not taken.
✓ Branch 46 → 47 taken 2052 times.
✗ Branch 46 → 53 not taken.
2052 else if (edge.role == QLatin1String("relation") || (edge.role == QLatin1String("inferred") && role.isEmpty()))
356
3/3
✓ Branch 47 → 48 taken 1247 times.
✓ Branch 47 → 49 taken 805 times.
✓ Branch 48 → 50 taken 1247 times.
4104 role = out ? RelationVocabulary::label(edge.type, node.gender, edge.label)
357
1/1
✓ Branch 49 → 50 taken 805 times.
2052 : RelationVocabulary::inverseLabel(edge.type, node.gender, edge.label);
358
2/2
✓ Branch 55 → 56 taken 1239 times.
✓ Branch 55 → 57 taken 941 times.
2180 if (edge.role == QLatin1String("relation"))
359 1239 break; // a stored relationship names it best
360 }
361
6/6
✓ Branch 61 → 62 taken 8 times.
✓ Branch 61 → 66 taken 2180 times.
✓ Branch 64 → 65 taken 7 times.
✓ Branch 64 → 66 taken 1 time.
✓ Branch 67 → 68 taken 7 times.
✓ Branch 67 → 72 taken 2181 times.
2188 if (role.isEmpty() && node.sector == QLatin1String("suggestions"))
362 //: Under a suggested person in the graph (S103.5.8): often in mails or appointments together.
363
1/1
✓ Branch 68 → 69 taken 7 times.
7 role = tr("Often together");
364 2188 return role;
365 2188 }
366
367 // *Everyone* shows no tree whatever the setting (the top bar's *Family tree*
368 // is not checked there): turning the tree on there leaves the overview for
369 // the tree of the last centre before it (else me), and Back returns to it.
370 5 void QuickContactGraph::setFamilyTree(bool on) {
371
4/4
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 5 taken 2 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 2 times.
5 const bool leaveOverview = on && m_overview;
372
3/4
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 4 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
5 if (on == m_familyTree && !leaveOverview)
373 ✗ return;
374 5 m_familyTree = on;
375
2/2
✓ Branch 9 → 10 taken 5 times.
✓ Branch 15 → 16 taken 5 times.
10 QSettings().setValue(QStringLiteral("contacts/graphFamilyTree"), on);
376
11/11
✓ Branch 20 → 21 taken 5 times.
✓ Branch 24 → 25 taken 5 times.
✓ Branch 25 → 26 taken 5 times.
✓ Branch 26 → 27 taken 3 times.
✓ Branch 26 → 28 taken 2 times.
✓ Branch 29 → 30 taken 5 times.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 4 times.
✓ Branch 33 → 34 taken 5 times.
✓ Branch 37 → 22 taken 5 times.
✓ Branch 37 → 38 taken 5 times.
10 qCInfo(lcGraph) << "Family tree" << (on ? "on" : "off") << (leaveOverview ? "(leaving Everyone)" : "");
377
2/2
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 77 taken 4 times.
5 if (leaveOverview) {
378 1 QString centre;
379
7/8
✓ Branch 52 → 53 taken 1 time.
✓ Branch 54 → 55 taken 2 times.
✓ Branch 55 → 56 taken 2 times.
✗ Branch 55 → 59 not taken.
✓ Branch 57 → 58 taken 1 time.
✓ Branch 57 → 59 taken 1 time.
✓ Branch 60 → 40 taken 1 time.
✓ Branch 60 → 61 taken 1 time.
2 for (auto it = m_history.crbegin(); it != m_history.crend() && centre.isEmpty(); ++it)
380
5/8
✓ Branch 41 → 42 taken 1 time.
✓ Branch 43 → 44 taken 1 time.
✗ Branch 43 → 48 not taken.
✓ Branch 44 → 45 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
✗ Branch 46 → 48 not taken.
✓ Branch 49 → 50 taken 1 time.
✗ Branch 49 → 52 not taken.
1 if (*it != QLatin1String(kOverviewKey) && m_cardIds.contains(*it))
381
1/1
✓ Branch 50 → 51 taken 1 time.
1 centre = *it;
382
1/1
✓ Branch 61 → 62 taken 1 time.
1 m_history.removeAll(centre);
383
1/1
✓ Branch 63 → 64 taken 1 time.
1 m_history.removeAll(QLatin1String(kOverviewKey));
384
2/2
✓ Branch 65 → 66 taken 1 time.
✓ Branch 66 → 67 taken 1 time.
1 m_history.append(QLatin1String(kOverviewKey));
385 1 m_overview = false;
386
1/3
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 71 taken 1 time.
✗ Branch 70 → 72 not taken.
1 m_centerKey = centre.isEmpty() ? defaultCenter() : centre;
387
1/1
✓ Branch 74 → 75 taken 1 time.
1 clearPath();
388 1 }
389 5 rebuild();
390 }
391
392 3 void QuickContactGraph::setLegendOpen(bool open) {
393
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
3 if (open == m_legendOpen)
394 ✗ return;
395 3 m_legendOpen = open;
396
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 10 → 11 taken 3 times.
6 QSettings().setValue(QStringLiteral("contacts/graphLegendOpen"), open);
397
8/8
✓ Branch 15 → 16 taken 3 times.
✓ Branch 19 → 20 taken 3 times.
✓ Branch 20 → 21 taken 3 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 23 taken 1 time.
✓ Branch 24 → 25 taken 3 times.
✓ Branch 28 → 17 taken 3 times.
✓ Branch 28 → 29 taken 3 times.
6 qCInfo(lcGraph) << "Legend" << (open ? "opened" : "closed");
398 3 emit legendOpenChanged();
399 }
400
401 8 void QuickContactGraph::setKinshipLimit(int limit) {
402 8 limit = qMax(0, limit);
403
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 if (limit == m_kinshipLimit)
404 ✗ return;
405 8 m_kinshipLimit = limit;
406
2/2
✓ Branch 5 → 6 taken 7 times.
✓ Branch 5 → 18 taken 1 time.
8 if (!m_compact)
407
2/2
✓ Branch 6 → 7 taken 7 times.
✓ Branch 12 → 13 taken 7 times.
14 QSettings().setValue(QStringLiteral("contacts/graphKinship"), limit);
408
15/21
✓ Branch 18 → 19 taken 8 times.
✓ Branch 22 → 23 taken 8 times.
✓ Branch 23 → 24 taken 8 times.
✓ Branch 24 → 25 taken 5 times.
✓ Branch 24 → 30 taken 3 times.
✓ Branch 29 → 33 taken 5 times.
✓ Branch 33 → 34 taken 8 times.
✓ Branch 35 → 36 taken 3 times.
✓ Branch 35 → 37 taken 5 times.
✓ Branch 37 → 38 taken 5 times.
✓ Branch 37 → 39 taken 3 times.
✓ Branch 39 → 40 taken 5 times.
✓ Branch 39 → 41 taken 3 times.
✓ Branch 44 → 20 taken 8 times.
✓ Branch 44 → 45 taken 8 times.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 69 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
16 qCInfo(lcGraph) << "Family filter:" << (limit > 0 ? QStringLiteral("up to degree %1").arg(limit) : QStringLiteral("all relatives"));
409 8 rebuild();
410 }
411
412 // The filter's steps from the unfiltered graph (`m_graph` as built): per
413 // degree of kinship the relatives it shows, their names with their role.
414 93 void QuickContactGraph::collectKinshipLevels(qint64 center) {
415
1/1
✓ Branch 2 → 3 taken 93 times.
93 const QSet<qint64> otherwise = RelationKinship::otherwiseRelated(m_input, center);
416
1/1
✓ Branch 3 → 4 taken 93 times.
93 const QHash<qint64, int> all = RelationKinship::degrees(m_input, center);
417 93 m_kinship.clear();
418
2/2
✓ Branch 20 → 6 taken 596 times.
✓ Branch 20 → 21 taken 93 times.
689 for (auto it = all.cbegin(); it != all.cend(); ++it)
419
6/6
✓ Branch 7 → 8 taken 503 times.
✓ Branch 7 → 12 taken 93 times.
✓ Branch 10 → 11 taken 463 times.
✓ Branch 10 → 12 taken 40 times.
✓ Branch 13 → 14 taken 463 times.
✓ Branch 13 → 17 taken 133 times.
596 if (it.key() != center && !otherwise.contains(it.key()))
420
1/1
✓ Branch 16 → 17 taken 463 times.
463 m_kinship.insert(it.key(), it.value());
421 93 QMap<int, QVariantList> people;
422 93 QMap<int, int> counts;
423
2/2
✓ Branch 51 → 22 taken 725 times.
✓ Branch 51 → 52 taken 93 times.
818 for (int i = 0; i < m_graph.nodes.size(); ++i) {
424 725 const RelationGraph::Node &node = m_graph.nodes.at(i);
425 725 const int degree = m_kinship.value(node.cardId, -1);
426
6/8
✓ Branch 24 → 25 taken 632 times.
✓ Branch 24 → 28 taken 93 times.
✓ Branch 25 → 26 taken 632 times.
✗ Branch 25 → 28 not taken.
✓ Branch 26 → 27 taken 632 times.
✗ Branch 26 → 28 not taken.
✓ Branch 27 → 28 taken 233 times.
✓ Branch 27 → 29 taken 399 times.
725 if (node.center || node.family || node.moreCount > 0 || degree < 1)
427 326 continue;
428
1/1
✓ Branch 29 → 30 taken 399 times.
399 counts[degree] += 1;
429
3/3
✓ Branch 30 → 31 taken 399 times.
✓ Branch 32 → 33 taken 397 times.
✓ Branch 32 → 48 taken 2 times.
399 if (people[degree].size() < 4) {
430
1/1
✓ Branch 33 → 34 taken 397 times.
397 const QString role = roleOf(i);
431 //: A relative in the graph's family filter: "Marta Silva (Sister)".
432
6/10
✓ Branch 34 → 35 taken 397 times.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 397 times.
✓ Branch 38 → 39 taken 397 times.
✓ Branch 39 → 40 taken 397 times.
✓ Branch 41 → 42 taken 397 times.
✓ Branch 44 → 45 taken 397 times.
✗ Branch 44 → 46 not taken.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
397 people[degree].append(role.isEmpty() ? node.name : tr("%1 (%2)").arg(node.name, role));
433 397 }
434 }
435
1/1
✓ Branch 52 → 53 taken 93 times.
93 m_kinshipLevels.clear();
436
4/4
✓ Branch 53 → 54 taken 93 times.
✓ Branch 89 → 90 taken 243 times.
✓ Branch 91 → 55 taken 150 times.
✓ Branch 91 → 92 taken 93 times.
243 for (auto it = counts.cbegin(); it != counts.cend(); ++it)
437
1/1
✓ Branch 74 → 75 taken 150 times.
450 m_kinshipLevels.append(QVariantMap{{QStringLiteral("degree"), it.key()},
438 300 {QStringLiteral("count"), it.value()},
439
3/5
✓ Branch 70 → 71 taken 150 times.
✓ Branch 78 → 79 taken 450 times.
✓ Branch 78 → 80 taken 150 times.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
900 {QStringLiteral("people"), people.value(it.key())}});
440 693 }
441
442 2 void QuickContactGraph::setDepth(int depth) {
443 2 depth = qBound(1, depth, 2);
444
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (depth == m_depth)
445 ✗ return;
446 2 m_depth = depth;
447 2 rebuild();
448 }
449
450 111 void QuickContactGraph::rebuild() {
451
1/1
✓ Branch 2 → 3 taken 111 times.
111 ensureInput();
452 111 QElapsedTimer timer;
453 111 timer.start();
454 111 const qint64 center = cardIdOf(m_centerKey);
455
1/1
✓ Branch 5 → 6 taken 111 times.
111 m_groups.clear();
456 // Where everyone stands now: the next projection lets them glide from there.
457 111 m_transitionFrom.clear();
458
2/2
✓ Branch 7 → 8 taken 80 times.
✓ Branch 7 → 57 taken 31 times.
111 if (!m_compact)
459
2/2
✓ Branch 55 → 11 taken 522 times.
✓ Branch 55 → 56 taken 80 times.
602 for (const QVariant &value : std::as_const(m_nodes)) {
460
1/1
✓ Branch 12 → 13 taken 522 times.
522 const QVariantMap node = value.toMap();
461 // World → canvas with the current view (S105.4.1): the glide starts where they are seen.
462
4/4
✓ Branch 17 → 18 taken 522 times.
✓ Branch 18 → 19 taken 522 times.
✓ Branch 23 → 24 taken 522 times.
✓ Branch 24 → 25 taken 522 times.
1566 const QPointF world(node.value(QStringLiteral("x")).toDouble(), node.value(QStringLiteral("y")).toDouble());
463
3/3
✓ Branch 44 → 45 taken 522 times.
✓ Branch 45 → 46 taken 522 times.
✓ Branch 46 → 47 taken 522 times.
1044 m_transitionFrom.insert(node.value(QStringLiteral("key")).toString(),
464 522 world * viewScale() + QPointF(viewX(), viewY()));
465 522 }
466 111 m_transitionPending = !m_transitionFrom.isEmpty();
467
2/2
✓ Branch 58 → 59 taken 8 times.
✓ Branch 58 → 87 taken 103 times.
111 if (m_overview) {
468
2/2
✓ Branch 59 → 60 taken 8 times.
✓ Branch 60 → 61 taken 8 times.
8 m_graph = RelationGraph::overview(m_input, layers());
469 8 RelationGraphLayout::Options options;
470
3/6
✓ Branch 64 → 65 taken 8 times.
✗ Branch 64 → 68 not taken.
✓ Branch 66 → 67 taken 8 times.
✗ Branch 66 → 68 not taken.
✓ Branch 69 → 70 taken 8 times.
✗ Branch 69 → 73 not taken.
8 if (m_size.width() > 0 && m_size.height() > 0)
471 8 options.aspect = m_size.width() / m_size.height(); // the clusters fill the canvas's shape
472
1/1
✓ Branch 74 → 75 taken 8 times.
8 const RelationGraphLayout::Layout layout = RelationGraphLayout::overviewLayout(m_graph, m_overviewPositions, {}, options);
473 8 m_positions = layout.positions;
474 8 m_routes = layout.routes;
475
2/2
✓ Branch 84 → 79 taken 145 times.
✓ Branch 84 → 85 taken 8 times.
153 for (int i = 0; i < m_graph.nodes.size(); ++i)
476
2/2
✓ Branch 79 → 80 taken 145 times.
✓ Branch 81 → 82 taken 145 times.
145 m_overviewPositions.insert(m_graph.nodes.at(i).key, m_positions.value(i));
477
2/2
✓ Branch 87 → 88 taken 10 times.
✓ Branch 87 → 92 taken 93 times.
111 } else if (center <= 0) {
478 10 m_graph = {};
479
1/1
✓ Branch 90 → 91 taken 10 times.
10 m_positions.clear();
480
1/1
✓ Branch 91 → 128 taken 10 times.
10 m_routes.clear();
481 } else {
482 // The unfiltered graph names the filter's steps; the family filter then leaves out the farther relatives.
483 106 const auto build = [&](const RelationGraph::Input &input) {
484 106 return m_familyTree ? RelationGraph::familyTree(input, center) // F103-32
485
7/9
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 4 taken 101 times.
✓ Branch 3 → 7 taken 5 times.
✓ Branch 5 → 6 taken 101 times.
✓ Branch 6 → 7 taken 101 times.
✓ Branch 8 → 9 taken 101 times.
✓ Branch 8 → 10 taken 5 times.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
212 : RelationGraph::ego(input, center, m_depth, layers());
486 93 };
487
1/1
✓ Branch 92 → 93 taken 93 times.
93 m_graph = build(m_input);
488
1/1
✓ Branch 95 → 96 taken 93 times.
93 collectKinshipLevels(center);
489
4/4
✓ Branch 96 → 97 taken 37 times.
✓ Branch 96 → 109 taken 56 times.
✓ Branch 98 → 99 taken 33 times.
✓ Branch 98 → 109 taken 4 times.
126 if (m_kinshipLimit > 0 && !m_kinshipLevels.isEmpty() &&
490
18/28
✓ Branch 99 → 100 taken 33 times.
✓ Branch 100 → 101 taken 33 times.
✓ Branch 105 → 106 taken 33 times.
✓ Branch 106 → 107 taken 33 times.
✓ Branch 107 → 108 taken 13 times.
✓ Branch 107 → 109 taken 20 times.
✓ Branch 110 → 111 taken 33 times.
✓ Branch 110 → 112 taken 60 times.
✓ Branch 112 → 113 taken 33 times.
✓ Branch 112 → 114 taken 60 times.
✓ Branch 114 → 115 taken 33 times.
✓ Branch 114 → 116 taken 60 times.
✓ Branch 116 → 117 taken 33 times.
✓ Branch 116 → 118 taken 60 times.
✓ Branch 118 → 119 taken 33 times.
✓ Branch 118 → 120 taken 60 times.
✓ Branch 120 → 121 taken 13 times.
✓ Branch 120 → 127 taken 80 times.
✗ Branch 266 → 267 not taken.
✗ Branch 266 → 268 not taken.
✗ Branch 270 → 271 not taken.
✗ Branch 270 → 272 not taken.
✗ Branch 274 → 275 not taken.
✗ Branch 274 → 276 not taken.
✗ Branch 278 → 279 not taken.
✗ Branch 278 → 280 not taken.
✗ Branch 282 → 283 not taken.
✗ Branch 282 → 284 not taken.
159 m_kinshipLevels.last().toMap().value(QStringLiteral("degree")).toInt() > m_kinshipLimit)
491
2/2
✓ Branch 121 → 122 taken 13 times.
✓ Branch 122 → 123 taken 13 times.
13 m_graph = build(RelationKinship::withinDegree(m_input, center, m_kinshipLimit));
492 }
493 111 m_omittedPeople = 0;
494
4/4
✓ Branch 128 → 129 taken 103 times.
✓ Branch 128 → 130 taken 8 times.
✓ Branch 129 → 130 taken 10 times.
✓ Branch 129 → 133 taken 93 times.
111 if (m_overview || center <= 0) {
495 18 m_kinship.clear();
496
1/1
✓ Branch 131 → 132 taken 18 times.
18 m_kinshipLevels.clear();
497
2/2
✓ Branch 133 → 134 taken 31 times.
✓ Branch 133 → 177 taken 62 times.
93 } else if (m_compact) { // F103-52: the card's gist
498
1/1
✓ Branch 134 → 135 taken 31 times.
31 m_graph = RelationGraphStar::gist(m_graph, m_kinship, RelationGraphStar::MaxPeople, &m_omittedPeople);
499
2/2
✓ Branch 169 → 140 taken 59 times.
✓ Branch 169 → 170 taken 31 times.
90 for (const QVariant &level : std::as_const(m_kinshipLevels))
500
5/5
✓ Branch 141 → 142 taken 59 times.
✓ Branch 146 → 147 taken 59 times.
✓ Branch 147 → 148 taken 59 times.
✓ Branch 153 → 154 taken 8 times.
✓ Branch 153 → 167 taken 51 times.
118 if (level.toMap().value(QStringLiteral("degree")).toInt() > m_kinshipLimit)
501
3/3
✓ Branch 154 → 155 taken 8 times.
✓ Branch 159 → 160 taken 8 times.
✓ Branch 160 → 161 taken 8 times.
16 m_omittedPeople += level.toMap().value(QStringLiteral("count")).toInt();
502
1/1
✓ Branch 172 → 173 taken 31 times.
31 const RelationGraphLayout::Layout layout = RelationGraphStar::layout(m_graph, m_size.width() / m_size.height());
503 31 m_positions = layout.positions;
504 31 m_routes = layout.routes;
505
2/2
✓ Branch 177 → 178 taken 4 times.
✓ Branch 177 → 185 taken 58 times.
93 } else if (m_familyTree) {
506
1/1
✓ Branch 178 → 179 taken 4 times.
4 m_positions = RelationGraphLayout::familyTree(m_graph);
507
1/1
✓ Branch 181 → 182 taken 4 times.
4 m_routes = RelationGraphLayout::routes(m_graph, m_positions);
508 } else {
509
1/1
✓ Branch 185 → 186 taken 58 times.
58 const RelationGraphLayout::Layout layout = RelationGraphLayout::egoLayout(m_graph);
510 58 m_positions = layout.positions;
511 58 m_routes = layout.routes;
512 58 m_groups = layout.groups;
513 58 }
514
9/9
✓ Branch 191 → 192 taken 111 times.
✓ Branch 195 → 196 taken 111 times.
✓ Branch 196 → 197 taken 111 times.
✓ Branch 198 → 199 taken 111 times.
✓ Branch 199 → 200 taken 111 times.
✓ Branch 201 → 202 taken 111 times.
✓ Branch 202 → 203 taken 111 times.
✓ Branch 206 → 193 taken 111 times.
✓ Branch 206 → 207 taken 111 times.
222 qCDebug(lcGraph) << "Ego graph:" << m_graph.nodes.size() << "nodes in" << timer.nsecsElapsed() / 1000 << "µs";
515
1/1
✓ Branch 207 → 208 taken 111 times.
111 emit graphChanged();
516
1/1
✓ Branch 208 → 209 taken 111 times.
111 fitTo(false);
517 111 }
518
519 225 void QuickContactGraph::setSize(double width, double height) {
520
4/4
✓ Branch 2 → 3 taken 216 times.
✓ Branch 2 → 4 taken 9 times.
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 5 taken 211 times.
225 if (width <= 0 || height <= 0)
521 14 return;
522 211 m_size = QSizeF(width, height);
523 211 fitTo(false);
524 }
525
526 2 void QuickContactGraph::fit() { fitTo(true); }
527
528 // `everything`: the explicit *Fit* (0, double click, View ▾) shows every
529 // person, down to m_minScale. Otherwise (a new centre, a resize) the graph
530 // is shown at a readable size: it fits when it can; a larger one stays at
531 // m_readableScale around the person in the middle (in the overview: me,
532 // else the middle of everyone) and the rest is reached by dragging and
533 // zooming — names are never shrunk to fit.
534 324 void QuickContactGraph::fitTo(bool everything) {
535 324 fitValues(everything, &m_scale, &m_offset);
536 324 project();
537 324 }
538
539 324 void QuickContactGraph::fitValues(bool everything, double *scale, QPointF *offset) const {
540
2/2
✓ Branch 3 → 4 taken 49 times.
✓ Branch 3 → 6 taken 275 times.
324 if (m_positions.isEmpty()) {
541 49 *offset = {};
542 49 *scale = 1.0;
543 49 return;
544 }
545 // Min/max by hand: QRectF::united() ignores empty (point) rectangles.
546 275 double left = m_positions.first().x(), right = left, top = m_positions.first().y(), bottom = top;
547
2/2
✓ Branch 24 → 13 taken 2203 times.
✓ Branch 24 → 25 taken 275 times.
2478 for (const QPointF &point : std::as_const(m_positions)) {
548 2203 left = qMin(left, point.x());
549 2203 right = qMax(right, point.x());
550 2203 top = qMin(top, point.y());
551 2203 bottom = qMax(bottom, point.y());
552 }
553 // F103-34: family areas (64 px around their members, not scaled) and their names stay in view.
554
2/2
✓ Branch 26 → 27 taken 224 times.
✓ Branch 26 → 28 taken 51 times.
275 const double margin = m_graph.areas.isEmpty() ? 60 : 140;
555 275 const QRectF bounds = QRectF(QPointF(left, top), QPointF(right, bottom)).adjusted(-margin, -margin, margin, margin);
556 275 const double whole = qMin(m_size.width() / bounds.width(), m_size.height() / bounds.height());
557
2/2
✓ Branch 38 → 39 taken 2 times.
✓ Branch 38 → 40 taken 273 times.
275 const double fitted = qBound(everything ? m_minScale : m_readableScale, whole, 1.4);
558 275 QPointF focus = bounds.center();
559
2/2
✓ Branch 43 → 44 taken 48 times.
✓ Branch 43 → 86 taken 227 times.
275 if (fitted > whole) { // it does not fit: start from the person who matters
560 48 int anchor = -1;
561
6/6
✓ Branch 50 → 51 taken 213 times.
✓ Branch 50 → 53 taken 4 times.
✓ Branch 51 → 52 taken 169 times.
✓ Branch 51 → 53 taken 44 times.
✓ Branch 54 → 45 taken 169 times.
✓ Branch 54 → 55 taken 48 times.
217 for (int i = 0; i < m_graph.nodes.size() && anchor < 0; ++i)
562
2/2
✓ Branch 46 → 47 taken 44 times.
✓ Branch 46 → 48 taken 125 times.
169 if (m_graph.nodes.at(i).center)
563 44 anchor = i;
564
6/6
✓ Branch 61 → 62 taken 112 times.
✓ Branch 61 → 64 taken 1 time.
✓ Branch 62 → 63 taken 65 times.
✓ Branch 62 → 64 taken 47 times.
✓ Branch 65 → 56 taken 65 times.
✓ Branch 65 → 66 taken 48 times.
113 for (int i = 0; i < m_graph.nodes.size() && anchor < 0; ++i)
565
2/2
✓ Branch 57 → 58 taken 3 times.
✓ Branch 57 → 59 taken 62 times.
65 if (m_graph.nodes.at(i).me)
566 3 anchor = i;
567
2/2
✓ Branch 66 → 67 taken 47 times.
✓ Branch 66 → 68 taken 1 time.
48 if (anchor >= 0)
568
1/1
✓ Branch 67 → 68 taken 47 times.
47 focus = m_positions.value(anchor);
569 // The view stays inside the graph: no empty half beside a person at its edge.
570 48 const double halfW = m_size.width() / 2 / fitted, halfH = m_size.height() / 2 / fitted;
571
2/2
✓ Branch 71 → 72 taken 47 times.
✓ Branch 71 → 78 taken 1 time.
48 if (bounds.width() > 2 * halfW)
572 47 focus.setX(qBound(bounds.left() + halfW, focus.x(), bounds.right() - halfW));
573
2/2
✓ Branch 79 → 80 taken 23 times.
✓ Branch 79 → 86 taken 25 times.
48 if (bounds.height() > 2 * halfH)
574 23 focus.setY(qBound(bounds.top() + halfH, focus.y(), bounds.bottom() - halfH));
575 }
576 275 *scale = fitted;
577 275 *offset = -focus * fitted;
578
12/12
✓ Branch 88 → 89 taken 275 times.
✓ Branch 92 → 93 taken 275 times.
✓ Branch 93 → 94 taken 275 times.
✓ Branch 94 → 95 taken 2 times.
✓ Branch 94 → 96 taken 273 times.
✓ Branch 97 → 98 taken 275 times.
✓ Branch 98 → 99 taken 275 times.
✓ Branch 99 → 100 taken 275 times.
✓ Branch 100 → 101 taken 275 times.
✓ Branch 101 → 102 taken 275 times.
✓ Branch 105 → 90 taken 275 times.
✓ Branch 105 → 106 taken 275 times.
550 qCDebug(lcGraph) << "Fit" << (everything ? "everything" : "readable") << "scale" << fitted << "whole" << whole;
579 }
580
581 ✗ QVariantMap QuickContactGraph::view() const {
582 ✗ return {{QStringLiteral("scale"), m_scale}, {QStringLiteral("offsetX"), m_offset.x()},
583 ✗ {QStringLiteral("offsetY"), m_offset.y()}};
584 ✗ }
585
586 ✗ QVariantMap QuickContactGraph::fitTarget(bool everything) const {
587 ✗ double scale = m_scale;
588 ✗ QPointF offset = m_offset;
589 ✗ fitValues(everything, &scale, &offset);
590 ✗ return {{QStringLiteral("scale"), scale}, {QStringLiteral("offsetX"), offset.x()},
591 ✗ {QStringLiteral("offsetY"), offset.y()}};
592 ✗ }
593
594 ✗ void QuickContactGraph::setView(double scale, double offsetX, double offsetY) {
595 ✗ if (!std::isfinite(scale) || !std::isfinite(offsetX) || !std::isfinite(offsetY))
596 ✗ return;
597 ✗ m_scale = qBound(m_minScale, scale, MaxScale);
598 ✗ m_offset = QPointF(offsetX, offsetY);
599 ✗ emit viewChanged();
600 }
601
602 1933 double QuickContactGraph::viewX() const {
603 1933 const double centre = m_size.width() / 2;
604 1933 return centre + m_offset.x() - (centre + m_projectedOffset.x()) * viewScale();
605 }
606
607 1930 double QuickContactGraph::viewY() const {
608 1930 const double centre = m_size.height() / 2;
609 1930 return centre + m_offset.y() - (centre + m_projectedOffset.y()) * viewScale();
610 }
611
612 // The room for a person's name (`labelWidth`, canvas px): it is centred
613 // under the avatar (on up to two lines), so it may be as wide as the
614 // distance to the nearest drawn person whose name could meet it (closer
615 // than Band vertically) less `spacing`, at most 132 px. The family tree's
616 // cards keep 52 px between them, room for the rings and year of a couple; zoomed out
617 // in Everyone the band and the narrowest name are 40 px (smaller avatars
618 // and names). Below 56 px there is no room for a
619 // name at all: 0, the name shows only when the person is pointed at (zoomed
620 // far out the people stay, their names step back). A sweep over the people
621 // sorted by y keeps this linear for wide rows.
622 324 void QuickContactGraph::insertLabelRoom(QVariantList *nodes, double spacing, double Band, double Narrowest) {
623 324 constexpr double Widest = 132;
624 324 QList<int> order;
625
2/2
✓ Branch 20 → 3 taken 2203 times.
✓ Branch 20 → 21 taken 324 times.
2527 for (int i = 0; i < nodes->size(); ++i) {
626
1/1
✓ Branch 4 → 5 taken 2203 times.
2203 const QVariantMap node = nodes->at(i).toMap();
627
4/4
✓ Branch 9 → 10 taken 2203 times.
✓ Branch 10 → 11 taken 2203 times.
✓ Branch 15 → 16 taken 2194 times.
✓ Branch 15 → 17 taken 9 times.
4406 if (!node.value(QStringLiteral("family")).toBool())
628
1/1
✓ Branch 16 → 17 taken 2194 times.
2194 order.append(i);
629 2203 }
630
4/4
✓ Branch 3 → 4 taken 37099 times.
✓ Branch 6 → 7 taken 37099 times.
✓ Branch 7 → 8 taken 37099 times.
✓ Branch 8 → 9 taken 37099 times.
37099 const auto at = [&](int i, const char *key) { return nodes->at(i).toMap().value(QLatin1String(key)).toDouble(); };
631
3/3
✓ Branch 21 → 22 taken 324 times.
✓ Branch 22 → 23 taken 324 times.
✓ Branch 23 → 24 taken 324 times.
6866 std::sort(order.begin(), order.end(), [&](int a, int b) { return at(a, "y") < at(b, "y"); });
632
2/2
✓ Branch 100 → 25 taken 2194 times.
✓ Branch 100 → 101 taken 324 times.
2518 for (int k = 0; k < order.size(); ++k) {
633 2194 const int i = order.at(k);
634
2/2
✓ Branch 26 → 27 taken 2194 times.
✓ Branch 27 → 28 taken 2194 times.
2194 const double x = at(i, "x"), y = at(i, "y");
635 2194 double nearest = Widest + 8;
636
7/7
✓ Branch 34 → 35 taken 5676 times.
✓ Branch 34 → 39 taken 674 times.
✓ Branch 36 → 37 taken 5676 times.
✓ Branch 37 → 38 taken 4156 times.
✓ Branch 37 → 39 taken 1520 times.
✓ Branch 40 → 29 taken 4156 times.
✓ Branch 40 → 41 taken 2194 times.
6350 for (int j = k - 1; j >= 0 && y - at(order.at(j), "y") < Band; --j)
637
1/1
✓ Branch 30 → 31 taken 4156 times.
4156 nearest = std::min(nearest, std::abs(at(order.at(j), "x") - x));
638
7/7
✓ Branch 48 → 49 taken 5639 times.
✓ Branch 48 → 53 taken 711 times.
✓ Branch 50 → 51 taken 5639 times.
✓ Branch 51 → 52 taken 4156 times.
✓ Branch 51 → 53 taken 1483 times.
✓ Branch 54 → 42 taken 4156 times.
✓ Branch 54 → 55 taken 2194 times.
6350 for (int j = k + 1; j < order.size() && at(order.at(j), "y") - y < Band; ++j)
639
1/1
✓ Branch 43 → 44 taken 4156 times.
4156 nearest = std::min(nearest, std::abs(at(order.at(j), "x") - x));
640
1/1
✓ Branch 56 → 57 taken 2194 times.
2194 QVariantMap node = nodes->at(i).toMap();
641 2194 const double room = std::min(nearest - spacing, Widest);
642
3/3
✓ Branch 58 → 59 taken 625 times.
✓ Branch 58 → 60 taken 1569 times.
✓ Branch 65 → 66 taken 2194 times.
4388 node.insert(QStringLiteral("labelWidth"), room < Narrowest ? 0.0 : room);
643 // S105.4.3: the same rule at another zoom (ContactGraphNode.nameRoom):
644 // the distance scales with the zoom, the spacing and the limits do not.
645
1/1
✓ Branch 73 → 74 taken 2194 times.
4388 node.insert(QStringLiteral("nearest"), nearest);
646
1/1
✓ Branch 81 → 82 taken 2194 times.
4388 node.insert(QStringLiteral("labelSpacing"), spacing);
647
1/1
✓ Branch 89 → 90 taken 2194 times.
4388 node.insert(QStringLiteral("labelNarrowest"), Narrowest);
648
1/1
✓ Branch 94 → 95 taken 2194 times.
2194 (*nodes)[i] = node;
649 2194 }
650 324 }
651
652 // Pans so that `key` stands in the middle when it is outside the canvas (or within 40 px of its rim).
653 1 void QuickContactGraph::reveal(const QString &key) {
654
1/2
✓ Branch 45 → 3 taken 3 times.
✗ Branch 45 → 46 not taken.
3 for (int i = 0; i < m_graph.nodes.size(); ++i) {
655
2/2
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 1 time.
3 if (m_graph.nodes.at(i).key != key)
656 2 continue;
657
1/1
✓ Branch 7 → 8 taken 1 time.
1 const QPointF at = QPointF(m_size.width() / 2, m_size.height() / 2) + m_offset + m_positions.value(i) * m_scale;
658
5/10
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 25 not taken.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 25 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 25 not taken.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 28 not taken.
1 if (at.x() > 40 && at.y() > 40 && at.x() < m_size.width() - 40 && at.y() < m_size.height() - 40)
659 1 return;
660 ✗ m_offset = -m_positions.value(i) * m_scale;
661 ✗ qCDebug(lcGraph) << "Revealed a selected person in the overview";
662 ✗ emit viewChanged();
663 ✗ return;
664 }
665 }
666
667 3 QStringList QuickContactGraph::about(int index) const {
668
5/6
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 5 taken 1 time.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 2 times.
3 if (index < 0 || index >= m_graph.nodes.size())
669 1 return {};
670 2 const RelationGraph::Node &node = m_graph.nodes.at(index);
671 2 QStringList first, rest;
672
2/2
✓ Branch 96 → 12 taken 2 times.
✓ Branch 96 → 97 taken 2 times.
4 for (const RelationGraph::Edge &edge : m_graph.edges) {
673
3/4
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 16 taken 1 time.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 1 time.
2 if (edge.from != index && edge.to != index)
674 ✗ continue;
675
2/2
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 1 time.
2 const int otherIndex = edge.from == index ? edge.to : edge.from;
676 2 const RelationGraph::Node &other = m_graph.nodes.at(otherIndex);
677
2/4
✓ Branch 20 → 21 taken 2 times.
✗ Branch 20 → 22 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 2 times.
2 if (other.family || other.moreCount > 0)
678 ✗ continue;
679 2 QString line;
680
2/6
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 29 taken 2 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✓ Branch 31 → 32 taken 2 times.
✗ Branch 31 → 47 not taken.
2 if (edge.kind == QLatin1String("relation") || edge.kind == QLatin1String("inferred")) {
681 // `type` is what `to` is to `from` (the card that holds the line).
682
1/1
✓ Branch 32 → 33 taken 2 times.
2 const QString word = RelationVocabulary::holderLabel(edge.type, edge.from == index, node.gender);
683
1/3
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 39 taken 2 times.
✗ Branch 38 → 41 not taken.
4 line = word.isEmpty() ? RelationVocabulary::sentence(m_graph.nodes.at(edge.to).name, edge.type,
684 ✗ m_graph.nodes.at(edge.from).name,
685 ✗ m_graph.nodes.at(edge.to).gender, edge.label)
686 //: Hover card in the graph: "Daughter of" + "Ana Silva".
687
3/6
✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 43 → 44 taken 2 times.
✗ Branch 43 → 45 not taken.
✗ Branch 114 → 115 not taken.
✗ Branch 114 → 116 not taken.
4 : tr("%1 %2").arg(word, other.name);
688
0/2
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 64 not taken.
2 } else if (edge.kind == QLatin1String("via")) {
689 //: Hover card in the graph: this person was introduced by someone / introduced someone.
690 ✗ line = edge.from == index ? tr("Introduced by %1").arg(other.name) : tr("Introduced %1").arg(other.name);
691 ✗ } else if (edge.kind == QLatin1String("suggestion")) {
692 //: Hover card in the graph (S103.5.8): often in mails or appointments together, not related yet.
693 ✗ line = tr("Often together with %1").arg(other.name);
694 }
695
4/8
✓ Branch 75 → 76 taken 2 times.
✗ Branch 75 → 80 not taken.
✓ Branch 77 → 78 taken 2 times.
✗ Branch 77 → 80 not taken.
✗ Branch 79 → 80 not taken.
✓ Branch 79 → 81 taken 2 times.
✗ Branch 82 → 83 not taken.
✓ Branch 82 → 84 taken 2 times.
2 if (line.isEmpty() || first.contains(line) || rest.contains(line))
696 ✗ continue;
697
3/3
✓ Branch 84 → 85 taken 1 time.
✓ Branch 84 → 86 taken 1 time.
✓ Branch 87 → 88 taken 2 times.
2 (other.center ? first : rest).append(line);
698 2 }
699
1/1
✓ Branch 97 → 98 taken 2 times.
2 QStringList lines = first + rest;
700
1/2
✗ Branch 99 → 100 not taken.
✓ Branch 99 → 108 taken 2 times.
2 if (lines.size() > 5) {
701 ✗ const int more = int(lines.size()) - 4;
702 ✗ lines = lines.mid(0, 4);
703 ✗ lines.append(tr("+%n more", "", more));
704 }
705 2 return lines;
706 2 }
707
708 // S105.4.1: a pan or a zoom changes the view transform only — no
709 // re-projection, so nothing is recomputed and nothing jumps when it ends.
710 15 void QuickContactGraph::panBy(double dx, double dy) {
711
3/6
✓ Branch 3 → 4 taken 15 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 15 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 15 times.
15 if (!std::isfinite(dx) || !std::isfinite(dy))
712 ✗ return;
713 15 m_offset += QPointF(dx, dy);
714 15 emit viewChanged();
715 }
716
717 23 void QuickContactGraph::zoomAt(double steps, double x, double y) {
718 23 zoomTo(m_scale * std::pow(1.25, steps), x, y);
719 23 }
720
721 273 void QuickContactGraph::zoomTo(double scale, double x, double y) {
722
4/8
✓ Branch 3 → 4 taken 273 times.
✗ Branch 3 → 8 not taken.
✓ Branch 5 → 6 taken 273 times.
✗ Branch 5 → 8 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 273 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 273 times.
273 if (!std::isfinite(scale) || !std::isfinite(x) || !std::isfinite(y))
723 ✗ return;
724 273 const double next = qBound(m_minScale, scale, MaxScale);
725 // Keep the layout point under (x, y) in place.
726 273 const QPointF anchor(x - m_size.width() / 2, y - m_size.height() / 2);
727 273 m_offset = anchor - (anchor - m_offset) * (next / m_scale);
728 273 m_scale = next;
729
1/1
✓ Branch 19 → 20 taken 273 times.
273 emit viewChanged();
730 }
731
732 // The heading of a side column of the ego view.
733 136 QString QuickContactGraph::groupLabel(const QString &sector) const {
734
2/2
✓ Branch 4 → 5 taken 54 times.
✓ Branch 4 → 6 taken 82 times.
136 if (sector == QLatin1String("friends"))
735 //: Ego graph: heading of the column of friends beside the family.
736 54 return tr("Friends");
737
2/2
✓ Branch 8 → 9 taken 40 times.
✓ Branch 8 → 10 taken 42 times.
82 if (sector == QLatin1String("work"))
738 //: Ego graph: heading of the column of work relationships beside the family.
739 40 return tr("Work");
740
1/2
✓ Branch 12 → 13 taken 42 times.
✗ Branch 12 → 14 not taken.
42 if (sector == QLatin1String("introductions"))
741 //: Ego graph: heading of the column of people who introduced the centre or were introduced by them.
742 42 return tr("Introductions");
743 //: Ego graph: heading of the column of other relationships (neighbours, …) beside the family.
744 ✗ return tr("Other");
745 }
746
747 // What a row of the family tree is to the centre, by generation (older positive).
748 14 QString QuickContactGraph::generationLabel(int generation) const {
749
4/8
✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 14 taken 4 times.
✓ Branch 2 → 15 taken 3 times.
✗ Branch 2 → 16 not taken.
✓ Branch 2 → 17 taken 3 times.
✗ Branch 2 → 18 not taken.
✗ Branch 2 → 19 not taken.
✗ Branch 2 → 20 not taken.
14 switch (generation) {
750 4 case 0: {
751 4 const int centre = m_graph.indexOf(cardIdOf(m_centerKey));
752 //: Family tree: the row of the person in the middle, e.g. "Ana Silva's generation".
753
4/8
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 4 times.
✓ Branch 9 → 11 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
✗ Branch 11 → 13 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
4 return centre >= 0 ? tr("%1's generation").arg(m_graph.nodes.at(centre).name) : QString();
754 }
755 //: Family tree: a row of the tree, seen from the person in the middle.
756 4 case 1: return tr("Parents");
757 //: Family tree: a row of the tree, seen from the person in the middle.
758 3 case 2: return tr("Grandparents");
759 //: Family tree: a row of the tree, seen from the person in the middle.
760 ✗ case 3: return tr("Great-grandparents");
761 //: Family tree: a row of the tree, seen from the person in the middle.
762 3 case -1: return tr("Children");
763 //: Family tree: a row of the tree, seen from the person in the middle.
764 ✗ case -2: return tr("Grandchildren");
765 //: Family tree: a row of the tree, seen from the person in the middle.
766 ✗ case -3: return tr("Great-grandchildren");
767 ✗ default:
768 //: Family tree: a row further up than great-grandparents.
769 ✗ return generation > 0 ? tr("Ancestors")
770 //: Family tree: a row further down than great-grandchildren.
771 ✗ : tr("Descendants");
772 }
773 }
774
775 324 void QuickContactGraph::project() {
776 324 m_projectedScale = m_scale;
777 324 m_projectedOffset = m_offset;
778 324 const QPointF centre(m_size.width() / 2, m_size.height() / 2);
779 34087 const auto toCanvas = [&](QPointF p) { return centre + m_offset + p * m_scale; };
780 8601 const auto canvas = [&](int index) { return toCanvas(m_positions.value(index)); };
781 6398 const auto coordinates = [&](const QList<QPointF> &points) {
782 6398 QVariantList out;
783
2/2
✓ Branch 16 → 4 taken 12668 times.
✓ Branch 16 → 17 taken 6398 times.
19066 for (const QPointF &p : points) {
784 12668 const QPointF at = toCanvas(p);
785
2/2
✓ Branch 8 → 9 taken 12668 times.
✓ Branch 11 → 12 taken 12668 times.
12668 out << at.x() << at.y();
786 }
787 6398 return out;
788 ✗ };
789 324 m_transition = m_transitionPending;
790 324 m_transitionPending = false;
791
1/1
✓ Branch 5 → 6 taken 324 times.
324 m_nodes.clear();
792
2/2
✓ Branch 180 → 7 taken 2203 times.
✓ Branch 180 → 181 taken 324 times.
2527 for (int i = 0; i < m_graph.nodes.size(); ++i) {
793 2203 const RelationGraph::Node &node = m_graph.nodes.at(i);
794 2203 QVariantMap map = m_avatars.value(node.cardId);
795
1/1
✓ Branch 9 → 10 taken 2203 times.
2203 const QPointF point = canvas(i);
796
3/5
✓ Branch 95 → 96 taken 2203 times.
✓ Branch 98 → 99 taken 37451 times.
✓ Branch 98 → 100 taken 2203 times.
✗ Branch 683 → 684 not taken.
✗ Branch 683 → 685 not taken.
44060 map.insert({{QStringLiteral("index"), i},
797 4406 {QStringLiteral("key"), node.key},
798
1/3
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 2203 times.
✗ Branch 22 → 24 not taken.
4406 {QStringLiteral("name"), node.moreCount > 0 ? tr("+%n more", "", node.moreCount) : node.name},
799 4406 {QStringLiteral("x"), point.x()},
800 4406 {QStringLiteral("y"), point.y()},
801 4406 {QStringLiteral("center"), node.center},
802 4406 {QStringLiteral("family"), node.family},
803 4406 {QStringLiteral("inScope"), node.inScope},
804 4406 {QStringLiteral("me"), node.me},
805 4406 {QStringLiteral("sector"), node.sector},
806 4406 {QStringLiteral("generation"), node.generation},
807 4406 {QStringLiteral("gender"), node.gender},
808
3/3
✓ Branch 69 → 70 taken 1246 times.
✓ Branch 70 → 71 taken 1035 times.
✓ Branch 70 → 74 taken 211 times.
5652 {QStringLiteral("birthYear"), node.birthday.size() >= 4 && !node.birthday.startsWith(QLatin1String("--"))
809
4/6
✓ Branch 67 → 68 taken 1246 times.
✓ Branch 67 → 74 taken 957 times.
✓ Branch 71 → 72 taken 1035 times.
✓ Branch 72 → 73 taken 1035 times.
✗ Branch 715 → 716 not taken.
✗ Branch 715 → 717 not taken.
5652 ? node.birthday.left(4).toInt() : 0},
810
2/2
✓ Branch 109 → 110 taken 1035 times.
✓ Branch 109 → 111 taken 1168 times.
4406 {QStringLiteral("moreCount"), node.moreCount},
811 4406 {QStringLiteral("degree"), node.degree},
812
1/1
✓ Branch 87 → 88 taken 2203 times.
4406 {QStringLiteral("role"), roleOf(i)},
813 4406 {QStringLiteral("kinship"), m_kinship.value(node.cardId, -1)}});
814
2/2
✓ Branch 138 → 139 taken 535 times.
✓ Branch 138 → 174 taken 1668 times.
2203 if (m_transition) {
815
2/2
✓ Branch 142 → 143 taken 380 times.
✓ Branch 142 → 164 taken 155 times.
535 if (const auto from = m_transitionFrom.constFind(node.key); from != m_transitionFrom.cend()) {
816
1/1
✓ Branch 149 → 150 taken 380 times.
760 map.insert(QStringLiteral("fromX"), from->x());
817
1/1
✓ Branch 159 → 160 taken 380 times.
760 map.insert(QStringLiteral("fromY"), from->y());
818 } else {
819
1/1
✓ Branch 168 → 169 taken 155 times.
310 map.insert(QStringLiteral("entering"), true);
820 }
821 }
822
1/1
✓ Branch 175 → 176 taken 2203 times.
2203 m_nodes.append(map);
823 2203 }
824 // Everyone zoomed out (semantic zoom, ContactGraphView.detail): smaller
825 // avatars and names need less room above and below, so names stay longer.
826
3/4
✓ Branch 181 → 182 taken 8 times.
✓ Branch 181 → 184 taken 316 times.
✗ Branch 182 → 183 not taken.
✓ Branch 182 → 184 taken 8 times.
324 const bool small = m_overview && m_scale < 0.6;
827
7/9
✗ Branch 185 → 186 not taken.
✓ Branch 185 → 187 taken 324 times.
✗ Branch 188 → 189 not taken.
✓ Branch 188 → 190 taken 324 times.
✓ Branch 191 → 192 taken 6 times.
✓ Branch 191 → 194 taken 318 times.
✓ Branch 192 → 193 taken 4 times.
✓ Branch 192 → 194 taken 2 times.
✓ Branch 195 → 196 taken 324 times.
324 insertLabelRoom(&m_nodes, m_familyTree && !m_overview ? 52 : 8, small ? 40 : 56, small ? 40 : 56);
828
1/1
✓ Branch 196 → 197 taken 324 times.
324 m_groupLabels.clear();
829
2/2
✓ Branch 250 → 200 taken 136 times.
✓ Branch 250 → 251 taken 324 times.
460 for (const RelationGraphLayout::Group &group : std::as_const(m_groups)) {
830 136 const QPointF at = toCanvas(group.at);
831
1/1
✓ Branch 230 → 231 taken 136 times.
408 m_groupLabels.append(QVariantMap{{QStringLiteral("x"), at.x()},
832 272 {QStringLiteral("y"), at.y()},
833
1/1
✓ Branch 215 → 216 taken 136 times.
272 {QStringLiteral("label"), groupLabel(group.sector)},
834 272 {QStringLiteral("category"), group.sector == QLatin1String("introductions")
835
6/10
✓ Branch 222 → 223 taken 42 times.
✓ Branch 222 → 226 taken 94 times.
✓ Branch 234 → 235 taken 544 times.
✓ Branch 234 → 236 taken 136 times.
✓ Branch 237 → 238 taken 42 times.
✓ Branch 237 → 239 taken 94 times.
✗ Branch 851 → 852 not taken.
✗ Branch 851 → 853 not taken.
✗ Branch 856 → 857 not taken.
✗ Branch 856 → 858 not taken.
994 ? QStringLiteral("other") : group.sector}});
836 }
837
1/1
✓ Branch 251 → 252 taken 324 times.
324 m_generationRows.clear();
838
5/6
✓ Branch 252 → 253 taken 6 times.
✓ Branch 252 → 308 taken 318 times.
✓ Branch 253 → 254 taken 4 times.
✓ Branch 253 → 308 taken 2 times.
✓ Branch 254 → 255 taken 4 times.
✗ Branch 254 → 308 not taken.
324 if (m_familyTree && !m_overview && !m_compact) {
839 4 QMap<int, double> rowY; // generation → layout y (older generations have smaller keys after the sign flip)
840
2/2
✓ Branch 265 → 256 taken 32 times.
✓ Branch 265 → 266 taken 4 times.
36 for (int i = 0; i < m_graph.nodes.size(); ++i)
841
1/2
✓ Branch 257 → 258 taken 32 times.
✗ Branch 257 → 263 not taken.
32 if (!m_graph.nodes.at(i).family)
842
2/2
✓ Branch 258 → 259 taken 32 times.
✓ Branch 261 → 262 taken 32 times.
32 rowY.insert(-m_graph.nodes.at(i).generation, m_positions.value(i).y());
843
4/4
✓ Branch 266 → 267 taken 4 times.
✓ Branch 303 → 304 taken 18 times.
✓ Branch 305 → 268 taken 14 times.
✓ Branch 305 → 306 taken 4 times.
18 for (auto it = rowY.cbegin(); it != rowY.cend(); ++it)
844
1/1
✓ Branch 289 → 290 taken 14 times.
42 m_generationRows.append(QVariantMap{{QStringLiteral("generation"), -it.key()},
845 28 {QStringLiteral("y"), toCanvas(QPointF(0, it.value())).y()},
846
3/5
✓ Branch 285 → 286 taken 14 times.
✓ Branch 293 → 294 taken 42 times.
✓ Branch 293 → 295 taken 14 times.
✗ Branch 909 → 910 not taken.
✗ Branch 909 → 911 not taken.
84 {QStringLiteral("label"), generationLabel(-it.key())}});
847 4 }
848
1/1
✓ Branch 308 → 309 taken 324 times.
324 m_edges.clear();
849
1/1
✓ Branch 309 → 310 taken 324 times.
324 m_drawnEdges.clear();
850 6398 const auto canvasPoints = [&](const QList<QPointF> &points) {
851 6398 QList<QPointF> out;
852
1/1
✓ Branch 3 → 4 taken 6398 times.
6398 out.reserve(points.size());
853
2/2
✓ Branch 11 → 6 taken 12668 times.
✓ Branch 11 → 12 taken 6398 times.
19066 for (const QPointF &p : points)
854
1/1
✓ Branch 8 → 9 taken 12668 times.
12668 out.append(toCanvas(p));
855 6398 return out;
856 ✗ };
857
2/2
✓ Branch 664 → 311 taken 3199 times.
✓ Branch 664 → 665 taken 324 times.
3523 for (int edgeIndex = 0; edgeIndex < m_graph.edges.size(); ++edgeIndex) {
858 3199 const RelationGraph::Edge &edge = m_graph.edges.at(edgeIndex);
859 3199 RelationGraphLayout::Route route = m_routes.value(edgeIndex, {QStringLiteral("line"), {}});
860
1/1
✓ Branch 318 → 319 taken 3199 times.
3199 const Stroke stroke = strokeOf(edge);
861 // A family-tree route runs from the older generation down, whichever card
862 // stores the line; the arrowhead sits at the route's end, so the route ends
863 // at the person the arrow points at.
864
1/1
✓ Branch 319 → 320 taken 3199 times.
3199 const int arrowTo = arrowTarget(edge);
865
6/6
✓ Branch 320 → 321 taken 1609 times.
✓ Branch 320 → 324 taken 1590 times.
✓ Branch 322 → 323 taken 1303 times.
✓ Branch 322 → 324 taken 306 times.
✓ Branch 325 → 326 taken 1303 times.
✓ Branch 325 → 338 taken 1896 times.
3199 if (arrowTo >= 0 && route.points.size() >= 2) {
866
1/1
✓ Branch 326 → 327 taken 1303 times.
1303 const QPointF tip = m_positions.value(arrowTo);
867
6/6
✓ Branch 327 → 328 taken 1303 times.
✓ Branch 329 → 330 taken 1303 times.
✓ Branch 330 → 331 taken 1303 times.
✓ Branch 332 → 333 taken 1303 times.
✓ Branch 333 → 334 taken 654 times.
✓ Branch 333 → 337 taken 649 times.
1303 if (QLineF(route.points.first(), tip).length() < QLineF(route.points.last(), tip).length())
868
3/3
✓ Branch 334 → 335 taken 654 times.
✓ Branch 335 → 336 taken 654 times.
✓ Branch 336 → 337 taken 654 times.
654 std::reverse(route.points.begin(), route.points.end());
869 }
870
1/1
✓ Branch 338 → 339 taken 3199 times.
3199 const QPointF a = canvas(edge.from);
871
1/1
✓ Branch 339 → 340 taken 3199 times.
3199 const QPointF b = canvas(edge.to);
872 // The drawn route and its bounding box [x0, y0, x1, y1] (the line canvas skips lines outside it).
873
2/2
✓ Branch 340 → 341 taken 3199 times.
✓ Branch 341 → 342 taken 3199 times.
3199 const QVariantList path = coordinates(RelationGraphLayout::flatten(route));
874 3199 double left = std::min(a.x(), b.x()), top = std::min(a.y(), b.y());
875 3199 double right = std::max(a.x(), b.x()), bottom = std::max(a.y(), b.y());
876
2/2
✓ Branch 366 → 356 taken 6678 times.
✓ Branch 366 → 367 taken 3199 times.
9877 for (qsizetype k = 0; k + 1 < path.size(); k += 2) {
877
2/2
✓ Branch 357 → 358 taken 6678 times.
✓ Branch 359 → 360 taken 6678 times.
6678 const double x = path[k].toDouble(), y = path[k + 1].toDouble();
878 6678 left = std::min(left, x), right = std::max(right, x), top = std::min(top, y), bottom = std::max(bottom, y);
879 }
880
2/4
✓ Branch 373 → 374 taken 12796 times.
✓ Branch 373 → 375 taken 3199 times.
✗ Branch 958 → 959 not taken.
✗ Branch 958 → 960 not taken.
15995 const QVariantList bounds{left, top, right, bottom};
881 12796 const auto personOf = [this](qint64 cardId) -> const RelationGraph::Person * {
882 12796 const int index = m_personIndex.value(cardId, -1);
883
1/2
✓ Branch 3 → 4 taken 12796 times.
✗ Branch 3 → 5 not taken.
12796 return index >= 0 ? &m_input.people.at(index) : nullptr;
884 3199 };
885 3199 const RelationGraph::Node &holder = m_graph.nodes.at(edge.from);
886 3199 const RelationGraph::Node &target = m_graph.nodes.at(edge.to);
887 3199 QString sentence;
888
2/2
✓ Branch 379 → 380 taken 1524 times.
✓ Branch 379 → 384 taken 1675 times.
3199 if (edge.kind == QLatin1String("relation"))
889
1/1
✓ Branch 380 → 381 taken 1524 times.
1524 sentence = RelationVocabulary::sentence(target.name, edge.type, holder.name, target.gender, edge.label);
890
2/2
✓ Branch 386 → 387 taken 31 times.
✓ Branch 386 → 401 taken 1644 times.
1675 else if (edge.kind == QLatin1String("member")) // F103-10: never drawn; the list and the area use it
891
3/6
✓ Branch 389 → 390 taken 31 times.
✗ Branch 389 → 392 not taken.
✓ Branch 390 → 391 taken 31 times.
✓ Branch 391 → 394 taken 31 times.
✗ Branch 972 → 973 not taken.
✗ Branch 972 → 974 not taken.
93 sentence = edge.type == QLatin1String("member") ? tr("%1 belongs to %2").arg(target.name, holder.name)
892
2/8
✗ Branch 392 → 393 not taken.
✗ Branch 393 → 394 not taken.
✗ Branch 396 → 397 not taken.
✓ Branch 396 → 398 taken 31 times.
✓ Branch 398 → 399 taken 31 times.
✗ Branch 398 → 400 not taken.
✗ Branch 968 → 969 not taken.
✗ Branch 968 → 970 not taken.
62 : tr("%1 belongs to %2").arg(holder.name, target.name);
893
2/2
✓ Branch 403 → 404 taken 188 times.
✓ Branch 403 → 410 taken 1456 times.
1644 else if (edge.kind == QLatin1String("via"))
894 //: Graph edge tooltip: "Ana Silva was introduced by Ben Weber".
895
2/2
✓ Branch 404 → 405 taken 188 times.
✓ Branch 405 → 406 taken 188 times.
188 sentence = tr("%1 was introduced by %2").arg(holder.name, target.name);
896
2/2
✓ Branch 412 → 413 taken 7 times.
✓ Branch 412 → 417 taken 1449 times.
1456 else if (edge.kind == QLatin1String("suggestion"))
897
1/1
✓ Branch 413 → 414 taken 7 times.
7 sentence = suggestionSentence(holder, target);
898
1/2
✓ Branch 419 → 420 taken 1449 times.
✗ Branch 419 → 431 not taken.
1449 else if (edge.kind == QLatin1String("inferred"))
899 //: Graph edge tooltip of a relationship that follows from others (F103-31).
900
1/1
✓ Branch 420 → 421 taken 1449 times.
1449 sentence = tr("%1 — follows from other relationships")
901
2/2
✓ Branch 423 → 424 taken 1449 times.
✓ Branch 424 → 425 taken 1449 times.
1449 .arg(RelationVocabulary::sentence(target.name, edge.type, holder.name, target.gender));
902 3199 const qint64 storedOn = edge.storedOnCardId;
903 3199 QString storedOnKey;
904
2/2
✓ Branch 432 → 433 taken 3177 times.
✓ Branch 432 → 435 taken 22 times.
3199 if (const int index = m_personIndex.value(storedOn, -1); index >= 0)
905 3177 storedOnKey = m_input.people.at(index).key;
906
1/1
✓ Branch 540 → 541 taken 3199 times.
9597 m_edges.append(QVariantMap{{QStringLiteral("from"), edge.from},
907 6398 {QStringLiteral("to"), edge.to},
908 6398 {QStringLiteral("x1"), a.x()},
909 6398 {QStringLiteral("y1"), a.y()},
910 6398 {QStringLiteral("x2"), b.x()},
911 6398 {QStringLiteral("y2"), b.y()},
912 6398 {QStringLiteral("kind"), edge.kind},
913 6398 {QStringLiteral("category"), edge.category},
914
2/2
✓ Branch 479 → 480 taken 1449 times.
✓ Branch 479 → 482 taken 226 times.
8073 {QStringLiteral("label"), edge.kind == QLatin1String("relation") || edge.kind == QLatin1String("inferred")
915
3/3
✓ Branch 476 → 477 taken 1675 times.
✓ Branch 476 → 480 taken 1524 times.
✓ Branch 480 → 481 taken 2973 times.
7847 ? RelationVocabulary::label(edge.type, target.gender, edge.label)
916 226 : edge.label},
917 6398 {QStringLiteral("sentence"), sentence},
918 6398 {QStringLiteral("storedOnKey"), storedOnKey},
919 6398 {QStringLiteral("sourceIndex"), edge.sourceIndex},
920 6398 {QStringLiteral("arrowTo"), arrowTo},
921 6398 {QStringLiteral("shape"), route.shape},
922 6398 {QStringLiteral("bundle"), route.bundle},
923
1/1
✓ Branch 513 → 514 taken 3199 times.
6398 {QStringLiteral("year"), weddingYear(edge, personOf(holder.cardId), personOf(target.cardId))},
924
1/1
✓ Branch 518 → 519 taken 3199 times.
6398 {QStringLiteral("points"), coordinates(route.points)},
925 6398 {QStringLiteral("path"), path},
926 6398 {QStringLiteral("bounds"), bounds},
927
1/1
✓ Branch 532 → 533 taken 3199 times.
6398 {QStringLiteral("style"), QString::fromLatin1(stroke.style)},
928
2/4
✓ Branch 544 → 545 taken 67179 times.
✓ Branch 544 → 546 taken 3199 times.
✗ Branch 999 → 1000 not taken.
✗ Branch 999 → 1001 not taken.
73577 {QStringLiteral("width"), stroke.width}});
929 3199 DrawnEdge drawn;
930 3199 drawn.from = edge.from;
931 3199 drawn.to = edge.to;
932 3199 drawn.arrowTo = arrowTo;
933 3199 drawn.bundle = route.bundle;
934
1/1
✓ Branch 593 → 594 taken 3199 times.
3199 drawn.year = weddingYear(edge, personOf(holder.cardId), personOf(target.cardId));
935 3199 drawn.width = stroke.width;
936 3199 drawn.kind = edge.kind;
937 3199 drawn.shape = route.shape;
938
1/1
✓ Branch 597 → 598 taken 3199 times.
3199 drawn.style = QString::fromLatin1(stroke.style);
939 3199 drawn.category = edge.category;
940
1/1
✓ Branch 601 → 602 taken 3199 times.
3199 drawn.points = canvasPoints(route.points);
941
2/2
✓ Branch 604 → 605 taken 3199 times.
✓ Branch 605 → 606 taken 3199 times.
3199 drawn.path = canvasPoints(RelationGraphLayout::flatten(route));
942
2/2
✓ Branch 610 → 611 taken 977 times.
✓ Branch 610 → 613 taken 2222 times.
3199 if (drawn.path.size() < 2)
943
1/1
✓ Branch 611 → 612 taken 977 times.
977 drawn.path = {a, b};
944
1/1
✓ Branch 613 → 614 taken 3199 times.
3199 m_drawnEdges.append(drawn);
945 // S103.7.2: the node beside the centre names how it is related.
946
4/4
✓ Branch 614 → 615 taken 1077 times.
✓ Branch 614 → 616 taken 2122 times.
✓ Branch 616 → 617 taken 741 times.
✓ Branch 616 → 618 taken 1381 times.
3199 const int other = holder.center ? edge.to : target.center ? edge.from : -1;
947
5/6
✓ Branch 620 → 621 taken 1818 times.
✓ Branch 620 → 624 taken 1381 times.
✓ Branch 622 → 623 taken 1818 times.
✗ Branch 622 → 624 not taken.
✓ Branch 625 → 626 taken 1818 times.
✓ Branch 625 → 656 taken 1381 times.
3199 if (other >= 0 && !sentence.isEmpty()) {
948
1/1
✓ Branch 627 → 628 taken 1818 times.
1818 QVariantMap node = m_nodes.at(other).toMap();
949
4/4
✓ Branch 632 → 633 taken 1818 times.
✓ Branch 633 → 634 taken 1818 times.
✓ Branch 640 → 641 taken 1781 times.
✓ Branch 640 → 654 taken 37 times.
3636 if (node.value(QStringLiteral("relation")).toString().isEmpty()) {
950
1/1
✓ Branch 645 → 646 taken 1781 times.
3562 node.insert(QStringLiteral("relation"), sentence);
951
1/1
✓ Branch 650 → 651 taken 1781 times.
1781 m_nodes[other] = node;
952 }
953 1818 }
954 3199 }
955 // As ContactGraphView.avatarSize / detail: the centre 48 px, else 32, in
956 // Everyone zoomed out 24 (below 60 %) and 14 (below 35 %, no years then).
957
3/6
✓ Branch 665 → 666 taken 8 times.
✓ Branch 665 → 671 taken 316 times.
✗ Branch 666 → 667 not taken.
✓ Branch 666 → 671 taken 8 times.
✗ Branch 667 → 668 not taken.
✗ Branch 667 → 669 not taken.
324 const int detail = !m_overview || m_scale >= 0.6 ? 2 : m_scale >= 0.35 ? 1 : 0;
958
1/1
✓ Branch 673 → 674 taken 324 times.
648 m_lineLabels = lineLabelsOf(m_edges, detail > 0, [this, detail](int node) {
959
3/6
✓ Branch 2 → 3 taken 372 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 372 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 372 times.
372 if (node < 0 || node >= m_graph.nodes.size())
960 ✗ return 16.0;
961
3/6
✓ Branch 10 → 11 taken 127 times.
✓ Branch 10 → 12 taken 245 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 17 taken 245 times.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
372 return m_graph.nodes.at(node).center ? 24.0 : detail == 2 ? 16.0 : detail == 1 ? 12.0 : 7.0;
962 324 });
963
1/1
✓ Branch 677 → 678 taken 324 times.
324 emit layoutChanged();
964
1/1
✓ Branch 678 → 679 taken 324 times.
324 emit viewChanged();
965 120689 }
966
967 290 QVariantList QuickContactGraph::areas() const {
968 290 QVariantList out;
969
2/2
✓ Branch 48 → 4 taken 505 times.
✓ Branch 48 → 49 taken 290 times.
795 for (const RelationGraph::Area &area : m_graph.areas) {
970 505 QVariantList members;
971
2/2
✓ Branch 13 → 7 taken 1049 times.
✓ Branch 13 → 14 taken 505 times.
1554 for (int member : area.members)
972
1/1
✓ Branch 9 → 10 taken 1049 times.
1049 members.append(member);
973 // F103-21: a family by name only has no card to open.
974 505 const bool named = RelationGraph::Families::isNamedFamily(area.familyCardId);
975
2/3
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 505 times.
✓ Branch 32 → 33 taken 505 times.
1515 out.append(QVariantMap{{QStringLiteral("key"), named ? QString() : area.key},
976 1010 {QStringLiteral("name"), area.name},
977
2/4
✓ Branch 36 → 37 taken 1515 times.
✓ Branch 36 → 38 taken 505 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
2525 {QStringLiteral("members"), members}});
978 505 }
979 290 return out;
980 2020 }
981
982 33 QVariantList QuickContactGraph::territories(const QVariantList &boxes, double reach, double margin) {
983 33 QElapsedTimer timer;
984 33 timer.start();
985
1/1
✓ Branch 3 → 4 taken 33 times.
33 m_territoryBoxes.clear();
986
2/2
✓ Branch 72 → 6 taken 246 times.
✓ Branch 72 → 73 taken 33 times.
279 for (const QVariant &value : boxes) {
987
1/1
✓ Branch 7 → 8 taken 246 times.
246 const QVariantMap box = value.toMap();
988
8/8
✓ Branch 8 → 9 taken 246 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 244 times.
✓ Branch 16 → 17 taken 244 times.
✓ Branch 22 → 23 taken 244 times.
✓ Branch 28 → 29 taken 244 times.
✓ Branch 34 → 35 taken 244 times.
✓ Branch 36 → 37 taken 246 times.
1222 m_territoryBoxes.append(box.isEmpty() ? QRectF()
989
18/34
✓ Branch 27 → 28 taken 244 times.
✓ Branch 33 → 34 taken 244 times.
✓ Branch 37 → 38 taken 244 times.
✓ Branch 37 → 39 taken 2 times.
✓ Branch 39 → 40 taken 244 times.
✓ Branch 39 → 41 taken 2 times.
✓ Branch 41 → 42 taken 244 times.
✓ Branch 41 → 43 taken 2 times.
✓ Branch 43 → 44 taken 244 times.
✓ Branch 43 → 45 taken 2 times.
✓ Branch 45 → 46 taken 244 times.
✓ Branch 45 → 47 taken 2 times.
✓ Branch 47 → 48 taken 244 times.
✓ Branch 47 → 49 taken 2 times.
✓ Branch 49 → 50 taken 244 times.
✓ Branch 49 → 51 taken 2 times.
✓ Branch 51 → 52 taken 244 times.
✓ Branch 51 → 53 taken 2 times.
✗ Branch 192 → 193 not taken.
✗ Branch 192 → 194 not taken.
✗ Branch 196 → 197 not taken.
✗ Branch 196 → 198 not taken.
✗ Branch 200 → 201 not taken.
✗ Branch 200 → 202 not taken.
✗ Branch 204 → 205 not taken.
✗ Branch 204 → 206 not taken.
✗ Branch 208 → 209 not taken.
✗ Branch 208 → 210 not taken.
✗ Branch 212 → 213 not taken.
✗ Branch 212 → 214 not taken.
✗ Branch 216 → 217 not taken.
✗ Branch 216 → 218 not taken.
✗ Branch 220 → 221 not taken.
✗ Branch 220 → 222 not taken.
1222 : QRectF(box.value(QStringLiteral("x")).toDouble(), box.value(QStringLiteral("y")).toDouble(),
990
18/34
✓ Branch 15 → 16 taken 244 times.
✓ Branch 21 → 22 taken 244 times.
✓ Branch 53 → 54 taken 244 times.
✓ Branch 53 → 55 taken 2 times.
✓ Branch 55 → 56 taken 244 times.
✓ Branch 55 → 57 taken 2 times.
✓ Branch 57 → 58 taken 244 times.
✓ Branch 57 → 59 taken 2 times.
✓ Branch 59 → 60 taken 244 times.
✓ Branch 59 → 61 taken 2 times.
✓ Branch 61 → 62 taken 244 times.
✓ Branch 61 → 63 taken 2 times.
✓ Branch 63 → 64 taken 244 times.
✓ Branch 63 → 65 taken 2 times.
✓ Branch 65 → 66 taken 244 times.
✓ Branch 65 → 67 taken 2 times.
✓ Branch 67 → 68 taken 244 times.
✓ Branch 67 → 69 taken 2 times.
✗ Branch 224 → 225 not taken.
✗ Branch 224 → 226 not taken.
✗ Branch 228 → 229 not taken.
✗ Branch 228 → 230 not taken.
✗ Branch 232 → 233 not taken.
✗ Branch 232 → 234 not taken.
✗ Branch 236 → 237 not taken.
✗ Branch 236 → 238 not taken.
✗ Branch 240 → 241 not taken.
✗ Branch 240 → 242 not taken.
✗ Branch 244 → 245 not taken.
✗ Branch 244 → 246 not taken.
✗ Branch 248 → 249 not taken.
✗ Branch 248 → 250 not taken.
✗ Branch 252 → 253 not taken.
✗ Branch 252 → 254 not taken.
1222 box.value(QStringLiteral("w")).toDouble(), box.value(QStringLiteral("h")).toDouble()));
991 246 }
992 // Members are the drawn people of each family (no family card, no "+N more").
993 33 QList<QList<int>> families;
994
4/4
✓ Branch 73 → 74 taken 33 times.
✓ Branch 74 → 75 taken 33 times.
✓ Branch 98 → 76 taken 34 times.
✓ Branch 98 → 99 taken 33 times.
67 for (const RelationGraph::Area &area : m_graph.areas) {
995 34 QList<int> members;
996
2/2
✓ Branch 93 → 79 taken 70 times.
✓ Branch 93 → 94 taken 34 times.
104 for (int member : area.members)
997
7/12
✓ Branch 80 → 81 taken 70 times.
✗ Branch 80 → 88 not taken.
✓ Branch 82 → 83 taken 70 times.
✗ Branch 82 → 88 not taken.
✓ Branch 83 → 84 taken 70 times.
✓ Branch 84 → 85 taken 70 times.
✗ Branch 84 → 88 not taken.
✓ Branch 85 → 86 taken 70 times.
✓ Branch 86 → 87 taken 70 times.
✗ Branch 86 → 88 not taken.
✓ Branch 89 → 90 taken 70 times.
✗ Branch 89 → 91 not taken.
70 if (member >= 0 && member < m_graph.nodes.size() && !m_graph.nodes[member].family && m_graph.nodes[member].moreCount == 0)
998
1/1
✓ Branch 90 → 91 taken 70 times.
70 members.append(member);
999
1/1
✓ Branch 94 → 95 taken 34 times.
34 families.append(members);
1000 34 }
1001 33 RelationGraphTerritories::Settings settings;
1002 33 settings.reach = reach;
1003 // World coordinates (S105.4.1): the territories cover every drawn person,
1004 // not only the canvas, so a pan or a zoom never needs new ones. A large
1005 // graph gets a coarser grid (at most ~400 steps across).
1006 33 QRectF region;
1007
2/2
✓ Branch 112 → 102 taken 246 times.
✓ Branch 112 → 113 taken 33 times.
279 for (const QRectF &box : std::as_const(m_territoryBoxes))
1008
2/2
✓ Branch 104 → 105 taken 244 times.
✓ Branch 104 → 110 taken 2 times.
246 if (!box.isEmpty())
1009
2/2
✓ Branch 106 → 107 taken 18 times.
✓ Branch 106 → 108 taken 226 times.
244 region = region.isNull() ? box : region.united(box);
1010 33 const double beyond = reach + margin + settings.corner;
1011 33 region = region.adjusted(-beyond, -beyond, beyond, beyond);
1012 33 settings.step = std::max(settings.step, std::max(region.width(), region.height()) / 400);
1013 33 m_territoryRegion = region;
1014
1/1
✓ Branch 118 → 119 taken 33 times.
33 m_territories = RelationGraphTerritories::build(m_territoryBoxes, families, region, settings);
1015 33 QVariantList out;
1016 33 int points = 0;
1017
2/2
✓ Branch 169 → 124 taken 34 times.
✓ Branch 169 → 170 taken 33 times.
67 for (const RelationGraphTerritories::Territory &territory : std::as_const(m_territories)) {
1018 34 QVariantList loops;
1019
2/2
✓ Branch 151 → 127 taken 35 times.
✓ Branch 151 → 152 taken 34 times.
69 for (const QList<QPointF> &loop : territory.loops) {
1020 35 QVariantList flat;
1021
1/1
✓ Branch 129 → 130 taken 35 times.
35 flat.reserve(2 * loop.size());
1022
2/2
✓ Branch 143 → 132 taken 7956 times.
✓ Branch 143 → 144 taken 35 times.
7991 for (QPointF point : loop)
1023
2/2
✓ Branch 135 → 136 taken 7956 times.
✓ Branch 138 → 139 taken 7956 times.
7956 flat << point.x() << point.y();
1024
1/1
✓ Branch 145 → 146 taken 35 times.
35 loops.append(QVariant(flat));
1025 35 points += int(loop.size());
1026 35 }
1027
3/5
✓ Branch 158 → 159 taken 34 times.
✓ Branch 162 → 163 taken 34 times.
✓ Branch 162 → 164 taken 34 times.
✗ Branch 286 → 287 not taken.
✗ Branch 286 → 288 not taken.
102 out.append(QVariantMap{{QStringLiteral("loops"), loops}});
1028 34 }
1029
9/9
✓ Branch 170 → 171 taken 33 times.
✓ Branch 174 → 175 taken 33 times.
✓ Branch 175 → 176 taken 33 times.
✓ Branch 177 → 178 taken 33 times.
✓ Branch 178 → 179 taken 33 times.
✓ Branch 179 → 180 taken 33 times.
✓ Branch 180 → 181 taken 33 times.
✓ Branch 187 → 172 taken 33 times.
✓ Branch 187 → 188 taken 33 times.
66 qCDebug(lcGraph) << "Territories:" << m_territories.size() << "families," << points << "outline points in"
1030
2/2
✓ Branch 182 → 183 taken 33 times.
✓ Branch 183 → 184 taken 33 times.
33 << timer.elapsed() << "ms";
1031
1/1
✓ Branch 188 → 189 taken 33 times.
33 emit territoriesChanged();
1032 33 return out;
1033 101 }
1034
1035 74 int QuickContactGraph::territoryAt(double x, double y) const {
1036
1/1
✓ Branch 3 → 4 taken 74 times.
74 return RelationGraphTerritories::territoryAt(m_territories, QPointF(x, y));
1037 }
1038
1039 4 QVariantMap QuickContactGraph::territoryLabel(int area, double width, double height, const QVariantList &keepClear) const {
1040
4/8
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 7 not taken.
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 4 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 4 times.
4 if (area < 0 || area >= m_territories.size() || area >= m_graph.areas.size())
1041 ✗ return {};
1042 12 const auto rectOf = [](const QVariant &value) {
1043
1/1
✓ Branch 2 → 3 taken 12 times.
12 const QVariantMap box = value.toMap();
1044
2/2
✓ Branch 19 → 20 taken 12 times.
✓ Branch 25 → 26 taken 12 times.
36 return QRectF(box.value(QStringLiteral("x")).toDouble(), box.value(QStringLiteral("y")).toDouble(),
1045
6/6
✓ Branch 7 → 8 taken 12 times.
✓ Branch 8 → 9 taken 12 times.
✓ Branch 13 → 14 taken 12 times.
✓ Branch 14 → 15 taken 12 times.
✓ Branch 20 → 21 taken 12 times.
✓ Branch 26 → 27 taken 12 times.
84 box.value(QStringLiteral("w")).toDouble(), box.value(QStringLiteral("h")).toDouble());
1046 12 };
1047 4 QList<QRectF> members;
1048
2/2
✓ Branch 28 → 14 taken 12 times.
✓ Branch 28 → 29 taken 4 times.
16 for (int member : m_graph.areas[area].members)
1049
4/8
✓ Branch 15 → 16 taken 12 times.
✗ Branch 15 → 22 not taken.
✓ Branch 17 → 18 taken 12 times.
✗ Branch 17 → 22 not taken.
✓ Branch 20 → 21 taken 12 times.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 12 times.
✗ Branch 23 → 26 not taken.
12 if (member >= 0 && member < m_territoryBoxes.size() && !m_territoryBoxes[member].isEmpty())
1050
1/1
✓ Branch 25 → 26 taken 12 times.
12 members.append(m_territoryBoxes[member]);
1051 4 QList<QRectF> people;
1052
2/2
✓ Branch 37 → 31 taken 90 times.
✓ Branch 37 → 38 taken 4 times.
94 for (const QRectF &box : m_territoryBoxes)
1053
2/2
✓ Branch 33 → 34 taken 86 times.
✓ Branch 33 → 35 taken 4 times.
90 if (!box.isEmpty())
1054
1/1
✓ Branch 34 → 35 taken 86 times.
86 people.append(box);
1055 4 QList<QList<QPointF>> lines;
1056
2/2
✓ Branch 161 → 40 taken 232 times.
✓ Branch 161 → 162 taken 4 times.
236 for (const QVariant &value : m_edges) {
1057
1/1
✓ Branch 41 → 42 taken 232 times.
232 const QVariantMap edge = value.toMap();
1058
27/52
✓ Branch 43 → 44 taken 232 times.
✓ Branch 48 → 49 taken 232 times.
✓ Branch 49 → 50 taken 232 times.
✓ Branch 50 → 51 taken 220 times.
✓ Branch 50 → 60 taken 12 times.
✓ Branch 52 → 53 taken 220 times.
✓ Branch 57 → 58 taken 220 times.
✓ Branch 58 → 59 taken 220 times.
✓ Branch 59 → 60 taken 100 times.
✓ Branch 59 → 61 taken 120 times.
✓ Branch 62 → 63 taken 220 times.
✓ Branch 62 → 64 taken 12 times.
✓ Branch 64 → 65 taken 220 times.
✓ Branch 64 → 66 taken 12 times.
✓ Branch 66 → 67 taken 220 times.
✓ Branch 66 → 68 taken 12 times.
✓ Branch 68 → 69 taken 220 times.
✓ Branch 68 → 70 taken 12 times.
✓ Branch 70 → 71 taken 220 times.
✓ Branch 70 → 72 taken 12 times.
✓ Branch 72 → 73 taken 232 times.
✗ Branch 72 → 74 not taken.
✓ Branch 74 → 75 taken 232 times.
✗ Branch 74 → 76 not taken.
✓ Branch 76 → 77 taken 232 times.
✗ Branch 76 → 78 not taken.
✓ Branch 78 → 79 taken 232 times.
✗ Branch 78 → 80 not taken.
✓ Branch 80 → 81 taken 232 times.
✗ Branch 80 → 82 not taken.
✓ Branch 82 → 83 taken 112 times.
✓ Branch 82 → 84 taken 120 times.
✗ Branch 217 → 218 not taken.
✗ Branch 217 → 219 not taken.
✗ Branch 221 → 222 not taken.
✗ Branch 221 → 223 not taken.
✗ Branch 225 → 226 not taken.
✗ Branch 225 → 227 not taken.
✗ Branch 229 → 230 not taken.
✗ Branch 229 → 231 not taken.
✗ Branch 233 → 234 not taken.
✗ Branch 233 → 235 not taken.
✗ Branch 238 → 239 not taken.
✗ Branch 238 → 240 not taken.
✗ Branch 242 → 243 not taken.
✗ Branch 242 → 244 not taken.
✗ Branch 246 → 247 not taken.
✗ Branch 246 → 248 not taken.
✗ Branch 250 → 251 not taken.
✗ Branch 250 → 252 not taken.
✗ Branch 254 → 255 not taken.
✗ Branch 254 → 256 not taken.
684 if (edge.value(QStringLiteral("kind")) == QLatin1String("member") || edge.value(QStringLiteral("shape")) == QLatin1String("none"))
1059 112 continue;
1060
2/2
✓ Branch 88 → 89 taken 120 times.
✓ Branch 89 → 90 taken 120 times.
240 const QVariantList path = edge.value(QStringLiteral("path")).toList();
1061 120 QList<QPointF> line;
1062
1/2
✓ Branch 95 → 96 taken 120 times.
✗ Branch 95 → 106 not taken.
120 if (path.size() >= 4)
1063
2/2
✓ Branch 105 → 97 taken 690 times.
✓ Branch 105 → 150 taken 120 times.
810 for (qsizetype k = 0; k + 1 < path.size(); k += 2)
1064
3/3
✓ Branch 98 → 99 taken 690 times.
✓ Branch 100 → 101 taken 690 times.
✓ Branch 102 → 103 taken 690 times.
690 line.append(QPointF(path[k].toDouble(), path[k + 1].toDouble()));
1065 else
1066 ✗ line = {QPointF(edge.value(QStringLiteral("x1")).toDouble(), edge.value(QStringLiteral("y1")).toDouble()),
1067 ✗ QPointF(edge.value(QStringLiteral("x2")).toDouble(), edge.value(QStringLiteral("y2")).toDouble())};
1068
1/1
✓ Branch 150 → 151 taken 120 times.
120 lines.append(line);
1069 232 }
1070 4 QList<QRectF> clear;
1071
2/2
✓ Branch 169 → 164 taken 12 times.
✓ Branch 169 → 170 taken 4 times.
16 for (const QVariant &value : keepClear)
1072
2/2
✓ Branch 165 → 166 taken 12 times.
✓ Branch 166 → 167 taken 12 times.
12 clear.append(rectOf(value));
1073
1/1
✓ Branch 172 → 173 taken 4 times.
4 const QRectF spot = RelationGraphTerritories::labelSpot(m_territories[area], members, QSizeF(width, height), people,
1074 4 lines, clear, m_territoryRegion);
1075
1/2
✗ Branch 174 → 175 not taken.
✓ Branch 174 → 176 taken 4 times.
4 if (spot.isEmpty())
1076 ✗ return {};
1077 16 return {{QStringLiteral("x"), spot.x()}, {QStringLiteral("y"), spot.y()}, {QStringLiteral("w"), spot.width()},
1078
2/4
✓ Branch 198 → 199 taken 16 times.
✓ Branch 198 → 200 taken 4 times.
✗ Branch 333 → 334 not taken.
✗ Branch 333 → 335 not taken.
28 {QStringLiteral("h"), spot.height()}};
1079 24 }
1080
1081 5 void QuickContactGraph::setRelationsList(bool on) {
1082
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 4 times.
5 if (on == m_relationsList)
1083 1 return;
1084 4 m_relationsList = on;
1085
8/8
✓ Branch 4 → 5 taken 4 times.
✓ Branch 8 → 9 taken 4 times.
✓ Branch 9 → 10 taken 4 times.
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 12 taken 2 times.
✓ Branch 13 → 14 taken 4 times.
✓ Branch 17 → 6 taken 4 times.
✓ Branch 17 → 18 taken 4 times.
8 qCInfo(lcGraph) << "Relationships list" << (on ? "shown" : "hidden");
1086 4 emit relationsListChanged();
1087 }
1088
1089 1613 void QuickContactGraph::setHiddenCategories(const QStringList &categories) {
1090 22 static const QStringList known{QStringLiteral("family"), QStringLiteral("friends"), QStringLiteral("work"),
1091
5/10
✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 28 taken 1591 times.
✓ Branch 4 → 5 taken 22 times.
✗ Branch 4 → 28 not taken.
✓ Branch 21 → 22 taken 88 times.
✓ Branch 21 → 23 taken 22 times.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
1745 QStringLiteral("other")};
1092 1613 QStringList hidden;
1093
2/2
✓ Branch 36 → 30 taken 6452 times.
✓ Branch 36 → 37 taken 1613 times.
8065 for (const QString &category : known) // a fixed order, unknown ids dropped
1094
2/2
✓ Branch 32 → 33 taken 11 times.
✓ Branch 32 → 34 taken 6441 times.
6452 if (categories.contains(category))
1095
1/1
✓ Branch 33 → 34 taken 11 times.
11 hidden.append(category);
1096
3/3
✓ Branch 37 → 38 taken 1613 times.
✓ Branch 38 → 39 taken 1602 times.
✓ Branch 38 → 40 taken 11 times.
1613 if (hidden == m_hiddenCategories)
1097 1602 return;
1098 11 m_hiddenCategories = hidden;
1099
2/2
✓ Branch 41 → 42 taken 11 times.
✓ Branch 47 → 48 taken 11 times.
22 QSettings().setValue(QStringLiteral("contacts/graphHidden"), hidden);
1100
6/6
✓ Branch 52 → 53 taken 11 times.
✓ Branch 56 → 57 taken 11 times.
✓ Branch 57 → 58 taken 11 times.
✓ Branch 59 → 60 taken 11 times.
✓ Branch 65 → 54 taken 11 times.
✓ Branch 65 → 66 taken 11 times.
22 qCInfo(lcGraph) << "Legend filter, hidden categories:" << hidden;
1101
1/1
✓ Branch 66 → 67 taken 11 times.
11 emit hiddenCategoriesChanged();
1102 1723 }
1103
1104 7 void QuickContactGraph::toggleCategory(const QString &category) {
1105 7 QStringList next = m_hiddenCategories;
1106
3/3
✓ Branch 3 → 4 taken 7 times.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 3 times.
7 if (!next.removeAll(category))
1107
1/1
✓ Branch 5 → 6 taken 4 times.
4 next.append(category);
1108
1/1
✓ Branch 6 → 7 taken 7 times.
7 setHiddenCategories(next);
1109 7 }
1110
1111 230 QVariantList QuickContactGraph::breadcrumb() const {
1112 230 QVariantList out;
1113
2/2
✓ Branch 69 → 3 taken 371 times.
✓ Branch 69 → 70 taken 230 times.
601 for (int step = 0; step < m_history.size(); ++step) {
1114 371 const QString &key = m_history.at(step);
1115
2/2
✓ Branch 6 → 7 taken 65 times.
✓ Branch 6 → 36 taken 306 times.
371 if (key == QLatin1String(kOverviewKey)) // F103-12: the overview of everyone
1116
2/2
✓ Branch 14 → 15 taken 65 times.
✓ Branch 22 → 23 taken 65 times.
260 out.append(QVariantMap{{QStringLiteral("key"), key}, {QStringLiteral("name"), tr("Everyone")},
1117
2/4
✓ Branch 26 → 27 taken 195 times.
✓ Branch 26 → 28 taken 65 times.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
390 {QStringLiteral("index"), step}});
1118
1/2
✓ Branch 38 → 39 taken 306 times.
✗ Branch 38 → 67 not taken.
306 else if (const int index = m_personIndex.value(cardIdOf(key), -1); index >= 0)
1119
1/1
✓ Branch 54 → 55 taken 306 times.
1224 out.append(QVariantMap{{QStringLiteral("key"), key}, {QStringLiteral("name"), m_input.people.at(index).name},
1120
2/4
✓ Branch 58 → 59 taken 918 times.
✓ Branch 58 → 60 taken 306 times.
✗ Branch 114 → 115 not taken.
✗ Branch 114 → 116 not taken.
1836 {QStringLiteral("index"), step}});
1121 }
1122 230 return out;
1123 1484 }
1124
1125 // F103-12: every person with a relationship, without a centre (force layout,
1126 // positions kept from the last overview so it does not reshuffle).
1127 5 void QuickContactGraph::showAll() {
1128 5 ensureInput();
1129
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
5 if (m_overview)
1130 ✗ return;
1131
1/2
✓ Branch 6 → 7 taken 5 times.
✗ Branch 6 → 13 not taken.
5 if (!m_centerKey.isEmpty()) {
1132 5 m_history.removeAll(m_centerKey);
1133 5 m_history.append(m_centerKey);
1134
1/2
✗ Branch 12 → 10 not taken.
✓ Branch 12 → 13 taken 5 times.
5 while (m_history.size() > 4)
1135 ✗ m_history.removeFirst();
1136 }
1137 5 m_overview = true;
1138 5 m_centerKey.clear();
1139 5 clearPath();
1140 5 rebuild();
1141 }
1142
1143 3 void QuickContactGraph::showMe() {
1144 3 ensureInput();
1145
2/3
✓ Branch 3 → 4 taken 3 times.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 5 → 7 not taken.
3 if (const QString me = defaultCenter(); !me.isEmpty())
1146
1/1
✓ Branch 6 → 7 taken 3 times.
3 show(me, true);
1147 3 }
1148
1149 54 bool QuickContactGraph::hasMe() const {
1150
3/3
✓ Branch 2 → 3 taken 54 times.
✓ Branch 5 → 6 taken 20 times.
✓ Branch 5 → 7 taken 34 times.
54 if (m_cardIds.contains(meKey()))
1151 20 return true;
1152
2/3
✓ Branch 8 → 9 taken 34 times.
✗ Branch 8 → 10 not taken.
✓ Branch 9 → 11 taken 34 times.
34 const QString suggested = m_meFallback ? m_meFallback() : QString();
1153 34 return !suggested.isEmpty();
1154 34 }
1155
1156 54 QVariantList QuickContactGraph::candidates() const {
1157 54 QHash<qint64, int> degree;
1158
2/2
✓ Branch 12 → 4 taken 2622 times.
✓ Branch 12 → 13 taken 54 times.
2676 for (const RelationGraph::StoredEdge &edge : m_input.edges)
1159
3/4
✓ Branch 5 → 6 taken 2588 times.
✓ Branch 5 → 10 taken 34 times.
✓ Branch 6 → 7 taken 2588 times.
✗ Branch 6 → 10 not taken.
2622 if (edge.toCardId > 0 && edge.fromCardId != edge.toCardId) {
1160
1/1
✓ Branch 7 → 8 taken 2588 times.
2588 degree[edge.fromCardId] += 1;
1161
1/1
✓ Branch 8 → 9 taken 2588 times.
2588 degree[edge.toCardId] += 1;
1162 }
1163
1/1
✓ Branch 13 → 14 taken 54 times.
54 QList<qint64> cards = degree.keys();
1164
3/3
✓ Branch 14 → 15 taken 54 times.
✓ Branch 15 → 16 taken 54 times.
✓ Branch 16 → 17 taken 54 times.
54 std::sort(cards.begin(), cards.end(), [&degree](qint64 a, qint64 b) {
1165
2/2
✓ Branch 4 → 5 taken 5008 times.
✓ Branch 4 → 8 taken 1286 times.
6294 return degree.value(a) != degree.value(b) ? degree.value(a) > degree.value(b) : a < b;
1166 });
1167 54 QVariantList out;
1168
5/5
✓ Branch 17 → 18 taken 54 times.
✓ Branch 18 → 19 taken 54 times.
✓ Branch 19 → 20 taken 54 times.
✓ Branch 56 → 21 taken 177 times.
✓ Branch 56 → 57 taken 54 times.
231 for (const qint64 card : cards.mid(0, 5))
1169
1/2
✓ Branch 23 → 24 taken 177 times.
✗ Branch 23 → 54 not taken.
177 if (const int index = m_personIndex.value(card, -1); index >= 0)
1170
1/1
✓ Branch 41 → 42 taken 177 times.
531 out.append(QVariantMap{{QStringLiteral("key"), m_input.people.at(index).key},
1171 354 {QStringLiteral("name"), m_input.people.at(index).name},
1172
2/4
✓ Branch 45 → 46 taken 531 times.
✓ Branch 45 → 47 taken 177 times.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
1116 {QStringLiteral("count"), degree.value(card)}});
1173 54 return out;
1174 762 }
1175
1176 2 bool QuickContactGraph::how(const QString &key, const QString &fromKey) {
1177
1/1
✓ Branch 2 → 3 taken 2 times.
2 ensureInput();
1178
2/3
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
✓ Branch 5 → 7 taken 2 times.
2 const qint64 from = cardIdOf(fromKey.isEmpty() ? meKey() : fromKey);
1179 2 const qint64 to = cardIdOf(key);
1180
1/1
✓ Branch 10 → 11 taken 2 times.
2 m_pathKeys.clear();
1181 2 m_pathText.clear();
1182
3/6
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 39 not taken.
✓ Branch 13 → 14 taken 2 times.
✗ Branch 13 → 39 not taken.
✓ Branch 14 → 15 taken 2 times.
✗ Branch 14 → 39 not taken.
2 if (from > 0 && to > 0 && from != to) {
1183
1/1
✓ Branch 15 → 16 taken 2 times.
2 const RelationPath::Path path = RelationPath::find(m_input, from, to);
1184
2/2
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 37 taken 1 time.
2 if (path.found) {
1185 // S106-275: a path may run through a card that is not a person of the
1186 // graph (an edge's far end the index knows nothing about) — the
1187 // default index 0 named the first person of the book instead.
1188 1 QStringList names;
1189 1 bool complete = true;
1190
2/2
✓ Branch 28 → 19 taken 5 times.
✓ Branch 28 → 29 taken 1 time.
6 for (const qint64 card : path.cards) {
1191 5 const int index = m_personIndex.value(card, -1);
1192
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 5 times.
5 if (index < 0) {
1193 ✗ complete = false;
1194 ✗ break;
1195 }
1196 5 const RelationGraph::Person &person = m_input.people.at(index);
1197
1/1
✓ Branch 24 → 25 taken 5 times.
5 names.append(person.name);
1198
1/1
✓ Branch 25 → 26 taken 5 times.
5 m_pathKeys.append(person.key);
1199 }
1200
1/2
✓ Branch 29 → 30 taken 1 time.
✗ Branch 29 → 34 not taken.
1 if (complete)
1201
1/1
✓ Branch 30 → 31 taken 1 time.
1 m_pathText = RelationPath::sentence(path, names);
1202 else
1203 ✗ m_pathKeys.clear();
1204 1 }
1205 2 }
1206
5/6
✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 44 taken 1 time.
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 44 not taken.
✓ Branch 45 → 46 taken 1 time.
✓ Branch 45 → 54 taken 1 time.
2 if (const int toIndex = m_personIndex.value(to, -1); m_pathText.isEmpty() && toIndex >= 0)
1207 //: "How do I know …?" without a chain of relationships or introductions.
1208
2/2
✓ Branch 46 → 47 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
2 m_pathText = tr("No chain of relationships leads to %1 yet.").arg(m_input.people.at(toIndex).name);
1209
7/7
✓ Branch 54 → 55 taken 2 times.
✓ Branch 58 → 59 taken 2 times.
✓ Branch 59 → 60 taken 2 times.
✓ Branch 61 → 62 taken 2 times.
✓ Branch 62 → 63 taken 2 times.
✓ Branch 66 → 56 taken 2 times.
✓ Branch 66 → 67 taken 2 times.
4 qCInfo(lcGraph) << "Path:" << m_pathKeys.size() << "people";
1210
1/1
✓ Branch 67 → 68 taken 2 times.
2 emit pathChanged();
1211 4 return !m_pathKeys.isEmpty();
1212 }
1213
1214 75 void QuickContactGraph::clearPath() {
1215
5/6
✓ Branch 3 → 4 taken 75 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 74 times.
✓ Branch 5 → 7 taken 1 time.
✓ Branch 8 → 9 taken 74 times.
✓ Branch 8 → 10 taken 1 time.
75 if (m_pathKeys.isEmpty() && m_pathText.isEmpty())
1216 74 return;
1217 1 m_pathKeys.clear();
1218 1 m_pathText.clear();
1219 1 emit pathChanged();
1220 }
1221