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 §or) 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(), [°ree](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 |