Chris@57
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@57
|
2
|
Chris@57
|
3 /*
|
Chris@57
|
4 EasyMercurial
|
Chris@57
|
5
|
Chris@57
|
6 Based on HgExplorer by Jari Korhonen
|
Chris@57
|
7 Copyright (c) 2010 Jari Korhonen
|
Chris@57
|
8 Copyright (c) 2010 Chris Cannam
|
Chris@57
|
9 Copyright (c) 2010 Queen Mary, University of London
|
Chris@57
|
10
|
Chris@57
|
11 This program is free software; you can redistribute it and/or
|
Chris@57
|
12 modify it under the terms of the GNU General Public License as
|
Chris@57
|
13 published by the Free Software Foundation; either version 2 of the
|
Chris@57
|
14 License, or (at your option) any later version. See the file
|
Chris@57
|
15 COPYING included with this distribution for more information.
|
Chris@57
|
16 */
|
Chris@44
|
17
|
Chris@44
|
18 #include "grapher.h"
|
Chris@46
|
19 #include "connectionitem.h"
|
Chris@53
|
20 #include "dateitem.h"
|
Chris@114
|
21 #include "debug.h"
|
Chris@119
|
22 #include "changesetscene.h"
|
Chris@44
|
23
|
Chris@44
|
24 #include <iostream>
|
Chris@44
|
25
|
Chris@106
|
26 int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
|
cannam@45
|
27 {
|
cannam@45
|
28 int col = parent;
|
cannam@45
|
29 if (preferParentCol) {
|
Chris@145
|
30 if (isAvailable(row, col)) return col;
|
cannam@45
|
31 }
|
cannam@45
|
32 while (col > 0) {
|
Chris@145
|
33 if (isAvailable(row, --col)) return col;
|
cannam@45
|
34 }
|
cannam@45
|
35 while (col < 0) {
|
Chris@145
|
36 if (isAvailable(row, ++col)) return col;
|
cannam@45
|
37 }
|
cannam@45
|
38 col = parent;
|
cannam@45
|
39 int sign = (col < 0 ? -1 : 1);
|
cannam@45
|
40 while (1) {
|
Chris@106
|
41 col += sign;
|
Chris@145
|
42 if (isAvailable(row, col)) return col;
|
cannam@45
|
43 }
|
cannam@45
|
44 }
|
Chris@44
|
45
|
Chris@145
|
46 bool Grapher::isAvailable(int row, int col)
|
Chris@145
|
47 {
|
Chris@145
|
48 if (m_alloc.contains(row) && m_alloc[row].contains(col)) return false;
|
Chris@145
|
49 if (!m_haveAllocatedUncommittedColumn) return true;
|
Chris@145
|
50 if (!m_uncommitted) return true;
|
Chris@145
|
51 return !(row <= m_uncommittedParentRow && col == m_uncommitted->column());
|
Chris@145
|
52 }
|
Chris@145
|
53
|
Chris@106
|
54 void Grapher::layoutRow(QString id)
|
Chris@44
|
55 {
|
cannam@45
|
56 if (m_handled.contains(id)) {
|
Chris@106
|
57 return;
|
Chris@44
|
58 }
|
Chris@46
|
59 if (!m_changesets.contains(id)) {
|
Chris@106
|
60 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
|
Chris@44
|
61 }
|
cannam@45
|
62 if (!m_items.contains(id)) {
|
Chris@106
|
63 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
|
Chris@44
|
64 }
|
Chris@46
|
65 Changeset *cs = m_changesets[id];
|
cannam@45
|
66 ChangesetItem *item = m_items[id];
|
Chris@114
|
67 DEBUG << "layoutRow: Looking at " << id.toStdString() << endl;
|
cannam@45
|
68
|
Chris@44
|
69 int row = 0;
|
cannam@45
|
70 int nparents = cs->parents().size();
|
cannam@45
|
71
|
cannam@45
|
72 if (nparents > 0) {
|
Chris@106
|
73 bool haveRow = false;
|
Chris@106
|
74 foreach (QString parentId, cs->parents()) {
|
cannam@45
|
75
|
Chris@106
|
76 if (!m_changesets.contains(parentId)) continue;
|
Chris@106
|
77 if (!m_items.contains(parentId)) continue;
|
cannam@45
|
78
|
Chris@106
|
79 if (!m_handled.contains(parentId)) {
|
Chris@106
|
80 layoutRow(parentId);
|
Chris@106
|
81 }
|
cannam@45
|
82
|
Chris@106
|
83 ChangesetItem *parentItem = m_items[parentId];
|
Chris@106
|
84 if (!haveRow || parentItem->row() < row) {
|
Chris@106
|
85 row = parentItem->row();
|
Chris@106
|
86 haveRow = true;
|
Chris@106
|
87 }
|
Chris@106
|
88 }
|
Chris@106
|
89 row = row - 1;
|
cannam@45
|
90 }
|
cannam@45
|
91
|
Chris@52
|
92 // row is now an upper bound on our eventual row (because we want
|
Chris@52
|
93 // to be above all parents). But we also want to ensure we are
|
Chris@52
|
94 // above all nodes that have earlier dates (to the nearest day).
|
Chris@52
|
95 // m_rowDates maps each row to a date: use that.
|
Chris@52
|
96
|
Chris@53
|
97 QString date = cs->age();
|
Chris@53
|
98 while (m_rowDates.contains(row) && m_rowDates[row] != date) {
|
Chris@106
|
99 --row;
|
Chris@52
|
100 }
|
Chris@52
|
101
|
Chris@52
|
102 // We have already laid out all nodes that have earlier timestamps
|
Chris@52
|
103 // than this one, so we know (among other things) that we can
|
Chris@52
|
104 // safely fill in this row has having this date, if it isn't in
|
Chris@52
|
105 // the map yet (it cannot have an earlier date)
|
Chris@52
|
106
|
Chris@52
|
107 if (!m_rowDates.contains(row)) {
|
Chris@106
|
108 m_rowDates[row] = date;
|
Chris@52
|
109 }
|
Chris@52
|
110
|
Chris@145
|
111 // If we're the parent of the uncommitted item, make a note of our
|
Chris@145
|
112 // row (we need it later, to avoid overwriting the connecting line)
|
Chris@145
|
113 if (m_uncommittedParentId == id) {
|
Chris@145
|
114 m_uncommittedParentRow = row;
|
Chris@145
|
115 }
|
Chris@145
|
116
|
Chris@114
|
117 DEBUG << "putting " << cs->id().toStdString() << " at row " << row
|
Chris@114
|
118 << endl;
|
cannam@45
|
119
|
Chris@44
|
120 item->setRow(row);
|
cannam@45
|
121 m_handled.insert(id);
|
Chris@44
|
122 }
|
Chris@44
|
123
|
Chris@106
|
124 void Grapher::layoutCol(QString id)
|
Chris@44
|
125 {
|
cannam@45
|
126 if (m_handled.contains(id)) {
|
Chris@114
|
127 DEBUG << "Already looked at " << id.toStdString() << endl;
|
Chris@106
|
128 return;
|
cannam@45
|
129 }
|
Chris@46
|
130 if (!m_changesets.contains(id)) {
|
Chris@106
|
131 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
|
cannam@45
|
132 }
|
cannam@45
|
133 if (!m_items.contains(id)) {
|
Chris@106
|
134 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
|
cannam@45
|
135 }
|
Chris@53
|
136
|
Chris@46
|
137 Changeset *cs = m_changesets[id];
|
Chris@122
|
138 DEBUG << "layoutCol: Looking at " << id.toStdString() << endl;
|
Chris@53
|
139
|
cannam@45
|
140 ChangesetItem *item = m_items[id];
|
Chris@47
|
141
|
cannam@45
|
142 int col = 0;
|
Chris@46
|
143 int row = item->row();
|
Chris@46
|
144 QString branch = cs->branch();
|
Chris@47
|
145
|
cannam@45
|
146 int nparents = cs->parents().size();
|
Chris@46
|
147 QString parentId;
|
Chris@46
|
148 int parentsOnSameBranch = 0;
|
cannam@45
|
149
|
Chris@46
|
150 switch (nparents) {
|
cannam@45
|
151
|
Chris@46
|
152 case 0:
|
Chris@106
|
153 col = m_branchHomes[cs->branch()];
|
Chris@106
|
154 col = findAvailableColumn(row, col, true);
|
Chris@106
|
155 break;
|
cannam@45
|
156
|
Chris@46
|
157 case 1:
|
Chris@106
|
158 parentId = cs->parents()[0];
|
cannam@45
|
159
|
Chris@106
|
160 if (!m_changesets.contains(parentId) ||
|
Chris@106
|
161 m_changesets[parentId]->branch() != branch) {
|
Chris@106
|
162 // new branch
|
Chris@106
|
163 col = m_branchHomes[branch];
|
Chris@106
|
164 } else {
|
Chris@106
|
165 col = m_items[parentId]->column();
|
Chris@106
|
166 }
|
cannam@45
|
167
|
Chris@106
|
168 col = findAvailableColumn(row, col, true);
|
Chris@106
|
169 break;
|
Chris@46
|
170
|
Chris@46
|
171 case 2:
|
Chris@106
|
172 // a merge: behave differently if parents are both on the same
|
Chris@106
|
173 // branch (we also want to behave differently for nodes that
|
Chris@106
|
174 // have multiple children on the same branch -- spreading them
|
Chris@106
|
175 // out rather than having affinity to a specific branch)
|
Chris@46
|
176
|
Chris@106
|
177 foreach (QString parentId, cs->parents()) {
|
Chris@106
|
178 if (!m_changesets.contains(parentId)) continue;
|
Chris@106
|
179 if (m_changesets[parentId]->branch() == branch) {
|
Chris@106
|
180 ChangesetItem *parentItem = m_items[parentId];
|
Chris@106
|
181 col += parentItem->column();
|
Chris@106
|
182 parentsOnSameBranch++;
|
Chris@106
|
183 }
|
Chris@106
|
184 }
|
Chris@46
|
185
|
Chris@106
|
186 if (parentsOnSameBranch > 0) {
|
Chris@106
|
187 col /= parentsOnSameBranch;
|
Chris@106
|
188 col = findAvailableColumn(item->row(), col, true);
|
Chris@106
|
189 } else {
|
Chris@106
|
190 col = findAvailableColumn(item->row(), col, false);
|
Chris@106
|
191 }
|
Chris@106
|
192 break;
|
Chris@44
|
193 }
|
Chris@44
|
194
|
Chris@122
|
195 DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl;
|
cannam@45
|
196
|
Chris@46
|
197 m_alloc[row].insert(col);
|
cannam@45
|
198 item->setColumn(col);
|
cannam@45
|
199 m_handled.insert(id);
|
Chris@47
|
200
|
Chris@145
|
201 // If we're the parent of the uncommitted item, it should be given
|
Chris@145
|
202 // the same column as us (ideally)
|
Chris@145
|
203
|
Chris@145
|
204 if (m_uncommittedParentId == id) {
|
Chris@145
|
205 int ucol = findAvailableColumn(row-1, col, true);
|
Chris@145
|
206 m_uncommitted->setColumn(ucol);
|
Chris@145
|
207 m_haveAllocatedUncommittedColumn = true;
|
Chris@145
|
208 }
|
Chris@145
|
209
|
Chris@51
|
210 // Normally the children will lay out themselves, but we can do
|
Chris@51
|
211 // a better job in some special cases:
|
Chris@51
|
212
|
Chris@47
|
213 int nchildren = cs->children().size();
|
Chris@49
|
214
|
Chris@53
|
215 // look for merging children and children distant from us but in a
|
Chris@53
|
216 // straight line, and make sure nobody is going to overwrite their
|
Chris@53
|
217 // connection lines
|
Chris@51
|
218
|
Chris@49
|
219 foreach (QString childId, cs->children()) {
|
Chris@139
|
220 DEBUG << "reserving connection line space" << endl;
|
Chris@49
|
221 if (!m_changesets.contains(childId)) continue;
|
Chris@49
|
222 Changeset *child = m_changesets[childId];
|
Chris@106
|
223 int childRow = m_items[childId]->row();
|
Chris@55
|
224 if (child->parents().size() > 1 ||
|
Chris@106
|
225 child->branch() == cs->branch()) {
|
Chris@55
|
226 for (int r = row-1; r > childRow; --r) {
|
Chris@49
|
227 m_alloc[r].insert(col);
|
Chris@49
|
228 }
|
Chris@106
|
229 }
|
Chris@49
|
230 }
|
Chris@49
|
231
|
Chris@51
|
232 // look for the case where exactly two children have the same
|
Chris@51
|
233 // branch as us: split them to a little either side of our position
|
Chris@51
|
234
|
Chris@47
|
235 if (nchildren > 1) {
|
Chris@106
|
236 QList<QString> special;
|
Chris@106
|
237 foreach (QString childId, cs->children()) {
|
Chris@106
|
238 if (!m_changesets.contains(childId)) continue;
|
Chris@106
|
239 Changeset *child = m_changesets[childId];
|
Chris@106
|
240 if (child->branch() == branch &&
|
Chris@106
|
241 child->parents().size() == 1) {
|
Chris@106
|
242 special.push_back(childId);
|
Chris@106
|
243 }
|
Chris@106
|
244 }
|
Chris@106
|
245 if (special.size() == 2) {
|
Chris@139
|
246 DEBUG << "handling split-in-two for children " << special[0] << " and " << special[1] << endl;
|
Chris@106
|
247 for (int i = 0; i < 2; ++i) {
|
Chris@106
|
248 int off = i * 2 - 1; // 0 -> -1, 1 -> 1
|
Chris@106
|
249 ChangesetItem *it = m_items[special[i]];
|
Chris@106
|
250 it->setColumn(findAvailableColumn(it->row(), col + off, true));
|
Chris@106
|
251 for (int r = row-1; r >= it->row(); --r) {
|
Chris@106
|
252 m_alloc[r].insert(it->column());
|
Chris@106
|
253 }
|
Chris@106
|
254 m_handled.insert(special[i]);
|
Chris@106
|
255 }
|
Chris@106
|
256 }
|
Chris@47
|
257 }
|
cannam@45
|
258 }
|
cannam@45
|
259
|
Chris@106
|
260 bool Grapher::rangesConflict(const Range &r1, const Range &r2)
|
Chris@46
|
261 {
|
Chris@46
|
262 // allow some additional space at edges. we really ought also to
|
Chris@46
|
263 // permit space at the end of a branch up to the point where the
|
Chris@46
|
264 // merge happens
|
Chris@46
|
265 int a1 = r1.first - 2, b1 = r1.second + 2;
|
Chris@46
|
266 int a2 = r2.first - 2, b2 = r2.second + 2;
|
Chris@46
|
267 if (a1 > b2 || b1 < a2) return false;
|
Chris@46
|
268 if (a2 > b1 || b2 < a1) return false;
|
Chris@46
|
269 return true;
|
Chris@46
|
270 }
|
Chris@46
|
271
|
Chris@106
|
272 void Grapher::allocateBranchHomes(Changesets csets)
|
Chris@46
|
273 {
|
Chris@46
|
274 foreach (Changeset *cs, csets) {
|
Chris@106
|
275 QString branch = cs->branch();
|
Chris@106
|
276 ChangesetItem *item = m_items[cs->id()];
|
Chris@106
|
277 if (!item) continue;
|
Chris@106
|
278 int row = item->row();
|
Chris@106
|
279 if (!m_branchRanges.contains(branch)) {
|
Chris@106
|
280 m_branchRanges[branch] = Range(row, row);
|
Chris@106
|
281 } else {
|
Chris@106
|
282 Range p = m_branchRanges[branch];
|
Chris@106
|
283 if (row < p.first) p.first = row;
|
Chris@106
|
284 if (row > p.second) p.second = row;
|
Chris@106
|
285 m_branchRanges[branch] = p;
|
Chris@106
|
286 }
|
Chris@46
|
287 }
|
Chris@46
|
288
|
Chris@46
|
289 m_branchHomes[""] = 0;
|
Chris@46
|
290
|
Chris@46
|
291 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@106
|
292 if (branch == "") continue;
|
Chris@106
|
293 QSet<int> taken;
|
Chris@106
|
294 taken.insert(0);
|
Chris@106
|
295 Range myRange = m_branchRanges[branch];
|
Chris@106
|
296 foreach (QString other, m_branchRanges.keys()) {
|
Chris@106
|
297 if (other == branch || other == "") continue;
|
Chris@106
|
298 Range otherRange = m_branchRanges[other];
|
Chris@106
|
299 if (rangesConflict(myRange, otherRange)) {
|
Chris@106
|
300 if (m_branchHomes.contains(other)) {
|
Chris@106
|
301 taken.insert(m_branchHomes[other]);
|
Chris@106
|
302 }
|
Chris@106
|
303 }
|
Chris@106
|
304 }
|
Chris@106
|
305 int home = 2;
|
Chris@106
|
306 while (taken.contains(home)) {
|
Chris@106
|
307 if (home > 0) {
|
Chris@106
|
308 if (home % 2 == 1) {
|
Chris@106
|
309 home = -home;
|
Chris@106
|
310 } else {
|
Chris@106
|
311 home = home + 1;
|
Chris@106
|
312 }
|
Chris@106
|
313 } else {
|
Chris@106
|
314 if ((-home) % 2 == 1) {
|
Chris@106
|
315 home = home + 1;
|
Chris@106
|
316 } else {
|
Chris@106
|
317 home = -(home-2);
|
Chris@106
|
318 }
|
Chris@106
|
319 }
|
Chris@106
|
320 }
|
Chris@106
|
321 m_branchHomes[branch] = home;
|
Chris@46
|
322 }
|
Chris@46
|
323
|
Chris@46
|
324 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@114
|
325 DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl;
|
Chris@46
|
326 }
|
Chris@46
|
327 }
|
Chris@46
|
328
|
Chris@52
|
329 static bool
|
Chris@145
|
330 compareChangesetsByDate(Changeset *const &a, Changeset *const &b)
|
Chris@52
|
331 {
|
Chris@52
|
332 return a->timestamp() < b->timestamp();
|
Chris@52
|
333 }
|
Chris@52
|
334
|
Chris@53
|
335 ChangesetItem *
|
Chris@145
|
336 Grapher::getItemFor(Changeset *cs)
|
Chris@53
|
337 {
|
Chris@53
|
338 if (!cs || !m_items.contains(cs->id())) return 0;
|
Chris@53
|
339 return m_items[cs->id()];
|
Chris@53
|
340 }
|
Chris@53
|
341
|
Chris@145
|
342 void Grapher::layout(Changesets csets, QString uncommittedSproutsFrom)
|
cannam@45
|
343 {
|
Chris@46
|
344 m_changesets.clear();
|
cannam@45
|
345 m_items.clear();
|
cannam@45
|
346 m_alloc.clear();
|
Chris@46
|
347 m_branchHomes.clear();
|
cannam@45
|
348
|
Chris@145
|
349 m_uncommittedParentId = uncommittedSproutsFrom;
|
Chris@145
|
350 m_haveAllocatedUncommittedColumn = false;
|
Chris@145
|
351 m_uncommittedParentRow = 0;
|
Chris@145
|
352 m_uncommitted = 0;
|
Chris@145
|
353
|
Chris@139
|
354 DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl;
|
Chris@139
|
355
|
Chris@53
|
356 if (csets.empty()) return;
|
Chris@53
|
357
|
Chris@145
|
358 // Create (but don't yet position) the changeset items
|
Chris@145
|
359
|
Chris@44
|
360 foreach (Changeset *cs, csets) {
|
cannam@45
|
361
|
Chris@106
|
362 QString id = cs->id();
|
cannam@45
|
363
|
Chris@106
|
364 if (id == "") {
|
Chris@106
|
365 throw LayoutException("Changeset has no ID");
|
Chris@106
|
366 }
|
Chris@106
|
367 if (m_changesets.contains(id)) {
|
Chris@145
|
368 DEBUG << "Duplicate changeset ID " << id
|
Chris@145
|
369 << " in Grapher::layout()" << endl;
|
Chris@106
|
370 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
|
Chris@106
|
371 }
|
cannam@45
|
372
|
Chris@106
|
373 m_changesets[id] = cs;
|
cannam@45
|
374
|
cannam@45
|
375 ChangesetItem *item = new ChangesetItem(cs);
|
cannam@45
|
376 item->setX(0);
|
cannam@45
|
377 item->setY(0);
|
Chris@106
|
378 m_items[id] = item;
|
Chris@141
|
379 m_scene->addChangesetItem(item);
|
cannam@45
|
380 }
|
cannam@45
|
381
|
Chris@74
|
382 // Add the connecting lines
|
Chris@74
|
383
|
Chris@46
|
384 foreach (Changeset *cs, csets) {
|
Chris@106
|
385 QString id = cs->id();
|
Chris@106
|
386 ChangesetItem *item = m_items[id];
|
Chris@106
|
387 bool merge = (cs->parents().size() > 1);
|
Chris@106
|
388 foreach (QString parentId, cs->parents()) {
|
Chris@106
|
389 if (!m_changesets.contains(parentId)) continue;
|
Chris@106
|
390 Changeset *parent = m_changesets[parentId];
|
Chris@106
|
391 parent->addChild(id);
|
Chris@106
|
392 ConnectionItem *conn = new ConnectionItem();
|
Chris@106
|
393 if (merge) conn->setConnectionType(ConnectionItem::Merge);
|
Chris@106
|
394 conn->setChild(item);
|
Chris@106
|
395 conn->setParent(m_items[parentId]);
|
Chris@106
|
396 m_scene->addItem(conn);
|
Chris@106
|
397 }
|
Chris@46
|
398 }
|
Chris@145
|
399
|
Chris@145
|
400 // Add uncommitted item and connecting line as necessary
|
Chris@145
|
401
|
Chris@145
|
402 if (m_uncommittedParentId != "") {
|
Chris@145
|
403 m_uncommitted = new UncommittedItem();
|
Chris@149
|
404 m_scene->addUncommittedItem(m_uncommitted);
|
Chris@145
|
405 ConnectionItem *conn = new ConnectionItem();
|
Chris@145
|
406 conn->setParent(m_items[m_uncommittedParentId]);
|
Chris@145
|
407 conn->setChild(m_uncommitted);
|
Chris@145
|
408 m_scene->addItem(conn);
|
Chris@145
|
409 }
|
Chris@46
|
410
|
Chris@74
|
411 // Add the branch labels
|
Chris@145
|
412
|
Chris@74
|
413 foreach (Changeset *cs, csets) {
|
Chris@74
|
414 QString id = cs->id();
|
Chris@74
|
415 ChangesetItem *item = m_items[id];
|
Chris@74
|
416 bool haveChildOnSameBranch = false;
|
Chris@74
|
417 foreach (QString childId, cs->children()) {
|
Chris@74
|
418 Changeset *child = m_changesets[childId];
|
Chris@74
|
419 if (child->branch() == cs->branch()) {
|
Chris@74
|
420 haveChildOnSameBranch = true;
|
Chris@74
|
421 break;
|
Chris@74
|
422 }
|
Chris@74
|
423 }
|
Chris@74
|
424 item->setShowBranch(!haveChildOnSameBranch);
|
Chris@74
|
425 }
|
Chris@74
|
426
|
Chris@52
|
427 // We need to lay out the changesets in forward chronological
|
Chris@52
|
428 // order. We have no guarantees about the order in which
|
Chris@52
|
429 // changesets appear in the list -- in a simple repository they
|
Chris@52
|
430 // will generally be reverse chronological, but that's far from
|
Chris@52
|
431 // guaranteed. So, sort explicitly using the date comparator
|
Chris@52
|
432 // above
|
Chris@51
|
433
|
Chris@52
|
434 qStableSort(csets.begin(), csets.end(), compareChangesetsByDate);
|
Chris@51
|
435
|
Chris@53
|
436 foreach (Changeset *cs, csets) {
|
Chris@145
|
437 DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp()
|
Chris@145
|
438 << ", date " << cs->datetime().toStdString() << endl;
|
Chris@53
|
439 }
|
Chris@53
|
440
|
cannam@45
|
441 m_handled.clear();
|
Chris@53
|
442 foreach (Changeset *cs, csets) {
|
Chris@106
|
443 layoutRow(cs->id());
|
cannam@45
|
444 }
|
Chris@46
|
445
|
Chris@46
|
446 allocateBranchHomes(csets);
|
Chris@46
|
447
|
cannam@45
|
448 m_handled.clear();
|
Chris@53
|
449 foreach (Changeset *cs, csets) {
|
Chris@106
|
450 foreach (QString parentId, cs->parents()) {
|
Chris@106
|
451 if (!m_handled.contains(parentId) &&
|
Chris@106
|
452 m_changesets.contains(parentId)) {
|
Chris@106
|
453 layoutCol(parentId);
|
Chris@106
|
454 }
|
Chris@106
|
455 }
|
Chris@106
|
456 layoutCol(cs->id());
|
Chris@53
|
457 }
|
Chris@53
|
458
|
Chris@145
|
459 // Find row and column extents. We know that 0 is an upper bound
|
Chris@145
|
460 // on row, and that mincol must be <= 0 and maxcol >= 0, so these
|
Chris@145
|
461 // initial values are good
|
Chris@55
|
462
|
Chris@53
|
463 int minrow = 0, maxrow = 0;
|
Chris@53
|
464 int mincol = 0, maxcol = 0;
|
Chris@53
|
465
|
Chris@53
|
466 foreach (int r, m_alloc.keys()) {
|
Chris@106
|
467 if (r < minrow) minrow = r;
|
Chris@106
|
468 if (r > maxrow) maxrow = r;
|
Chris@106
|
469 ColumnSet &c = m_alloc[r];
|
Chris@106
|
470 foreach (int i, c) {
|
Chris@106
|
471 if (i < mincol) mincol = i;
|
Chris@106
|
472 if (i > maxcol) maxcol = i;
|
Chris@106
|
473 }
|
Chris@53
|
474 }
|
Chris@53
|
475
|
Chris@145
|
476 // We've given the uncommitted item a column, but not a row yet --
|
Chris@145
|
477 // it always goes at the top
|
Chris@145
|
478
|
Chris@145
|
479 if (m_uncommitted) {
|
Chris@145
|
480 --minrow;
|
Chris@145
|
481 DEBUG << "putting uncommitted item at row " << minrow << endl;
|
Chris@145
|
482 m_uncommitted->setRow(minrow);
|
Chris@145
|
483 }
|
Chris@145
|
484
|
Chris@145
|
485 // Changeset items that have nothing to either side of them can be
|
Chris@145
|
486 // made double-width
|
Chris@145
|
487
|
Chris@145
|
488 foreach (Changeset *cs, csets) {
|
Chris@145
|
489 ChangesetItem *item = m_items[cs->id()];
|
Chris@145
|
490 if (isAvailable(item->row(), item->column()-1) &&
|
Chris@145
|
491 isAvailable(item->row(), item->column()+1)) {
|
Chris@145
|
492 item->setWide(true);
|
Chris@145
|
493 }
|
Chris@145
|
494 }
|
Chris@145
|
495
|
Chris@145
|
496 if (m_uncommitted) {
|
Chris@145
|
497 if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) &&
|
Chris@145
|
498 isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) {
|
Chris@145
|
499 m_uncommitted->setWide(true);
|
Chris@145
|
500 }
|
Chris@145
|
501 }
|
Chris@145
|
502
|
Chris@53
|
503 QString prevDate;
|
Chris@53
|
504 int changeRow = 0;
|
Chris@53
|
505
|
Chris@53
|
506 bool even = false;
|
Chris@53
|
507 int n = 0;
|
Chris@53
|
508
|
Chris@145
|
509 if (mincol == maxcol) {
|
Chris@145
|
510 --mincol;
|
Chris@145
|
511 ++maxcol;
|
Chris@145
|
512 }
|
Chris@145
|
513
|
Chris@53
|
514 for (int row = minrow; row <= maxrow; ++row) {
|
Chris@53
|
515
|
Chris@106
|
516 QString date = m_rowDates[row];
|
Chris@106
|
517 n++;
|
Chris@106
|
518
|
Chris@106
|
519 if (date != prevDate) {
|
Chris@106
|
520 if (prevDate != "") {
|
Chris@106
|
521 DateItem *item = new DateItem();
|
Chris@106
|
522 item->setDateString(prevDate);
|
Chris@106
|
523 item->setCols(mincol, maxcol - mincol + 1);
|
Chris@106
|
524 item->setRows(changeRow, n);
|
Chris@106
|
525 item->setEven(even);
|
Chris@106
|
526 item->setZValue(-1);
|
Chris@106
|
527 m_scene->addItem(item);
|
Chris@106
|
528 even = !even;
|
Chris@106
|
529 }
|
Chris@106
|
530 prevDate = date;
|
Chris@106
|
531 changeRow = row;
|
Chris@106
|
532 n = 0;
|
Chris@106
|
533 }
|
Chris@53
|
534 }
|
Chris@53
|
535
|
Chris@53
|
536 if (n > 0) {
|
Chris@106
|
537 DateItem *item = new DateItem();
|
Chris@106
|
538 item->setDateString(prevDate);
|
Chris@106
|
539 item->setCols(mincol, maxcol - mincol + 1);
|
Chris@106
|
540 item->setRows(changeRow, n+1);
|
Chris@106
|
541 item->setEven(even);
|
Chris@106
|
542 item->setZValue(-1);
|
Chris@106
|
543 m_scene->addItem(item);
|
Chris@106
|
544 even = !even;
|
cannam@45
|
545 }
|
Chris@44
|
546 }
|
Chris@44
|
547
|