annotate grapher.cpp @ 200:8c8c04bdf0fa

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