annotate src/grapher.cpp @ 571:012ba1b83328

Show cancel button with progress bar only when running an operation that it makes sense to cancel (we don't really want people cancelling e.g. initial folder scan because it would leave things in an inconsistent state)
author Chris Cannam
date Thu, 01 Mar 2012 22:53:54 +0000
parents 533519ebc0cb
children ae67ea0af696
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@560 8 Copyright (c) 2012 Chris Cannam
Chris@560 9 Copyright (c) 2012 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