annotate grapher.cpp @ 145:644bd31e8301

* Include the uncommitted item in general graph layout (in case it is not at the head, when other items will need to avoid it)
author Chris Cannam
date Wed, 01 Dec 2010 17:41:14 +0000
parents f61f032b06f9
children 38faf16df9b6
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@145 404 m_scene->addItem(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