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
|
cannam@45
|
102 int col = 0;
|
Chris@46
|
103 int row = item->row();
|
Chris@46
|
104 QString branch = cs->branch();
|
cannam@45
|
105 int nparents = cs->parents().size();
|
Chris@46
|
106 QString parentId;
|
Chris@46
|
107 int parentsOnSameBranch = 0;
|
cannam@45
|
108
|
Chris@46
|
109 switch (nparents) {
|
cannam@45
|
110
|
Chris@46
|
111 case 0:
|
Chris@46
|
112 col = m_branchHomes[cs->branch()];
|
Chris@46
|
113 col = findAvailableColumn(row, col, true);
|
Chris@46
|
114 break;
|
cannam@45
|
115
|
Chris@46
|
116 case 1:
|
Chris@46
|
117 parentId = cs->parents()[0];
|
cannam@45
|
118
|
Chris@46
|
119 if (!m_changesets.contains(parentId) ||
|
Chris@46
|
120 m_changesets[parentId]->branch() != branch) {
|
Chris@46
|
121 // new branch
|
Chris@46
|
122 col = m_branchHomes[branch];
|
Chris@46
|
123 } else {
|
Chris@46
|
124 col = m_items[parentId]->column();
|
Chris@44
|
125 }
|
cannam@45
|
126
|
Chris@46
|
127 col = findAvailableColumn(row, col, true);
|
Chris@46
|
128 break;
|
Chris@46
|
129
|
Chris@46
|
130 case 2:
|
Chris@46
|
131 // a merge: behave differently if parents are both on the same
|
Chris@46
|
132 // branch (we also want to behave differently for nodes that
|
Chris@46
|
133 // have multiple children on the same branch -- spreading them
|
Chris@46
|
134 // out rather than having affinity to a specific branch)
|
Chris@46
|
135
|
Chris@46
|
136 foreach (QString parentId, cs->parents()) {
|
Chris@46
|
137 if (!m_changesets.contains(parentId)) continue;
|
Chris@46
|
138 if (m_changesets[parentId]->branch() == branch) {
|
Chris@46
|
139 ChangesetItem *parentItem = m_items[parentId];
|
Chris@46
|
140 col += parentItem->column();
|
Chris@46
|
141 parentsOnSameBranch++;
|
Chris@46
|
142 }
|
Chris@46
|
143 }
|
Chris@46
|
144
|
Chris@46
|
145 if (parentsOnSameBranch > 0) {
|
Chris@46
|
146 col /= parentsOnSameBranch;
|
Chris@46
|
147 col = findAvailableColumn(item->row(), col, true);
|
Chris@46
|
148 } else {
|
Chris@46
|
149 col = findAvailableColumn(item->row(), col, false);
|
Chris@46
|
150 }
|
Chris@46
|
151 break;
|
Chris@44
|
152 }
|
Chris@44
|
153
|
cannam@45
|
154 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
|
cannam@45
|
155
|
Chris@46
|
156 m_alloc[row].insert(col);
|
cannam@45
|
157 item->setColumn(col);
|
cannam@45
|
158 m_handled.insert(id);
|
cannam@45
|
159 }
|
cannam@45
|
160
|
Chris@46
|
161 bool
|
Chris@46
|
162 Grapher::rangesConflict(const Range &r1, const Range &r2)
|
Chris@46
|
163 {
|
Chris@46
|
164 // allow some additional space at edges. we really ought also to
|
Chris@46
|
165 // permit space at the end of a branch up to the point where the
|
Chris@46
|
166 // merge happens
|
Chris@46
|
167 int a1 = r1.first - 2, b1 = r1.second + 2;
|
Chris@46
|
168 int a2 = r2.first - 2, b2 = r2.second + 2;
|
Chris@46
|
169 if (a1 > b2 || b1 < a2) return false;
|
Chris@46
|
170 if (a2 > b1 || b2 < a1) return false;
|
Chris@46
|
171 return true;
|
Chris@46
|
172 }
|
Chris@46
|
173
|
Chris@46
|
174 void
|
Chris@46
|
175 Grapher::allocateBranchHomes(Changesets csets)
|
Chris@46
|
176 {
|
Chris@46
|
177 foreach (Changeset *cs, csets) {
|
Chris@46
|
178 QString branch = cs->branch();
|
Chris@46
|
179 ChangesetItem *item = m_items[cs->id()];
|
Chris@46
|
180 if (!item) continue;
|
Chris@46
|
181 int row = item->row();
|
Chris@46
|
182 if (!m_branchRanges.contains(branch)) {
|
Chris@46
|
183 m_branchRanges[branch] = Range(row, row);
|
Chris@46
|
184 } else {
|
Chris@46
|
185 Range p = m_branchRanges[branch];
|
Chris@46
|
186 if (row < p.first) p.first = row;
|
Chris@46
|
187 if (row > p.second) p.second = row;
|
Chris@46
|
188 m_branchRanges[branch] = p;
|
Chris@46
|
189 }
|
Chris@46
|
190 }
|
Chris@46
|
191
|
Chris@46
|
192 m_branchHomes[""] = 0;
|
Chris@46
|
193
|
Chris@46
|
194 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@46
|
195 if (branch == "") continue;
|
Chris@46
|
196 QSet<int> taken;
|
Chris@46
|
197 taken.insert(0);
|
Chris@46
|
198 Range myRange = m_branchRanges[branch];
|
Chris@46
|
199 foreach (QString other, m_branchRanges.keys()) {
|
Chris@46
|
200 if (other == branch || other == "") continue;
|
Chris@46
|
201 Range otherRange = m_branchRanges[other];
|
Chris@46
|
202 if (rangesConflict(myRange, otherRange)) {
|
Chris@46
|
203 if (m_branchHomes.contains(other)) {
|
Chris@46
|
204 taken.insert(m_branchHomes[other]);
|
Chris@46
|
205 }
|
Chris@46
|
206 }
|
Chris@46
|
207 }
|
Chris@46
|
208 int home = 2;
|
Chris@46
|
209 while (taken.contains(home)) {
|
Chris@46
|
210 if (home > 0) home = -home;
|
Chris@46
|
211 else home = -(home-2);
|
Chris@46
|
212 }
|
Chris@46
|
213 m_branchHomes[branch] = home;
|
Chris@46
|
214 }
|
Chris@46
|
215
|
Chris@46
|
216 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@46
|
217 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl;
|
Chris@46
|
218 }
|
Chris@46
|
219 }
|
Chris@46
|
220
|
cannam@45
|
221 void
|
cannam@45
|
222 Grapher::layout(Changesets csets)
|
cannam@45
|
223 {
|
Chris@46
|
224 m_changesets.clear();
|
cannam@45
|
225 m_items.clear();
|
cannam@45
|
226 m_alloc.clear();
|
Chris@46
|
227 m_branchHomes.clear();
|
cannam@45
|
228
|
Chris@44
|
229 foreach (Changeset *cs, csets) {
|
cannam@45
|
230
|
cannam@45
|
231 QString id = cs->id();
|
cannam@45
|
232 std::cerr << id.toStdString() << std::endl;
|
cannam@45
|
233
|
cannam@45
|
234 if (id == "") {
|
cannam@45
|
235 throw LayoutException("Changeset has no ID");
|
cannam@45
|
236 }
|
Chris@46
|
237 if (m_changesets.contains(id)) {
|
cannam@45
|
238 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
|
cannam@45
|
239 }
|
cannam@45
|
240
|
Chris@46
|
241 m_changesets[id] = cs;
|
cannam@45
|
242
|
cannam@45
|
243 ChangesetItem *item = new ChangesetItem(cs);
|
cannam@45
|
244 item->setX(0);
|
cannam@45
|
245 item->setY(0);
|
cannam@45
|
246 m_items[id] = item;
|
cannam@45
|
247 m_scene->addItem(item);
|
cannam@45
|
248 }
|
cannam@45
|
249
|
Chris@46
|
250 foreach (Changeset *cs, csets) {
|
Chris@46
|
251 QString id = cs->id();
|
Chris@46
|
252 ChangesetItem *item = m_items[id];
|
Chris@46
|
253 foreach (QString parentId, cs->parents()) {
|
Chris@46
|
254 if (!m_items.contains(parentId)) continue;
|
Chris@46
|
255 ConnectionItem *conn = new ConnectionItem();
|
Chris@46
|
256 conn->setChild(item);
|
Chris@46
|
257 conn->setParent(m_items[parentId]);
|
Chris@46
|
258 m_scene->addItem(conn);
|
Chris@46
|
259 }
|
Chris@46
|
260 }
|
Chris@46
|
261
|
cannam@45
|
262 // Layout in reverse order, i.e. forward chronological order.
|
cannam@45
|
263 // This ensures that parents will normally be laid out before
|
cannam@45
|
264 // their children -- though we can recurse from layout() if we
|
cannam@45
|
265 // find any weird exceptions
|
cannam@45
|
266 m_handled.clear();
|
cannam@45
|
267 for (int i = csets.size() - 1; i >= 0; --i) {
|
cannam@45
|
268 layoutRow(csets[i]->id());
|
cannam@45
|
269 }
|
Chris@46
|
270
|
Chris@46
|
271 allocateBranchHomes(csets);
|
Chris@46
|
272
|
cannam@45
|
273 m_handled.clear();
|
cannam@45
|
274 for (int i = csets.size() - 1; i >= 0; --i) {
|
cannam@45
|
275 layoutCol(csets[i]->id());
|
cannam@45
|
276 }
|
Chris@46
|
277 /*
|
cannam@45
|
278 foreach (Changeset *cs, csets) {
|
cannam@45
|
279 QString id = cs->id();
|
cannam@45
|
280 if (!m_items.contains(id)) continue;
|
cannam@45
|
281 ChangesetItem *me = m_items[id];
|
cannam@45
|
282 foreach (QString parentId, cs->parents()) {
|
cannam@45
|
283 if (!m_items.contains(parentId)) continue;
|
cannam@45
|
284 ChangesetItem *parent = m_items[parentId];
|
cannam@45
|
285 QGraphicsLineItem *line = new QGraphicsLineItem;
|
cannam@45
|
286 line->setLine(me->x() + 25, me->y() + 50,
|
cannam@45
|
287 parent->x() + 25, parent->y());
|
cannam@45
|
288 m_scene->addItem(line);
|
cannam@45
|
289 }
|
Chris@44
|
290 }
|
Chris@46
|
291 */
|
Chris@44
|
292 }
|
Chris@44
|
293
|