annotate grapher.cpp @ 150:fb697ce0f625

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