annotate src/grapher.cpp @ 703:bfd6014ee278

URL typo fix
author Chris Cannam
date Wed, 12 Dec 2018 10:30:21 +0000
parents 5b3bcb2d0943
children
rev   line source
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@644 8 Copyright (c) 2013 Chris Cannam
Chris@644 9 Copyright (c) 2013 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@114 20 #include "debug.h"
Chris@119 21 #include "changesetscene.h"
Chris@44 22
Chris@273 23 #include <QSettings>
Chris@273 24
Chris@44 25 #include <iostream>
Chris@44 26
Chris@691 27 //#define GRAPHER_VERBOSE_DEBUG 1
Chris@691 28
Chris@273 29 Grapher::Grapher(ChangesetScene *scene) :
Chris@273 30 m_scene(scene)
Chris@273 31 {
Chris@273 32 QSettings settings;
Chris@273 33 settings.beginGroup("Presentation");
Chris@273 34 m_showDates = (settings.value("dateformat", 0) == 1);
Chris@512 35 m_showClosedBranches = (settings.value("showclosedbranches", false).toBool());
Chris@273 36 }
Chris@273 37
Chris@268 38 int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
cannam@45 39 {
cannam@45 40 int col = parent;
cannam@45 41 if (preferParentCol) {
Chris@145 42 if (isAvailable(row, col)) return col;
cannam@45 43 }
cannam@45 44 while (col > 0) {
Chris@145 45 if (isAvailable(row, --col)) return col;
cannam@45 46 }
cannam@45 47 while (col < 0) {
Chris@145 48 if (isAvailable(row, ++col)) return col;
cannam@45 49 }
cannam@45 50 col = parent;
Chris@268 51 int sign = (col < 0 ? -1 : 1);
cannam@45 52 while (1) {
Chris@106 53 col += sign;
Chris@145 54 if (isAvailable(row, col)) return col;
cannam@45 55 }
cannam@45 56 }
Chris@44 57
Chris@145 58 bool Grapher::isAvailable(int row, int col)
Chris@145 59 {
Chris@145 60 if (m_alloc.contains(row) && m_alloc[row].contains(col)) return false;
Chris@145 61 if (!m_haveAllocatedUncommittedColumn) return true;
Chris@145 62 if (!m_uncommitted) return true;
Chris@145 63 return !(row <= m_uncommittedParentRow && col == m_uncommitted->column());
Chris@145 64 }
Chris@145 65
Chris@106 66 void Grapher::layoutRow(QString id)
Chris@44 67 {
cannam@45 68 if (m_handled.contains(id)) {
Chris@106 69 return;
Chris@44 70 }
Chris@46 71 if (!m_changesets.contains(id)) {
Chris@106 72 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
Chris@44 73 }
cannam@45 74 if (!m_items.contains(id)) {
Chris@512 75 return;
Chris@44 76 }
Chris@46 77 Changeset *cs = m_changesets[id];
cannam@45 78 ChangesetItem *item = m_items[id];
Chris@691 79 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@114 80 DEBUG << "layoutRow: Looking at " << id.toStdString() << endl;
Chris@691 81 #endif
cannam@45 82
Chris@44 83 int row = 0;
cannam@45 84 int nparents = cs->parents().size();
cannam@45 85
cannam@45 86 if (nparents > 0) {
Chris@106 87 bool haveRow = false;
Chris@106 88 foreach (QString parentId, cs->parents()) {
cannam@45 89
Chris@106 90 if (!m_changesets.contains(parentId)) continue;
Chris@106 91 if (!m_items.contains(parentId)) continue;
cannam@45 92
Chris@106 93 if (!m_handled.contains(parentId)) {
Chris@106 94 layoutRow(parentId);
Chris@106 95 }
cannam@45 96
Chris@106 97 ChangesetItem *parentItem = m_items[parentId];
Chris@106 98 if (!haveRow || parentItem->row() < row) {
Chris@106 99 row = parentItem->row();
Chris@106 100 haveRow = true;
Chris@106 101 }
Chris@106 102 }
Chris@106 103 row = row - 1;
cannam@45 104 }
cannam@45 105
Chris@52 106 // row is now an upper bound on our eventual row (because we want
Chris@52 107 // to be above all parents). But we also want to ensure we are
Chris@52 108 // above all nodes that have earlier dates (to the nearest day).
Chris@52 109 // m_rowDates maps each row to a date: use that.
Chris@52 110
Chris@273 111 QString date;
Chris@273 112 if (m_showDates) {
Chris@273 113 date = cs->date();
Chris@273 114 } else {
Chris@273 115 date = cs->age();
Chris@273 116 }
Chris@53 117 while (m_rowDates.contains(row) && m_rowDates[row] != date) {
Chris@106 118 --row;
Chris@52 119 }
Chris@52 120
Chris@52 121 // We have already laid out all nodes that have earlier timestamps
Chris@52 122 // than this one, so we know (among other things) that we can
Chris@52 123 // safely fill in this row has having this date, if it isn't in
Chris@52 124 // the map yet (it cannot have an earlier date)
Chris@52 125
Chris@52 126 if (!m_rowDates.contains(row)) {
Chris@106 127 m_rowDates[row] = date;
Chris@52 128 }
Chris@52 129
Chris@145 130 // If we're the parent of the uncommitted item, make a note of our
Chris@145 131 // row (we need it later, to avoid overwriting the connecting line)
Chris@153 132 if (!m_uncommittedParents.empty() && m_uncommittedParents[0] == id) {
Chris@145 133 m_uncommittedParentRow = row;
Chris@145 134 }
Chris@145 135
Chris@691 136 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@114 137 DEBUG << "putting " << cs->id().toStdString() << " at row " << row
Chris@114 138 << endl;
Chris@691 139 #endif
Chris@691 140
Chris@44 141 item->setRow(row);
cannam@45 142 m_handled.insert(id);
Chris@44 143 }
Chris@44 144
Chris@106 145 void Grapher::layoutCol(QString id)
Chris@44 146 {
cannam@45 147 if (m_handled.contains(id)) {
Chris@691 148 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@114 149 DEBUG << "Already looked at " << id.toStdString() << endl;
Chris@691 150 #endif
Chris@106 151 return;
cannam@45 152 }
Chris@46 153 if (!m_changesets.contains(id)) {
Chris@106 154 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
cannam@45 155 }
cannam@45 156 if (!m_items.contains(id)) {
Chris@512 157 return;
cannam@45 158 }
Chris@53 159
Chris@46 160 Changeset *cs = m_changesets[id];
Chris@691 161 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@122 162 DEBUG << "layoutCol: Looking at " << id.toStdString() << endl;
Chris@691 163 #endif
Chris@53 164
cannam@45 165 ChangesetItem *item = m_items[id];
Chris@47 166
cannam@45 167 int col = 0;
Chris@46 168 int row = item->row();
Chris@46 169 QString branch = cs->branch();
Chris@47 170
cannam@45 171 int nparents = cs->parents().size();
Chris@46 172 QString parentId;
Chris@46 173 int parentsOnSameBranch = 0;
cannam@45 174
Chris@46 175 switch (nparents) {
cannam@45 176
Chris@46 177 case 0:
Chris@268 178 col = m_branchHomes[cs->branch()];
Chris@268 179 col = findAvailableColumn(row, col, true);
Chris@106 180 break;
cannam@45 181
Chris@46 182 case 1:
Chris@106 183 parentId = cs->parents()[0];
cannam@45 184
Chris@106 185 if (!m_changesets.contains(parentId) ||
Chris@153 186 !m_changesets[parentId]->isOnBranch(branch)) {
Chris@106 187 // new branch
Chris@106 188 col = m_branchHomes[branch];
Chris@512 189 } else if (m_items.contains(parentId)) {
Chris@106 190 col = m_items[parentId]->column();
Chris@106 191 }
cannam@45 192
Chris@268 193 col = findAvailableColumn(row, col, true);
Chris@106 194 break;
Chris@46 195
Chris@46 196 case 2:
Chris@106 197 // a merge: behave differently if parents are both on the same
Chris@106 198 // branch (we also want to behave differently for nodes that
Chris@106 199 // have multiple children on the same branch -- spreading them
Chris@106 200 // out rather than having affinity to a specific branch)
Chris@46 201
Chris@106 202 foreach (QString parentId, cs->parents()) {
Chris@106 203 if (!m_changesets.contains(parentId)) continue;
Chris@153 204 if (m_changesets[parentId]->isOnBranch(branch)) {
Chris@512 205 if (m_items.contains(parentId)) {
Chris@512 206 ChangesetItem *parentItem = m_items[parentId];
Chris@512 207 col += parentItem->column();
Chris@512 208 parentsOnSameBranch++;
Chris@512 209 }
Chris@106 210 }
Chris@106 211 }
Chris@46 212
Chris@106 213 if (parentsOnSameBranch > 0) {
Chris@106 214 col /= parentsOnSameBranch;
Chris@268 215 col = findAvailableColumn(item->row(), col, true);
Chris@106 216 } else {
Chris@268 217 col = findAvailableColumn(item->row(), col, false);
Chris@106 218 }
Chris@106 219 break;
Chris@44 220 }
Chris@44 221
Chris@691 222 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@122 223 DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl;
Chris@691 224 #endif
Chris@691 225
Chris@46 226 m_alloc[row].insert(col);
cannam@45 227 item->setColumn(col);
cannam@45 228 m_handled.insert(id);
Chris@47 229
Chris@153 230 // If we're the first parent of the uncommitted item, it should be
Chris@153 231 // given the same column as us (we already noted that its
Chris@153 232 // connecting line would end at our row)
Chris@145 233
Chris@153 234 if (m_uncommittedParents.contains(id)) {
Chris@153 235 if (m_uncommittedParents[0] == id) {
Chris@268 236 int ucol = findAvailableColumn(row-1, col, true);
Chris@153 237 m_uncommitted->setColumn(ucol);
Chris@153 238 m_haveAllocatedUncommittedColumn = true;
Chris@153 239 }
Chris@153 240 // also, if the uncommitted item has a different branch from
Chris@153 241 // any of its parents, tell it to show the branch
Chris@153 242 if (!cs->isOnBranch(m_uncommitted->branch())) {
Chris@153 243 DEBUG << "Uncommitted branch " << m_uncommitted->branch()
Chris@153 244 << " differs from my branch " << cs->branch()
Chris@153 245 << ", asking it to show branch" << endl;
Chris@153 246 m_uncommitted->setShowBranch(true);
Chris@153 247 }
Chris@145 248 }
Chris@145 249
Chris@311 250
Chris@51 251 // Normally the children will lay out themselves, but we can do
Chris@51 252 // a better job in some special cases:
Chris@51 253
Chris@47 254 int nchildren = cs->children().size();
Chris@49 255
Chris@53 256 // look for merging children and children distant from us but in a
Chris@53 257 // straight line, and make sure nobody is going to overwrite their
Chris@53 258 // connection lines
Chris@51 259
Chris@49 260 foreach (QString childId, cs->children()) {
Chris@691 261 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@139 262 DEBUG << "reserving connection line space" << endl;
Chris@691 263 #endif
Chris@512 264 if (!m_items.contains(childId)) continue;
Chris@49 265 Changeset *child = m_changesets[childId];
Chris@106 266 int childRow = m_items[childId]->row();
Chris@55 267 if (child->parents().size() > 1 ||
Chris@153 268 child->isOnBranch(cs->branch())) {
Chris@55 269 for (int r = row-1; r > childRow; --r) {
Chris@49 270 m_alloc[r].insert(col);
Chris@49 271 }
Chris@106 272 }
Chris@49 273 }
Chris@49 274
Chris@51 275 // look for the case where exactly two children have the same
Chris@51 276 // branch as us: split them to a little either side of our position
Chris@51 277
Chris@47 278 if (nchildren > 1) {
Chris@106 279 QList<QString> special;
Chris@106 280 foreach (QString childId, cs->children()) {
Chris@512 281 if (!m_items.contains(childId)) continue;
Chris@106 282 Changeset *child = m_changesets[childId];
Chris@153 283 if (child->isOnBranch(branch) &&
Chris@106 284 child->parents().size() == 1) {
Chris@106 285 special.push_back(childId);
Chris@106 286 }
Chris@106 287 }
Chris@106 288 if (special.size() == 2) {
Chris@691 289 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@139 290 DEBUG << "handling split-in-two for children " << special[0] << " and " << special[1] << endl;
Chris@691 291 #endif
Chris@106 292 for (int i = 0; i < 2; ++i) {
Chris@106 293 int off = i * 2 - 1; // 0 -> -1, 1 -> 1
Chris@106 294 ChangesetItem *it = m_items[special[i]];
Chris@268 295 it->setColumn(findAvailableColumn(it->row(), col + off, true));
Chris@106 296 for (int r = row-1; r >= it->row(); --r) {
Chris@106 297 m_alloc[r].insert(it->column());
Chris@106 298 }
Chris@106 299 m_handled.insert(special[i]);
Chris@106 300 }
Chris@106 301 }
Chris@47 302 }
cannam@45 303 }
cannam@45 304
Chris@106 305 bool Grapher::rangesConflict(const Range &r1, const Range &r2)
Chris@46 306 {
Chris@46 307 // allow some additional space at edges. we really ought also to
Chris@46 308 // permit space at the end of a branch up to the point where the
Chris@46 309 // merge happens
Chris@46 310 int a1 = r1.first - 2, b1 = r1.second + 2;
Chris@46 311 int a2 = r2.first - 2, b2 = r2.second + 2;
Chris@46 312 if (a1 > b2 || b1 < a2) return false;
Chris@46 313 if (a2 > b1 || b2 < a1) return false;
Chris@46 314 return true;
Chris@46 315 }
Chris@46 316
Chris@106 317 void Grapher::allocateBranchHomes(Changesets csets)
Chris@46 318 {
Chris@46 319 foreach (Changeset *cs, csets) {
Chris@512 320 QString id = cs->id();
Chris@512 321 if (!m_items.contains(id)) continue;
Chris@512 322 ChangesetItem *item = m_items[id];
Chris@106 323 QString branch = cs->branch();
Chris@106 324 int row = item->row();
Chris@106 325 if (!m_branchRanges.contains(branch)) {
Chris@106 326 m_branchRanges[branch] = Range(row, row);
Chris@106 327 } else {
Chris@106 328 Range p = m_branchRanges[branch];
Chris@106 329 if (row < p.first) p.first = row;
Chris@106 330 if (row > p.second) p.second = row;
Chris@106 331 m_branchRanges[branch] = p;
Chris@106 332 }
Chris@46 333 }
Chris@46 334
Chris@46 335 m_branchHomes[""] = 0;
Chris@153 336 m_branchHomes["default"] = 0;
Chris@46 337
Chris@268 338 foreach (QString branch, m_branchRanges.keys()) {
Chris@106 339 if (branch == "") continue;
Chris@106 340 QSet<int> taken;
Chris@106 341 taken.insert(0);
Chris@106 342 Range myRange = m_branchRanges[branch];
Chris@106 343 foreach (QString other, m_branchRanges.keys()) {
Chris@106 344 if (other == branch || other == "") continue;
Chris@106 345 Range otherRange = m_branchRanges[other];
Chris@106 346 if (rangesConflict(myRange, otherRange)) {
Chris@106 347 if (m_branchHomes.contains(other)) {
Chris@106 348 taken.insert(m_branchHomes[other]);
Chris@106 349 }
Chris@106 350 }
Chris@106 351 }
Chris@106 352 int home = 2;
Chris@106 353 while (taken.contains(home)) {
Chris@268 354 if (home > 0) {
Chris@268 355 if (home % 2 == 1) {
Chris@268 356 home = -home;
Chris@268 357 } else {
Chris@268 358 home = home + 1;
Chris@268 359 }
Chris@268 360 } else {
Chris@268 361 if ((-home) % 2 == 1) {
Chris@268 362 home = home + 1;
Chris@268 363 } else {
Chris@268 364 home = -(home-2);
Chris@268 365 }
Chris@268 366 }
Chris@106 367 }
Chris@106 368 m_branchHomes[branch] = home;
Chris@46 369 }
Chris@46 370
Chris@691 371 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@268 372 foreach (QString branch, m_branchRanges.keys()) {
Chris@268 373 DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl;
Chris@46 374 }
Chris@691 375 #endif
Chris@46 376 }
Chris@46 377
Chris@52 378 static bool
Chris@145 379 compareChangesetsByDate(Changeset *const &a, Changeset *const &b)
Chris@52 380 {
Chris@52 381 return a->timestamp() < b->timestamp();
Chris@52 382 }
Chris@52 383
Chris@53 384 ChangesetItem *
Chris@145 385 Grapher::getItemFor(Changeset *cs)
Chris@53 386 {
Chris@371 387 if (!cs) return 0;
Chris@371 388 return getItemFor(cs->id());
Chris@371 389 }
Chris@371 390
Chris@371 391 ChangesetItem *
Chris@371 392 Grapher::getItemFor(QString id)
Chris@371 393 {
Chris@371 394 if (!m_items.contains(id)) return 0;
Chris@371 395 return m_items[id];
Chris@53 396 }
Chris@53 397
Chris@517 398 void
Chris@517 399 Grapher::markClosedChangesets()
Chris@517 400 {
Chris@517 401 // Ensure the closed branch changesets are all marked as closed.
Chris@517 402
Chris@517 403 QSet<QString> deferred;
Chris@517 404
Chris@517 405 foreach (QString id, m_closedIds) {
Chris@517 406 markClosedChangesetsFrom(id, deferred);
Chris@517 407 // std::cerr << "after closed id " << id << ": candidates now contains " << deferred.size() << " element(s)" << std::endl;
Chris@517 408 }
Chris@517 409
Chris@517 410 while (!deferred.empty()) {
Chris@517 411 foreach (QString id, deferred) {
Chris@517 412 markClosedChangesetsFrom(id, deferred);
Chris@517 413 deferred.remove(id);
Chris@517 414 // std::cerr << "after id " << id << ": deferred now contains " << deferred.size() << " element(s)" << std::endl;
Chris@517 415 }
Chris@517 416 }
Chris@517 417 }
Chris@517 418
Chris@517 419 void
Chris@517 420 Grapher::markClosedChangesetsFrom(QString id, QSet<QString> &deferred)
Chris@517 421 {
Chris@517 422 // A changeset should be marked as closed (i) if it is in the list
Chris@517 423 // of closed heads [and has no children]; or (ii) all of its
Chris@517 424 // children that have the same branch name as it are marked as
Chris@517 425 // closed [and there is at least one of those]
Chris@517 426
Chris@517 427 if (!m_changesets.contains(id)) {
Chris@517 428 // std::cerr << "no good" << std::endl;
Chris@517 429 return;
Chris@517 430 }
Chris@517 431
Chris@517 432 // std::cerr << "looking at id " << id << std::endl;
Chris@517 433
Chris@517 434 Changeset *cs = m_changesets[id];
Chris@517 435 QString branch = cs->branch();
Chris@517 436
Chris@517 437 bool closed = false;
Chris@517 438
Chris@517 439 if (m_closedIds.contains(id)) {
Chris@517 440
Chris@517 441 closed = true;
Chris@517 442
Chris@517 443 } else {
Chris@517 444
Chris@517 445 closed = false;
Chris@517 446 foreach (QString childId, cs->children()) {
Chris@517 447 if (!m_changesets.contains(childId)) {
Chris@517 448 continue;
Chris@517 449 }
Chris@517 450 Changeset *ccs = m_changesets[childId];
Chris@517 451 if (ccs->isOnBranch(branch)) {
Chris@517 452 if (ccs->closed()) {
Chris@517 453 // closed becomes true only when we see a closed
Chris@517 454 // child on the same branch
Chris@517 455 closed = true;
Chris@517 456 } else {
Chris@517 457 // and it becomes false as soon as we see any
Chris@517 458 // un-closed child on the same branch
Chris@517 459 closed = false;
Chris@517 460 break;
Chris@517 461 }
Chris@517 462 }
Chris@517 463 }
Chris@517 464 }
Chris@517 465
Chris@517 466 if (closed) {
Chris@517 467 // set closed on this cset and its direct simple parents
Chris@517 468 QString csid = id;
Chris@517 469 while (cs) {
Chris@517 470 cs->setClosed(true);
Chris@517 471 if (cs->parents().size() == 1) {
Chris@517 472 QString pid = cs->parents()[0];
Chris@517 473 if (!m_changesets.contains(pid)) break;
Chris@517 474 cs = m_changesets[pid];
Chris@517 475 if (cs->children().size() > 1) {
Chris@517 476 // std::cerr << "adding pid " << pid << " (it has more than one child)" << std::endl;
Chris@517 477 deferred.insert(pid); // examine later
Chris@517 478 cs = 0;
Chris@517 479 }
Chris@517 480 } else if (cs->parents().size() > 1) {
Chris@517 481 foreach (QString pid, cs->parents()) {
Chris@517 482 // std::cerr << "recursing to pid " << pid << " (it is one of multiple parents)" << std::endl;
Chris@517 483 markClosedChangesetsFrom(pid, deferred);
Chris@517 484 }
Chris@517 485 cs = 0;
Chris@517 486 } else {
Chris@517 487 cs = 0;
Chris@517 488 }
Chris@517 489 }
Chris@517 490 } else {
Chris@517 491 cs->setClosed(false);
Chris@517 492 }
Chris@517 493
Chris@517 494 // std::cerr << "finished with id " << id << std::endl;
Chris@517 495 }
Chris@517 496
Chris@153 497 void Grapher::layout(Changesets csets,
Chris@153 498 QStringList uncommittedParents,
Chris@153 499 QString uncommittedBranch)
cannam@45 500 {
Chris@46 501 m_changesets.clear();
cannam@45 502 m_items.clear();
cannam@45 503 m_alloc.clear();
Chris@46 504 m_branchHomes.clear();
cannam@45 505
Chris@153 506 m_uncommittedParents = uncommittedParents;
Chris@145 507 m_haveAllocatedUncommittedColumn = false;
Chris@145 508 m_uncommittedParentRow = 0;
Chris@145 509 m_uncommitted = 0;
Chris@145 510
Chris@139 511 DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl;
Chris@139 512
Chris@53 513 if (csets.empty()) return;
Chris@53 514
Chris@509 515 // Initialise changesets hash
Chris@145 516
Chris@44 517 foreach (Changeset *cs, csets) {
cannam@45 518
Chris@106 519 QString id = cs->id();
cannam@45 520
Chris@106 521 if (id == "") {
Chris@106 522 throw LayoutException("Changeset has no ID");
Chris@106 523 }
Chris@106 524 if (m_changesets.contains(id)) {
Chris@145 525 DEBUG << "Duplicate changeset ID " << id
Chris@145 526 << " in Grapher::layout()" << endl;
Chris@106 527 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
Chris@106 528 }
cannam@45 529
Chris@106 530 m_changesets[id] = cs;
Chris@509 531 }
Chris@509 532
Chris@509 533 // Set the children for each changeset
cannam@45 534
Chris@509 535 foreach (Changeset *cs, csets) {
Chris@509 536 QString id = cs->id();
Chris@509 537 foreach (QString parentId, cs->parents()) {
Chris@509 538 if (!m_changesets.contains(parentId)) continue;
Chris@509 539 Changeset *parent = m_changesets[parentId];
Chris@509 540 parent->addChild(id);
Chris@509 541 }
Chris@509 542 }
Chris@511 543
Chris@515 544 // Ensure the closed branch changesets are all marked as closed.
Chris@511 545
Chris@517 546 markClosedChangesets();
Chris@509 547
Chris@509 548 // Create (but don't yet position) the changeset items
Chris@509 549
Chris@509 550 foreach (Changeset *cs, csets) {
Chris@512 551 if (cs->closed() && !m_showClosedBranches) continue;
Chris@509 552 QString id = cs->id();
cannam@45 553 ChangesetItem *item = new ChangesetItem(cs);
cannam@45 554 item->setX(0);
cannam@45 555 item->setY(0);
Chris@250 556 item->setZValue(0);
Chris@106 557 m_items[id] = item;
Chris@141 558 m_scene->addChangesetItem(item);
cannam@45 559 }
Chris@145 560
Chris@511 561 // Ensure the closing changeset items are appropriately marked
Chris@506 562
Chris@506 563 foreach (QString closedId, m_closedIds) {
Chris@506 564 if (!m_items.contains(closedId)) continue;
Chris@511 565 m_items[closedId]->setClosingCommit(true);
Chris@506 566 }
Chris@506 567
Chris@509 568 // Add the connecting lines
Chris@509 569
Chris@509 570 foreach (Changeset *cs, csets) {
Chris@509 571 QString id = cs->id();
Chris@512 572 if (!m_items.contains(id)) continue;
Chris@509 573 ChangesetItem *item = m_items[id];
Chris@509 574 bool merge = (cs->parents().size() > 1);
Chris@509 575 foreach (QString parentId, cs->parents()) {
Chris@516 576 if (!m_changesets.contains(parentId)) continue;
Chris@509 577 ConnectionItem *conn = new ConnectionItem();
Chris@509 578 if (merge) conn->setConnectionType(ConnectionItem::Merge);
Chris@509 579 conn->setChild(item);
Chris@509 580 conn->setZValue(-1);
Chris@516 581 if (m_items.contains(parentId)) {
Chris@516 582 conn->setParent(m_items[parentId]);
Chris@516 583 } else {
Chris@516 584 conn->setMergedBranch(m_changesets[parentId]->branch());
Chris@516 585 }
Chris@509 586 m_scene->addItem(conn);
Chris@509 587 }
Chris@509 588 }
Chris@509 589
Chris@145 590 // Add uncommitted item and connecting line as necessary
Chris@145 591
Chris@153 592 if (!m_uncommittedParents.empty()) {
Chris@311 593
Chris@145 594 m_uncommitted = new UncommittedItem();
Chris@153 595 m_uncommitted->setBranch(uncommittedBranch);
Chris@250 596 m_uncommitted->setZValue(10);
Chris@149 597 m_scene->addUncommittedItem(m_uncommitted);
Chris@311 598
Chris@311 599 bool haveParentOnBranch = false;
Chris@153 600 foreach (QString p, m_uncommittedParents) {
Chris@512 601 if (!m_items.contains(p)) continue;
Chris@153 602 ConnectionItem *conn = new ConnectionItem();
Chris@153 603 conn->setConnectionType(ConnectionItem::Merge);
Chris@311 604 ChangesetItem *pitem = m_items[p];
Chris@311 605 conn->setParent(pitem);
Chris@153 606 conn->setChild(m_uncommitted);
Chris@388 607 conn->setZValue(-1);
Chris@153 608 m_scene->addItem(conn);
Chris@311 609 if (pitem) {
Chris@409 610 if (pitem->getChangeset()->isOnBranch(uncommittedBranch)) {
Chris@311 611 haveParentOnBranch = true;
Chris@311 612 }
Chris@311 613 }
Chris@153 614 }
Chris@311 615
Chris@311 616 // If the uncommitted item has no parents on the same branch,
Chris@311 617 // tell it it has a new branch (the "show branch" flag is set
Chris@311 618 // elsewhere for this item)
Chris@311 619 m_uncommitted->setIsNewBranch(!haveParentOnBranch);
Chris@399 620
Chris@399 621 // Uncommitted is a merge if it has more than one parent
Chris@399 622 m_uncommitted->setIsMerge(m_uncommittedParents.size() > 1);
Chris@145 623 }
Chris@46 624
Chris@74 625 // Add the branch labels
Chris@145 626
Chris@74 627 foreach (Changeset *cs, csets) {
Chris@74 628 QString id = cs->id();
Chris@512 629 if (!m_items.contains(id)) continue;
Chris@74 630 ChangesetItem *item = m_items[id];
Chris@74 631 bool haveChildOnSameBranch = false;
Chris@74 632 foreach (QString childId, cs->children()) {
Chris@74 633 Changeset *child = m_changesets[childId];
Chris@74 634 if (child->branch() == cs->branch()) {
Chris@74 635 haveChildOnSameBranch = true;
Chris@74 636 break;
Chris@74 637 }
Chris@74 638 }
Chris@74 639 item->setShowBranch(!haveChildOnSameBranch);
Chris@74 640 }
Chris@74 641
Chris@52 642 // We need to lay out the changesets in forward chronological
Chris@52 643 // order. We have no guarantees about the order in which
Chris@52 644 // changesets appear in the list -- in a simple repository they
Chris@52 645 // will generally be reverse chronological, but that's far from
Chris@52 646 // guaranteed. So, sort explicitly using the date comparator
Chris@52 647 // above
Chris@51 648
Chris@52 649 qStableSort(csets.begin(), csets.end(), compareChangesetsByDate);
Chris@51 650
Chris@691 651 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@53 652 foreach (Changeset *cs, csets) {
Chris@145 653 DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp()
Chris@145 654 << ", date " << cs->datetime().toStdString() << endl;
Chris@53 655 }
Chris@691 656 #endif
Chris@53 657
cannam@45 658 m_handled.clear();
Chris@53 659 foreach (Changeset *cs, csets) {
Chris@106 660 layoutRow(cs->id());
cannam@45 661 }
Chris@46 662
Chris@46 663 allocateBranchHomes(csets);
Chris@46 664
cannam@45 665 m_handled.clear();
Chris@53 666 foreach (Changeset *cs, csets) {
Chris@106 667 foreach (QString parentId, cs->parents()) {
Chris@106 668 if (!m_handled.contains(parentId) &&
Chris@106 669 m_changesets.contains(parentId)) {
Chris@106 670 layoutCol(parentId);
Chris@106 671 }
Chris@106 672 }
Chris@106 673 layoutCol(cs->id());
Chris@53 674 }
Chris@53 675
Chris@145 676 // Find row and column extents. We know that 0 is an upper bound
Chris@145 677 // on row, and that mincol must be <= 0 and maxcol >= 0, so these
Chris@145 678 // initial values are good
Chris@55 679
Chris@53 680 int minrow = 0, maxrow = 0;
Chris@53 681 int mincol = 0, maxcol = 0;
Chris@53 682
Chris@53 683 foreach (int r, m_alloc.keys()) {
Chris@106 684 if (r < minrow) minrow = r;
Chris@106 685 if (r > maxrow) maxrow = r;
Chris@106 686 ColumnSet &c = m_alloc[r];
Chris@106 687 foreach (int i, c) {
Chris@106 688 if (i < mincol) mincol = i;
Chris@106 689 if (i > maxcol) maxcol = i;
Chris@106 690 }
Chris@53 691 }
Chris@53 692
Chris@153 693 int datemincol = mincol, datemaxcol = maxcol;
Chris@153 694
Chris@153 695 if (mincol == maxcol) {
Chris@153 696 --datemincol;
Chris@153 697 ++datemaxcol;
Chris@153 698 } else if (m_alloc[minrow].contains(mincol)) {
Chris@153 699 --datemincol;
Chris@153 700 }
Chris@153 701
Chris@145 702 // We've given the uncommitted item a column, but not a row yet --
Chris@145 703 // it always goes at the top
Chris@145 704
Chris@145 705 if (m_uncommitted) {
Chris@145 706 --minrow;
Chris@691 707 #ifdef GRAPHER_VERBOSE_DEBUG
Chris@145 708 DEBUG << "putting uncommitted item at row " << minrow << endl;
Chris@691 709 #endif
Chris@145 710 m_uncommitted->setRow(minrow);
Chris@145 711 }
Chris@145 712
Chris@145 713 // Changeset items that have nothing to either side of them can be
Chris@145 714 // made double-width
Chris@145 715
Chris@145 716 foreach (Changeset *cs, csets) {
Chris@512 717 QString id = cs->id();
Chris@512 718 if (!m_items.contains(id)) continue;
Chris@512 719 ChangesetItem *item = m_items[id];
Chris@145 720 if (isAvailable(item->row(), item->column()-1) &&
Chris@145 721 isAvailable(item->row(), item->column()+1)) {
Chris@145 722 item->setWide(true);
Chris@145 723 }
Chris@145 724 }
Chris@145 725
Chris@145 726 if (m_uncommitted) {
Chris@145 727 if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) &&
Chris@145 728 isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) {
Chris@145 729 m_uncommitted->setWide(true);
Chris@145 730 }
Chris@145 731 }
Chris@145 732
Chris@53 733 QString prevDate;
Chris@53 734 int changeRow = 0;
Chris@53 735
Chris@53 736 bool even = false;
Chris@53 737 int n = 0;
Chris@53 738
Chris@53 739 for (int row = minrow; row <= maxrow; ++row) {
Chris@53 740
Chris@106 741 QString date = m_rowDates[row];
Chris@106 742 n++;
Chris@106 743
Chris@106 744 if (date != prevDate) {
Chris@106 745 if (prevDate != "") {
Chris@397 746 m_scene->addDateRange(prevDate, changeRow, n, even);
Chris@106 747 even = !even;
Chris@106 748 }
Chris@106 749 prevDate = date;
Chris@106 750 changeRow = row;
Chris@106 751 n = 0;
Chris@106 752 }
Chris@53 753 }
Chris@53 754
Chris@53 755 if (n > 0) {
Chris@397 756 m_scene->addDateRange(prevDate, changeRow, n+1, even);
Chris@106 757 even = !even;
cannam@45 758 }
Chris@397 759
Chris@397 760 m_scene->itemAddCompleted();
Chris@44 761 }
Chris@44 762