Chris@44
|
1
|
Chris@44
|
2 #include "grapher.h"
|
Chris@46
|
3 #include "connectionitem.h"
|
Chris@44
|
4
|
cannam@45
|
5 #include <QGraphicsScene>
|
Chris@44
|
6
|
Chris@44
|
7 #include <iostream>
|
Chris@44
|
8
|
cannam@45
|
9 int
|
cannam@45
|
10 Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
|
cannam@45
|
11 {
|
cannam@45
|
12 int col = parent;
|
cannam@45
|
13 if (preferParentCol) {
|
cannam@45
|
14 if (!m_alloc[row].contains(col)) {
|
cannam@45
|
15 return col;
|
cannam@45
|
16 }
|
cannam@45
|
17 }
|
cannam@45
|
18 while (col > 0) {
|
cannam@45
|
19 if (!m_alloc[row].contains(--col)) return col;
|
cannam@45
|
20 }
|
cannam@45
|
21 while (col < 0) {
|
cannam@45
|
22 if (!m_alloc[row].contains(++col)) return col;
|
cannam@45
|
23 }
|
cannam@45
|
24 col = parent;
|
cannam@45
|
25 int sign = (col < 0 ? -1 : 1);
|
cannam@45
|
26 while (1) {
|
cannam@45
|
27 col += sign;
|
cannam@45
|
28 if (!m_alloc[row].contains(col)) return col;
|
cannam@45
|
29 }
|
cannam@45
|
30 }
|
Chris@44
|
31
|
cannam@45
|
32 void
|
cannam@45
|
33 Grapher::layoutRow(QString id)
|
Chris@44
|
34 {
|
cannam@45
|
35 if (m_handled.contains(id)) {
|
cannam@45
|
36 return;
|
Chris@44
|
37 }
|
Chris@46
|
38 if (!m_changesets.contains(id)) {
|
cannam@45
|
39 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
|
Chris@44
|
40 }
|
cannam@45
|
41 if (!m_items.contains(id)) {
|
cannam@45
|
42 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
|
Chris@44
|
43 }
|
Chris@46
|
44 Changeset *cs = m_changesets[id];
|
cannam@45
|
45 ChangesetItem *item = m_items[id];
|
cannam@45
|
46 std::cerr << "Looking at " << id.toStdString() << std::endl;
|
cannam@45
|
47
|
Chris@44
|
48 int row = 0;
|
cannam@45
|
49 int nparents = cs->parents().size();
|
cannam@45
|
50
|
cannam@45
|
51 if (nparents > 0) {
|
Chris@44
|
52 bool haveRow = false;
|
Chris@44
|
53 foreach (QString parentId, cs->parents()) {
|
cannam@45
|
54
|
Chris@46
|
55 if (!m_changesets.contains(parentId)) continue;
|
cannam@45
|
56 if (!m_items.contains(parentId)) continue;
|
cannam@45
|
57
|
cannam@45
|
58 if (!m_handled.contains(parentId)) {
|
cannam@45
|
59 layoutRow(parentId);
|
cannam@45
|
60 }
|
cannam@45
|
61
|
cannam@45
|
62 ChangesetItem *parentItem = m_items[parentId];
|
Chris@44
|
63 if (!haveRow || parentItem->row() < row) {
|
Chris@44
|
64 row = parentItem->row();
|
Chris@44
|
65 haveRow = true;
|
Chris@44
|
66 }
|
Chris@44
|
67 }
|
Chris@44
|
68 row = row - 1;
|
cannam@45
|
69 }
|
cannam@45
|
70
|
cannam@45
|
71 std::cerr << "putting " << cs->id().toStdString() << " at row " << row
|
cannam@45
|
72 << std::endl;
|
cannam@45
|
73
|
Chris@44
|
74 item->setRow(row);
|
cannam@45
|
75 m_handled.insert(id);
|
Chris@44
|
76 }
|
Chris@44
|
77
|
Chris@44
|
78 void
|
cannam@45
|
79 Grapher::layoutCol(QString id)
|
Chris@44
|
80 {
|
cannam@45
|
81 if (m_handled.contains(id)) {
|
cannam@45
|
82 std::cerr << "Already looked at " << id.toStdString() << std::endl;
|
cannam@45
|
83 return;
|
cannam@45
|
84 }
|
Chris@46
|
85 if (!m_changesets.contains(id)) {
|
cannam@45
|
86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
|
cannam@45
|
87 }
|
cannam@45
|
88 if (!m_items.contains(id)) {
|
cannam@45
|
89 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
|
cannam@45
|
90 }
|
Chris@46
|
91 Changeset *cs = m_changesets[id];
|
cannam@45
|
92 ChangesetItem *item = m_items[id];
|
cannam@45
|
93 std::cerr << "Looking at " << id.toStdString() << std::endl;
|
cannam@45
|
94
|
Chris@46
|
95 foreach (QString parentId, cs->parents()) {
|
Chris@46
|
96 if (!m_changesets.contains(parentId)) continue;
|
Chris@46
|
97 if (!m_handled.contains(parentId)) {
|
Chris@46
|
98 layoutCol(parentId);
|
Chris@46
|
99 }
|
Chris@46
|
100 }
|
Chris@46
|
101
|
Chris@47
|
102 // Parent may have layed out child in the recursive call
|
Chris@47
|
103 if (m_handled.contains(id)) {
|
Chris@47
|
104 std::cerr << "Looks like we've dealt with " << id.toStdString() << std::endl;
|
Chris@47
|
105 return;
|
Chris@47
|
106 }
|
Chris@47
|
107
|
cannam@45
|
108 int col = 0;
|
Chris@46
|
109 int row = item->row();
|
Chris@46
|
110 QString branch = cs->branch();
|
Chris@47
|
111
|
cannam@45
|
112 int nparents = cs->parents().size();
|
Chris@46
|
113 QString parentId;
|
Chris@46
|
114 int parentsOnSameBranch = 0;
|
cannam@45
|
115
|
Chris@46
|
116 switch (nparents) {
|
cannam@45
|
117
|
Chris@46
|
118 case 0:
|
Chris@46
|
119 col = m_branchHomes[cs->branch()];
|
Chris@46
|
120 col = findAvailableColumn(row, col, true);
|
Chris@46
|
121 break;
|
cannam@45
|
122
|
Chris@46
|
123 case 1:
|
Chris@46
|
124 parentId = cs->parents()[0];
|
cannam@45
|
125
|
Chris@46
|
126 if (!m_changesets.contains(parentId) ||
|
Chris@46
|
127 m_changesets[parentId]->branch() != branch) {
|
Chris@46
|
128 // new branch
|
Chris@46
|
129 col = m_branchHomes[branch];
|
Chris@46
|
130 } else {
|
Chris@46
|
131 col = m_items[parentId]->column();
|
Chris@44
|
132 }
|
cannam@45
|
133
|
Chris@46
|
134 col = findAvailableColumn(row, col, true);
|
Chris@46
|
135 break;
|
Chris@46
|
136
|
Chris@46
|
137 case 2:
|
Chris@46
|
138 // a merge: behave differently if parents are both on the same
|
Chris@46
|
139 // branch (we also want to behave differently for nodes that
|
Chris@46
|
140 // have multiple children on the same branch -- spreading them
|
Chris@46
|
141 // out rather than having affinity to a specific branch)
|
Chris@46
|
142
|
Chris@46
|
143 foreach (QString parentId, cs->parents()) {
|
Chris@46
|
144 if (!m_changesets.contains(parentId)) continue;
|
Chris@46
|
145 if (m_changesets[parentId]->branch() == branch) {
|
Chris@46
|
146 ChangesetItem *parentItem = m_items[parentId];
|
Chris@46
|
147 col += parentItem->column();
|
Chris@46
|
148 parentsOnSameBranch++;
|
Chris@46
|
149 }
|
Chris@46
|
150 }
|
Chris@46
|
151
|
Chris@46
|
152 if (parentsOnSameBranch > 0) {
|
Chris@46
|
153 col /= parentsOnSameBranch;
|
Chris@46
|
154 col = findAvailableColumn(item->row(), col, true);
|
Chris@46
|
155 } else {
|
Chris@46
|
156 col = findAvailableColumn(item->row(), col, false);
|
Chris@46
|
157 }
|
Chris@46
|
158 break;
|
Chris@44
|
159 }
|
Chris@44
|
160
|
cannam@45
|
161 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
|
cannam@45
|
162
|
Chris@46
|
163 m_alloc[row].insert(col);
|
cannam@45
|
164 item->setColumn(col);
|
cannam@45
|
165 m_handled.insert(id);
|
Chris@47
|
166
|
Chris@47
|
167 int nchildren = cs->children().size();
|
Chris@49
|
168
|
Chris@49
|
169 // look for merging children and make sure nobody
|
Chris@49
|
170 // is going to overwrite their "merge lines"
|
Chris@49
|
171 foreach (QString childId, cs->children()) {
|
Chris@49
|
172 if (!m_changesets.contains(childId)) continue;
|
Chris@49
|
173 Changeset *child = m_changesets[childId];
|
Chris@49
|
174 if (child->parents().size() > 1) {
|
Chris@49
|
175 int childRow = m_items[childId]->row();
|
Chris@49
|
176 std::cerr << "I'm at " << row << ", child with >1 parents is at " << childRow << std::endl;
|
Chris@49
|
177 for (int r = row; r >= childRow; --r) {
|
Chris@49
|
178 std::cerr << "setting row " << r << ", col " << col << std::endl;
|
Chris@49
|
179 m_alloc[r].insert(col);
|
Chris@49
|
180 }
|
Chris@49
|
181 }
|
Chris@49
|
182 }
|
Chris@49
|
183
|
Chris@47
|
184 if (nchildren > 1) {
|
Chris@47
|
185 // Normally the children will lay out themselves. We just
|
Chris@47
|
186 // want to handle the case where exactly two children have the
|
Chris@47
|
187 // same branch as us, because we can handle that neatly
|
Chris@47
|
188 QList<QString> special;
|
Chris@47
|
189 foreach (QString childId, cs->children()) {
|
Chris@47
|
190 if (!m_changesets.contains(childId)) continue;
|
Chris@47
|
191 Changeset *child = m_changesets[childId];
|
Chris@47
|
192 if (child->branch() == branch &&
|
Chris@47
|
193 child->parents().size() == 1) {
|
Chris@47
|
194 special.push_back(childId);
|
Chris@47
|
195 }
|
Chris@47
|
196 }
|
Chris@47
|
197 if (special.size() == 2) {
|
Chris@47
|
198 m_items[special[0]]->setColumn
|
Chris@47
|
199 (findAvailableColumn(item->row() - 1, col - 1, true));
|
Chris@47
|
200 m_items[special[1]]->setColumn
|
Chris@47
|
201 (findAvailableColumn(item->row() - 1, col + 1, true));
|
Chris@47
|
202 m_handled.insert(special[0]);
|
Chris@47
|
203 m_handled.insert(special[1]);
|
Chris@47
|
204 }
|
Chris@47
|
205 }
|
cannam@45
|
206 }
|
cannam@45
|
207
|
Chris@46
|
208 bool
|
Chris@46
|
209 Grapher::rangesConflict(const Range &r1, const Range &r2)
|
Chris@46
|
210 {
|
Chris@46
|
211 // allow some additional space at edges. we really ought also to
|
Chris@46
|
212 // permit space at the end of a branch up to the point where the
|
Chris@46
|
213 // merge happens
|
Chris@46
|
214 int a1 = r1.first - 2, b1 = r1.second + 2;
|
Chris@46
|
215 int a2 = r2.first - 2, b2 = r2.second + 2;
|
Chris@46
|
216 if (a1 > b2 || b1 < a2) return false;
|
Chris@46
|
217 if (a2 > b1 || b2 < a1) return false;
|
Chris@46
|
218 return true;
|
Chris@46
|
219 }
|
Chris@46
|
220
|
Chris@46
|
221 void
|
Chris@46
|
222 Grapher::allocateBranchHomes(Changesets csets)
|
Chris@46
|
223 {
|
Chris@46
|
224 foreach (Changeset *cs, csets) {
|
Chris@46
|
225 QString branch = cs->branch();
|
Chris@46
|
226 ChangesetItem *item = m_items[cs->id()];
|
Chris@46
|
227 if (!item) continue;
|
Chris@46
|
228 int row = item->row();
|
Chris@46
|
229 if (!m_branchRanges.contains(branch)) {
|
Chris@46
|
230 m_branchRanges[branch] = Range(row, row);
|
Chris@46
|
231 } else {
|
Chris@46
|
232 Range p = m_branchRanges[branch];
|
Chris@46
|
233 if (row < p.first) p.first = row;
|
Chris@46
|
234 if (row > p.second) p.second = row;
|
Chris@46
|
235 m_branchRanges[branch] = p;
|
Chris@46
|
236 }
|
Chris@46
|
237 }
|
Chris@46
|
238
|
Chris@46
|
239 m_branchHomes[""] = 0;
|
Chris@46
|
240
|
Chris@46
|
241 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@46
|
242 if (branch == "") continue;
|
Chris@46
|
243 QSet<int> taken;
|
Chris@46
|
244 taken.insert(0);
|
Chris@46
|
245 Range myRange = m_branchRanges[branch];
|
Chris@46
|
246 foreach (QString other, m_branchRanges.keys()) {
|
Chris@46
|
247 if (other == branch || other == "") continue;
|
Chris@46
|
248 Range otherRange = m_branchRanges[other];
|
Chris@46
|
249 if (rangesConflict(myRange, otherRange)) {
|
Chris@46
|
250 if (m_branchHomes.contains(other)) {
|
Chris@46
|
251 taken.insert(m_branchHomes[other]);
|
Chris@46
|
252 }
|
Chris@46
|
253 }
|
Chris@46
|
254 }
|
Chris@47
|
255 int home = 3;
|
Chris@46
|
256 while (taken.contains(home)) {
|
Chris@46
|
257 if (home > 0) home = -home;
|
Chris@47
|
258 else home = -(home-3);
|
Chris@46
|
259 }
|
Chris@46
|
260 m_branchHomes[branch] = home;
|
Chris@46
|
261 }
|
Chris@46
|
262
|
Chris@46
|
263 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@46
|
264 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl;
|
Chris@46
|
265 }
|
Chris@46
|
266 }
|
Chris@46
|
267
|
cannam@45
|
268 void
|
cannam@45
|
269 Grapher::layout(Changesets csets)
|
cannam@45
|
270 {
|
Chris@46
|
271 m_changesets.clear();
|
cannam@45
|
272 m_items.clear();
|
cannam@45
|
273 m_alloc.clear();
|
Chris@46
|
274 m_branchHomes.clear();
|
cannam@45
|
275
|
Chris@44
|
276 foreach (Changeset *cs, csets) {
|
cannam@45
|
277
|
cannam@45
|
278 QString id = cs->id();
|
cannam@45
|
279 std::cerr << id.toStdString() << std::endl;
|
cannam@45
|
280
|
cannam@45
|
281 if (id == "") {
|
cannam@45
|
282 throw LayoutException("Changeset has no ID");
|
cannam@45
|
283 }
|
Chris@46
|
284 if (m_changesets.contains(id)) {
|
cannam@45
|
285 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
|
cannam@45
|
286 }
|
cannam@45
|
287
|
Chris@46
|
288 m_changesets[id] = cs;
|
cannam@45
|
289
|
cannam@45
|
290 ChangesetItem *item = new ChangesetItem(cs);
|
cannam@45
|
291 item->setX(0);
|
cannam@45
|
292 item->setY(0);
|
cannam@45
|
293 m_items[id] = item;
|
cannam@45
|
294 m_scene->addItem(item);
|
cannam@45
|
295 }
|
cannam@45
|
296
|
Chris@46
|
297 foreach (Changeset *cs, csets) {
|
Chris@46
|
298 QString id = cs->id();
|
Chris@46
|
299 ChangesetItem *item = m_items[id];
|
Chris@46
|
300 foreach (QString parentId, cs->parents()) {
|
Chris@47
|
301 if (!m_changesets.contains(parentId)) continue;
|
Chris@47
|
302 Changeset *parent = m_changesets[parentId];
|
Chris@47
|
303 parent->addChild(id);
|
Chris@46
|
304 ConnectionItem *conn = new ConnectionItem();
|
Chris@46
|
305 conn->setChild(item);
|
Chris@46
|
306 conn->setParent(m_items[parentId]);
|
Chris@46
|
307 m_scene->addItem(conn);
|
Chris@46
|
308 }
|
Chris@46
|
309 }
|
Chris@46
|
310
|
cannam@45
|
311 // Layout in reverse order, i.e. forward chronological order.
|
cannam@45
|
312 // This ensures that parents will normally be laid out before
|
cannam@45
|
313 // their children -- though we can recurse from layout() if we
|
cannam@45
|
314 // find any weird exceptions
|
cannam@45
|
315 m_handled.clear();
|
cannam@45
|
316 for (int i = csets.size() - 1; i >= 0; --i) {
|
cannam@45
|
317 layoutRow(csets[i]->id());
|
cannam@45
|
318 }
|
Chris@46
|
319
|
Chris@46
|
320 allocateBranchHomes(csets);
|
Chris@46
|
321
|
cannam@45
|
322 m_handled.clear();
|
cannam@45
|
323 for (int i = csets.size() - 1; i >= 0; --i) {
|
cannam@45
|
324 layoutCol(csets[i]->id());
|
cannam@45
|
325 }
|
Chris@44
|
326 }
|
Chris@44
|
327
|