Mercurial > hg > easyhg
comparison grapher.cpp @ 46:bd3accba9b3f
* Better layout for branches; spline connection paths
author | Chris Cannam |
---|---|
date | Wed, 10 Nov 2010 17:11:41 +0000 |
parents | 4286836bb3c9 |
children | 24efab584ee5 |
comparison
equal
deleted
inserted
replaced
45:4286836bb3c9 | 46:bd3accba9b3f |
---|---|
1 | 1 |
2 #include "grapher.h" | 2 #include "grapher.h" |
3 #include "connectionitem.h" | |
3 | 4 |
4 #include <QGraphicsScene> | 5 #include <QGraphicsScene> |
5 | 6 |
6 #include <iostream> | 7 #include <iostream> |
7 | 8 |
32 Grapher::layoutRow(QString id) | 33 Grapher::layoutRow(QString id) |
33 { | 34 { |
34 if (m_handled.contains(id)) { | 35 if (m_handled.contains(id)) { |
35 return; | 36 return; |
36 } | 37 } |
37 if (!m_idCsetMap.contains(id)) { | 38 if (!m_changesets.contains(id)) { |
38 throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); | 39 throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); |
39 } | 40 } |
40 if (!m_items.contains(id)) { | 41 if (!m_items.contains(id)) { |
41 throw LayoutException(QString("Changeset %1 not in item map").arg(id)); | 42 throw LayoutException(QString("Changeset %1 not in item map").arg(id)); |
42 } | 43 } |
43 Changeset *cs = m_idCsetMap[id]; | 44 Changeset *cs = m_changesets[id]; |
44 ChangesetItem *item = m_items[id]; | 45 ChangesetItem *item = m_items[id]; |
45 std::cerr << "Looking at " << id.toStdString() << std::endl; | 46 std::cerr << "Looking at " << id.toStdString() << std::endl; |
46 | 47 |
47 int row = 0; | 48 int row = 0; |
48 int nparents = cs->parents().size(); | 49 int nparents = cs->parents().size(); |
49 | 50 |
50 if (nparents > 0) { | 51 if (nparents > 0) { |
51 bool haveRow = false; | 52 bool haveRow = false; |
52 foreach (QString parentId, cs->parents()) { | 53 foreach (QString parentId, cs->parents()) { |
53 | 54 |
54 if (!m_idCsetMap.contains(parentId)) continue; | 55 if (!m_changesets.contains(parentId)) continue; |
55 if (!m_items.contains(parentId)) continue; | 56 if (!m_items.contains(parentId)) continue; |
56 | 57 |
57 if (!m_handled.contains(parentId)) { | 58 if (!m_handled.contains(parentId)) { |
58 layoutRow(parentId); | 59 layoutRow(parentId); |
59 } | 60 } |
69 | 70 |
70 std::cerr << "putting " << cs->id().toStdString() << " at row " << row | 71 std::cerr << "putting " << cs->id().toStdString() << " at row " << row |
71 << std::endl; | 72 << std::endl; |
72 | 73 |
73 item->setRow(row); | 74 item->setRow(row); |
74 item->setY(row * 100); | |
75 m_handled.insert(id); | 75 m_handled.insert(id); |
76 } | 76 } |
77 | 77 |
78 void | 78 void |
79 Grapher::layoutCol(QString id) | 79 Grapher::layoutCol(QString id) |
80 { | 80 { |
81 if (m_handled.contains(id)) { | 81 if (m_handled.contains(id)) { |
82 std::cerr << "Already looked at " << id.toStdString() << std::endl; | 82 std::cerr << "Already looked at " << id.toStdString() << std::endl; |
83 return; | 83 return; |
84 } | 84 } |
85 if (!m_idCsetMap.contains(id)) { | 85 if (!m_changesets.contains(id)) { |
86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); | 86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); |
87 } | 87 } |
88 if (!m_items.contains(id)) { | 88 if (!m_items.contains(id)) { |
89 throw LayoutException(QString("Changeset %1 not in item map").arg(id)); | 89 throw LayoutException(QString("Changeset %1 not in item map").arg(id)); |
90 } | 90 } |
91 Changeset *cs = m_idCsetMap[id]; | 91 Changeset *cs = m_changesets[id]; |
92 ChangesetItem *item = m_items[id]; | 92 ChangesetItem *item = m_items[id]; |
93 std::cerr << "Looking at " << id.toStdString() << std::endl; | 93 std::cerr << "Looking at " << id.toStdString() << std::endl; |
94 | 94 |
95 foreach (QString parentId, cs->parents()) { | |
96 if (!m_changesets.contains(parentId)) continue; | |
97 if (!m_handled.contains(parentId)) { | |
98 layoutCol(parentId); | |
99 } | |
100 } | |
101 | |
95 int col = 0; | 102 int col = 0; |
103 int row = item->row(); | |
104 QString branch = cs->branch(); | |
96 int nparents = cs->parents().size(); | 105 int nparents = cs->parents().size(); |
97 | 106 QString parentId; |
98 if (nparents > 0) { | 107 int parentsOnSameBranch = 0; |
99 bool preferParentCol = true; | 108 |
100 foreach (QString parentId, cs->parents()) { | 109 switch (nparents) { |
101 | 110 |
102 if (!m_idCsetMap.contains(parentId)) continue; | 111 case 0: |
103 if (!m_items.contains(parentId)) continue; | 112 col = m_branchHomes[cs->branch()]; |
104 | 113 col = findAvailableColumn(row, col, true); |
105 if (nparents == 1) { | 114 break; |
106 // when introducing a new branch, aim _not_ to | 115 |
107 // position child on the same column as parent | 116 case 1: |
108 Changeset *parent = m_idCsetMap[parentId]; | 117 parentId = cs->parents()[0]; |
109 if (parent->branch() != cs->branch()) { | 118 |
110 preferParentCol = false; | 119 if (!m_changesets.contains(parentId) || |
120 m_changesets[parentId]->branch() != branch) { | |
121 // new branch | |
122 col = m_branchHomes[branch]; | |
123 } else { | |
124 col = m_items[parentId]->column(); | |
125 } | |
126 | |
127 col = findAvailableColumn(row, col, true); | |
128 break; | |
129 | |
130 case 2: | |
131 // a merge: behave differently if parents are both on the same | |
132 // branch (we also want to behave differently for nodes that | |
133 // have multiple children on the same branch -- spreading them | |
134 // out rather than having affinity to a specific branch) | |
135 | |
136 foreach (QString parentId, cs->parents()) { | |
137 if (!m_changesets.contains(parentId)) continue; | |
138 if (m_changesets[parentId]->branch() == branch) { | |
139 ChangesetItem *parentItem = m_items[parentId]; | |
140 col += parentItem->column(); | |
141 parentsOnSameBranch++; | |
142 } | |
143 } | |
144 | |
145 if (parentsOnSameBranch > 0) { | |
146 col /= parentsOnSameBranch; | |
147 col = findAvailableColumn(item->row(), col, true); | |
148 } else { | |
149 col = findAvailableColumn(item->row(), col, false); | |
150 } | |
151 break; | |
152 } | |
153 | |
154 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl; | |
155 | |
156 m_alloc[row].insert(col); | |
157 item->setColumn(col); | |
158 m_handled.insert(id); | |
159 } | |
160 | |
161 bool | |
162 Grapher::rangesConflict(const Range &r1, const Range &r2) | |
163 { | |
164 // allow some additional space at edges. we really ought also to | |
165 // permit space at the end of a branch up to the point where the | |
166 // merge happens | |
167 int a1 = r1.first - 2, b1 = r1.second + 2; | |
168 int a2 = r2.first - 2, b2 = r2.second + 2; | |
169 if (a1 > b2 || b1 < a2) return false; | |
170 if (a2 > b1 || b2 < a1) return false; | |
171 return true; | |
172 } | |
173 | |
174 void | |
175 Grapher::allocateBranchHomes(Changesets csets) | |
176 { | |
177 foreach (Changeset *cs, csets) { | |
178 QString branch = cs->branch(); | |
179 ChangesetItem *item = m_items[cs->id()]; | |
180 if (!item) continue; | |
181 int row = item->row(); | |
182 if (!m_branchRanges.contains(branch)) { | |
183 m_branchRanges[branch] = Range(row, row); | |
184 } else { | |
185 Range p = m_branchRanges[branch]; | |
186 if (row < p.first) p.first = row; | |
187 if (row > p.second) p.second = row; | |
188 m_branchRanges[branch] = p; | |
189 } | |
190 } | |
191 | |
192 m_branchHomes[""] = 0; | |
193 | |
194 foreach (QString branch, m_branchRanges.keys()) { | |
195 if (branch == "") continue; | |
196 QSet<int> taken; | |
197 taken.insert(0); | |
198 Range myRange = m_branchRanges[branch]; | |
199 foreach (QString other, m_branchRanges.keys()) { | |
200 if (other == branch || other == "") continue; | |
201 Range otherRange = m_branchRanges[other]; | |
202 if (rangesConflict(myRange, otherRange)) { | |
203 if (m_branchHomes.contains(other)) { | |
204 taken.insert(m_branchHomes[other]); | |
111 } | 205 } |
112 } | 206 } |
113 | 207 } |
114 if (!m_handled.contains(parentId)) { | 208 int home = 2; |
115 layoutCol(parentId); | 209 while (taken.contains(home)) { |
116 } | 210 if (home > 0) home = -home; |
117 | 211 else home = -(home-2); |
118 ChangesetItem *parentItem = m_items[parentId]; | 212 } |
119 col += parentItem->column(); | 213 m_branchHomes[branch] = home; |
120 } | 214 } |
121 | 215 |
122 col /= cs->parents().size(); | 216 foreach (QString branch, m_branchRanges.keys()) { |
123 col = findAvailableColumn(item->row(), col, preferParentCol); | 217 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl; |
124 m_alloc[item->row()].insert(col); | 218 } |
125 } | |
126 | |
127 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl; | |
128 | |
129 item->setColumn(col); | |
130 item->setX(col * 100); | |
131 m_handled.insert(id); | |
132 } | 219 } |
133 | 220 |
134 void | 221 void |
135 Grapher::layout(Changesets csets) | 222 Grapher::layout(Changesets csets) |
136 { | 223 { |
137 m_idCsetMap.clear(); | 224 m_changesets.clear(); |
138 m_items.clear(); | 225 m_items.clear(); |
139 m_alloc.clear(); | 226 m_alloc.clear(); |
227 m_branchHomes.clear(); | |
140 | 228 |
141 foreach (Changeset *cs, csets) { | 229 foreach (Changeset *cs, csets) { |
142 | 230 |
143 QString id = cs->id(); | 231 QString id = cs->id(); |
144 std::cerr << id.toStdString() << std::endl; | 232 std::cerr << id.toStdString() << std::endl; |
145 | 233 |
146 if (id == "") { | 234 if (id == "") { |
147 throw LayoutException("Changeset has no ID"); | 235 throw LayoutException("Changeset has no ID"); |
148 } | 236 } |
149 if (m_idCsetMap.contains(id)) { | 237 if (m_changesets.contains(id)) { |
150 throw LayoutException(QString("Duplicate changeset ID %1").arg(id)); | 238 throw LayoutException(QString("Duplicate changeset ID %1").arg(id)); |
151 } | 239 } |
152 | 240 |
153 m_idCsetMap[id] = cs; | 241 m_changesets[id] = cs; |
154 | 242 |
155 ChangesetItem *item = new ChangesetItem(cs); | 243 ChangesetItem *item = new ChangesetItem(cs); |
156 item->setX(0); | 244 item->setX(0); |
157 item->setY(0); | 245 item->setY(0); |
158 m_items[id] = item; | 246 m_items[id] = item; |
159 m_scene->addItem(item); | 247 m_scene->addItem(item); |
248 } | |
249 | |
250 foreach (Changeset *cs, csets) { | |
251 QString id = cs->id(); | |
252 ChangesetItem *item = m_items[id]; | |
253 foreach (QString parentId, cs->parents()) { | |
254 if (!m_items.contains(parentId)) continue; | |
255 ConnectionItem *conn = new ConnectionItem(); | |
256 conn->setChild(item); | |
257 conn->setParent(m_items[parentId]); | |
258 m_scene->addItem(conn); | |
259 } | |
160 } | 260 } |
161 | 261 |
162 // Layout in reverse order, i.e. forward chronological order. | 262 // Layout in reverse order, i.e. forward chronological order. |
163 // This ensures that parents will normally be laid out before | 263 // This ensures that parents will normally be laid out before |
164 // their children -- though we can recurse from layout() if we | 264 // their children -- though we can recurse from layout() if we |
165 // find any weird exceptions | 265 // find any weird exceptions |
166 m_handled.clear(); | 266 m_handled.clear(); |
167 for (int i = csets.size() - 1; i >= 0; --i) { | 267 for (int i = csets.size() - 1; i >= 0; --i) { |
168 layoutRow(csets[i]->id()); | 268 layoutRow(csets[i]->id()); |
169 } | 269 } |
270 | |
271 allocateBranchHomes(csets); | |
272 | |
170 m_handled.clear(); | 273 m_handled.clear(); |
171 for (int i = csets.size() - 1; i >= 0; --i) { | 274 for (int i = csets.size() - 1; i >= 0; --i) { |
172 layoutCol(csets[i]->id()); | 275 layoutCol(csets[i]->id()); |
173 } | 276 } |
174 | 277 /* |
175 foreach (Changeset *cs, csets) { | 278 foreach (Changeset *cs, csets) { |
176 QString id = cs->id(); | 279 QString id = cs->id(); |
177 if (!m_items.contains(id)) continue; | 280 if (!m_items.contains(id)) continue; |
178 ChangesetItem *me = m_items[id]; | 281 ChangesetItem *me = m_items[id]; |
179 foreach (QString parentId, cs->parents()) { | 282 foreach (QString parentId, cs->parents()) { |
183 line->setLine(me->x() + 25, me->y() + 50, | 286 line->setLine(me->x() + 25, me->y() + 50, |
184 parent->x() + 25, parent->y()); | 287 parent->x() + 25, parent->y()); |
185 m_scene->addItem(line); | 288 m_scene->addItem(line); |
186 } | 289 } |
187 } | 290 } |
188 } | 291 */ |
189 | 292 } |
293 |