annotate src/grapher.cpp @ 644:ae67ea0af696

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