comparison grapher.cpp @ 145:644bd31e8301

* Include the uncommitted item in general graph layout (in case it is not at the head, when other items will need to avoid it)
author Chris Cannam
date Wed, 01 Dec 2010 17:41:14 +0000
parents f61f032b06f9
children 38faf16df9b6
comparison
equal deleted inserted replaced
143:f61f032b06f9 145:644bd31e8301
25 25
26 int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol) 26 int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
27 { 27 {
28 int col = parent; 28 int col = parent;
29 if (preferParentCol) { 29 if (preferParentCol) {
30 if (!m_alloc[row].contains(col)) { 30 if (isAvailable(row, col)) return col;
31 return col;
32 }
33 } 31 }
34 while (col > 0) { 32 while (col > 0) {
35 if (!m_alloc[row].contains(--col)) return col; 33 if (isAvailable(row, --col)) return col;
36 } 34 }
37 while (col < 0) { 35 while (col < 0) {
38 if (!m_alloc[row].contains(++col)) return col; 36 if (isAvailable(row, ++col)) return col;
39 } 37 }
40 col = parent; 38 col = parent;
41 int sign = (col < 0 ? -1 : 1); 39 int sign = (col < 0 ? -1 : 1);
42 while (1) { 40 while (1) {
43 col += sign; 41 col += sign;
44 if (!m_alloc[row].contains(col)) return col; 42 if (isAvailable(row, col)) return col;
45 } 43 }
44 }
45
46 bool Grapher::isAvailable(int row, int col)
47 {
48 if (m_alloc.contains(row) && m_alloc[row].contains(col)) return false;
49 if (!m_haveAllocatedUncommittedColumn) return true;
50 if (!m_uncommitted) return true;
51 return !(row <= m_uncommittedParentRow && col == m_uncommitted->column());
46 } 52 }
47 53
48 void Grapher::layoutRow(QString id) 54 void Grapher::layoutRow(QString id)
49 { 55 {
50 if (m_handled.contains(id)) { 56 if (m_handled.contains(id)) {
98 // safely fill in this row has having this date, if it isn't in 104 // safely fill in this row has having this date, if it isn't in
99 // the map yet (it cannot have an earlier date) 105 // the map yet (it cannot have an earlier date)
100 106
101 if (!m_rowDates.contains(row)) { 107 if (!m_rowDates.contains(row)) {
102 m_rowDates[row] = date; 108 m_rowDates[row] = date;
109 }
110
111 // If we're the parent of the uncommitted item, make a note of our
112 // row (we need it later, to avoid overwriting the connecting line)
113 if (m_uncommittedParentId == id) {
114 m_uncommittedParentRow = row;
103 } 115 }
104 116
105 DEBUG << "putting " << cs->id().toStdString() << " at row " << row 117 DEBUG << "putting " << cs->id().toStdString() << " at row " << row
106 << endl; 118 << endl;
107 119
183 DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl; 195 DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl;
184 196
185 m_alloc[row].insert(col); 197 m_alloc[row].insert(col);
186 item->setColumn(col); 198 item->setColumn(col);
187 m_handled.insert(id); 199 m_handled.insert(id);
200
201 // If we're the parent of the uncommitted item, it should be given
202 // the same column as us (ideally)
203
204 if (m_uncommittedParentId == id) {
205 int ucol = findAvailableColumn(row-1, col, true);
206 m_uncommitted->setColumn(ucol);
207 m_haveAllocatedUncommittedColumn = true;
208 }
188 209
189 // Normally the children will lay out themselves, but we can do 210 // Normally the children will lay out themselves, but we can do
190 // a better job in some special cases: 211 // a better job in some special cases:
191 212
192 int nchildren = cs->children().size(); 213 int nchildren = cs->children().size();
304 DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl; 325 DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl;
305 } 326 }
306 } 327 }
307 328
308 static bool 329 static bool
309 compareChangesetsByDate(Changeset *const &a, Changeset *const &b) 330 compareChangesetsByDate(Changeset *const &a, Changeset *const &b)
310 { 331 {
311 return a->timestamp() < b->timestamp(); 332 return a->timestamp() < b->timestamp();
312 } 333 }
313 334
314 ChangesetItem * 335 ChangesetItem *
315 Grapher::getItemFor(Changeset *cs) 336 Grapher::getItemFor(Changeset *cs)
316 { 337 {
317 if (!cs || !m_items.contains(cs->id())) return 0; 338 if (!cs || !m_items.contains(cs->id())) return 0;
318 return m_items[cs->id()]; 339 return m_items[cs->id()];
319 } 340 }
320 341
321 void Grapher::layout(Changesets csets) 342 void Grapher::layout(Changesets csets, QString uncommittedSproutsFrom)
322 { 343 {
323 m_changesets.clear(); 344 m_changesets.clear();
324 m_items.clear(); 345 m_items.clear();
325 m_alloc.clear(); 346 m_alloc.clear();
326 m_branchHomes.clear(); 347 m_branchHomes.clear();
327 348
349 m_uncommittedParentId = uncommittedSproutsFrom;
350 m_haveAllocatedUncommittedColumn = false;
351 m_uncommittedParentRow = 0;
352 m_uncommitted = 0;
353
328 DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl; 354 DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl;
329 355
330 if (csets.empty()) return; 356 if (csets.empty()) return;
331 357
358 // Create (but don't yet position) the changeset items
359
332 foreach (Changeset *cs, csets) { 360 foreach (Changeset *cs, csets) {
333 361
334 QString id = cs->id(); 362 QString id = cs->id();
335 // DEBUG << id.toStdString() << endl;
336 363
337 if (id == "") { 364 if (id == "") {
338 throw LayoutException("Changeset has no ID"); 365 throw LayoutException("Changeset has no ID");
339 } 366 }
340 if (m_changesets.contains(id)) { 367 if (m_changesets.contains(id)) {
341 DEBUG << "Duplicate changeset ID " << id << " in Grapher::layout()" << endl; 368 DEBUG << "Duplicate changeset ID " << id
369 << " in Grapher::layout()" << endl;
342 throw LayoutException(QString("Duplicate changeset ID %1").arg(id)); 370 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
343 } 371 }
344 372
345 m_changesets[id] = cs; 373 m_changesets[id] = cs;
346 374
366 conn->setChild(item); 394 conn->setChild(item);
367 conn->setParent(m_items[parentId]); 395 conn->setParent(m_items[parentId]);
368 m_scene->addItem(conn); 396 m_scene->addItem(conn);
369 } 397 }
370 } 398 }
399
400 // Add uncommitted item and connecting line as necessary
401
402 if (m_uncommittedParentId != "") {
403 m_uncommitted = new UncommittedItem();
404 m_scene->addItem(m_uncommitted);
405 ConnectionItem *conn = new ConnectionItem();
406 conn->setParent(m_items[m_uncommittedParentId]);
407 conn->setChild(m_uncommitted);
408 m_scene->addItem(conn);
409 }
371 410
372 // Add the branch labels 411 // Add the branch labels
412
373 foreach (Changeset *cs, csets) { 413 foreach (Changeset *cs, csets) {
374 QString id = cs->id(); 414 QString id = cs->id();
375 ChangesetItem *item = m_items[id]; 415 ChangesetItem *item = m_items[id];
376 bool haveChildOnSameBranch = false; 416 bool haveChildOnSameBranch = false;
377 foreach (QString childId, cs->children()) { 417 foreach (QString childId, cs->children()) {
392 // above 432 // above
393 433
394 qStableSort(csets.begin(), csets.end(), compareChangesetsByDate); 434 qStableSort(csets.begin(), csets.end(), compareChangesetsByDate);
395 435
396 foreach (Changeset *cs, csets) { 436 foreach (Changeset *cs, csets) {
397 DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp() << ", date " << cs->datetime().toStdString() << endl; 437 DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp()
438 << ", date " << cs->datetime().toStdString() << endl;
398 } 439 }
399 440
400 m_handled.clear(); 441 m_handled.clear();
401 foreach (Changeset *cs, csets) { 442 foreach (Changeset *cs, csets) {
402 layoutRow(cs->id()); 443 layoutRow(cs->id());
413 } 454 }
414 } 455 }
415 layoutCol(cs->id()); 456 layoutCol(cs->id());
416 } 457 }
417 458
418 foreach (Changeset *cs, csets) { 459 // Find row and column extents. We know that 0 is an upper bound
419 ChangesetItem *item = m_items[cs->id()]; 460 // on row, and that mincol must be <= 0 and maxcol >= 0, so these
420 if (!m_alloc[item->row()].contains(item->column()-1) && 461 // initial values are good
421 !m_alloc[item->row()].contains(item->column()+1)) { 462
422 item->setWide(true);
423 }
424 }
425
426 // we know that 0 is an upper bound on row, and that mincol must
427 // be <= 0 and maxcol >= 0, so these initial values are good
428 int minrow = 0, maxrow = 0; 463 int minrow = 0, maxrow = 0;
429 int mincol = 0, maxcol = 0; 464 int mincol = 0, maxcol = 0;
430 465
431 foreach (int r, m_alloc.keys()) { 466 foreach (int r, m_alloc.keys()) {
432 if (r < minrow) minrow = r; 467 if (r < minrow) minrow = r;
436 if (i < mincol) mincol = i; 471 if (i < mincol) mincol = i;
437 if (i > maxcol) maxcol = i; 472 if (i > maxcol) maxcol = i;
438 } 473 }
439 } 474 }
440 475
476 // We've given the uncommitted item a column, but not a row yet --
477 // it always goes at the top
478
479 if (m_uncommitted) {
480 --minrow;
481 DEBUG << "putting uncommitted item at row " << minrow << endl;
482 m_uncommitted->setRow(minrow);
483 }
484
485 // Changeset items that have nothing to either side of them can be
486 // made double-width
487
488 foreach (Changeset *cs, csets) {
489 ChangesetItem *item = m_items[cs->id()];
490 if (isAvailable(item->row(), item->column()-1) &&
491 isAvailable(item->row(), item->column()+1)) {
492 item->setWide(true);
493 }
494 }
495
496 if (m_uncommitted) {
497 if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) &&
498 isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) {
499 m_uncommitted->setWide(true);
500 }
501 }
502
441 QString prevDate; 503 QString prevDate;
442 int changeRow = 0; 504 int changeRow = 0;
443 505
444 bool even = false; 506 bool even = false;
445 int n = 0; 507 int n = 0;
508
509 if (mincol == maxcol) {
510 --mincol;
511 ++maxcol;
512 }
446 513
447 for (int row = minrow; row <= maxrow; ++row) { 514 for (int row = minrow; row <= maxrow; ++row) {
448 515
449 QString date = m_rowDates[row]; 516 QString date = m_rowDates[row];
450 n++; 517 n++;