annotate grapher.cpp @ 271:d98c642fc66f

* Bump version to 0.3.2
author Chris Cannam
date Thu, 20 Jan 2011 13:56:01 +0000
parents b8ded5213d16
children cc95394e2392
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@244 8 Copyright (c) 2011 Chris Cannam
Chris@244 9 Copyright (c) 2011 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@268 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;
Chris@268 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@153 113 if (!m_uncommittedParents.empty() && m_uncommittedParents[0] == 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@268 153 col = m_branchHomes[cs->branch()];
Chris@268 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@153 161 !m_changesets[parentId]->isOnBranch(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@268 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@153 179 if (m_changesets[parentId]->isOnBranch(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@268 188 col = findAvailableColumn(item->row(), col, true);
Chris@106 189 } else {
Chris@268 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@153 201 // If we're the first parent of the uncommitted item, it should be
Chris@153 202 // given the same column as us (we already noted that its
Chris@153 203 // connecting line would end at our row)
Chris@145 204
Chris@153 205 if (m_uncommittedParents.contains(id)) {
Chris@153 206 if (m_uncommittedParents[0] == id) {
Chris@268 207 int ucol = findAvailableColumn(row-1, col, true);
Chris@153 208 m_uncommitted->setColumn(ucol);
Chris@153 209 m_haveAllocatedUncommittedColumn = true;
Chris@153 210 }
Chris@153 211 // also, if the uncommitted item has a different branch from
Chris@153 212 // any of its parents, tell it to show the branch
Chris@153 213 if (!cs->isOnBranch(m_uncommitted->branch())) {
Chris@153 214 DEBUG << "Uncommitted branch " << m_uncommitted->branch()
Chris@153 215 << " differs from my branch " << cs->branch()
Chris@153 216 << ", asking it to show branch" << endl;
Chris@153 217 m_uncommitted->setShowBranch(true);
Chris@153 218 }
Chris@145 219 }
Chris@145 220
Chris@51 221 // Normally the children will lay out themselves, but we can do
Chris@51 222 // a better job in some special cases:
Chris@51 223
Chris@47 224 int nchildren = cs->children().size();
Chris@49 225
Chris@53 226 // look for merging children and children distant from us but in a
Chris@53 227 // straight line, and make sure nobody is going to overwrite their
Chris@53 228 // connection lines
Chris@51 229
Chris@49 230 foreach (QString childId, cs->children()) {
Chris@139 231 DEBUG << "reserving connection line space" << endl;
Chris@49 232 if (!m_changesets.contains(childId)) continue;
Chris@49 233 Changeset *child = m_changesets[childId];
Chris@106 234 int childRow = m_items[childId]->row();
Chris@55 235 if (child->parents().size() > 1 ||
Chris@153 236 child->isOnBranch(cs->branch())) {
Chris@55 237 for (int r = row-1; r > childRow; --r) {
Chris@49 238 m_alloc[r].insert(col);
Chris@49 239 }
Chris@106 240 }
Chris@49 241 }
Chris@49 242
Chris@51 243 // look for the case where exactly two children have the same
Chris@51 244 // branch as us: split them to a little either side of our position
Chris@51 245
Chris@47 246 if (nchildren > 1) {
Chris@106 247 QList<QString> special;
Chris@106 248 foreach (QString childId, cs->children()) {
Chris@106 249 if (!m_changesets.contains(childId)) continue;
Chris@106 250 Changeset *child = m_changesets[childId];
Chris@153 251 if (child->isOnBranch(branch) &&
Chris@106 252 child->parents().size() == 1) {
Chris@106 253 special.push_back(childId);
Chris@106 254 }
Chris@106 255 }
Chris@106 256 if (special.size() == 2) {
Chris@139 257 DEBUG << "handling split-in-two for children " << special[0] << " and " << special[1] << endl;
Chris@106 258 for (int i = 0; i < 2; ++i) {
Chris@106 259 int off = i * 2 - 1; // 0 -> -1, 1 -> 1
Chris@106 260 ChangesetItem *it = m_items[special[i]];
Chris@268 261 it->setColumn(findAvailableColumn(it->row(), col + off, true));
Chris@106 262 for (int r = row-1; r >= it->row(); --r) {
Chris@106 263 m_alloc[r].insert(it->column());
Chris@106 264 }
Chris@106 265 m_handled.insert(special[i]);
Chris@106 266 }
Chris@106 267 }
Chris@47 268 }
cannam@45 269 }
cannam@45 270
Chris@106 271 bool Grapher::rangesConflict(const Range &r1, const Range &r2)
Chris@46 272 {
Chris@46 273 // allow some additional space at edges. we really ought also to
Chris@46 274 // permit space at the end of a branch up to the point where the
Chris@46 275 // merge happens
Chris@46 276 int a1 = r1.first - 2, b1 = r1.second + 2;
Chris@46 277 int a2 = r2.first - 2, b2 = r2.second + 2;
Chris@46 278 if (a1 > b2 || b1 < a2) return false;
Chris@46 279 if (a2 > b1 || b2 < a1) return false;
Chris@46 280 return true;
Chris@46 281 }
Chris@46 282
Chris@106 283 void Grapher::allocateBranchHomes(Changesets csets)
Chris@46 284 {
Chris@46 285 foreach (Changeset *cs, csets) {
Chris@106 286 QString branch = cs->branch();
Chris@106 287 ChangesetItem *item = m_items[cs->id()];
Chris@106 288 if (!item) continue;
Chris@106 289 int row = item->row();
Chris@106 290 if (!m_branchRanges.contains(branch)) {
Chris@106 291 m_branchRanges[branch] = Range(row, row);
Chris@106 292 } else {
Chris@106 293 Range p = m_branchRanges[branch];
Chris@106 294 if (row < p.first) p.first = row;
Chris@106 295 if (row > p.second) p.second = row;
Chris@106 296 m_branchRanges[branch] = p;
Chris@106 297 }
Chris@46 298 }
Chris@46 299
Chris@46 300 m_branchHomes[""] = 0;
Chris@153 301 m_branchHomes["default"] = 0;
Chris@46 302
Chris@268 303 foreach (QString branch, m_branchRanges.keys()) {
Chris@106 304 if (branch == "") continue;
Chris@106 305 QSet<int> taken;
Chris@106 306 taken.insert(0);
Chris@106 307 Range myRange = m_branchRanges[branch];
Chris@106 308 foreach (QString other, m_branchRanges.keys()) {
Chris@106 309 if (other == branch || other == "") continue;
Chris@106 310 Range otherRange = m_branchRanges[other];
Chris@106 311 if (rangesConflict(myRange, otherRange)) {
Chris@106 312 if (m_branchHomes.contains(other)) {
Chris@106 313 taken.insert(m_branchHomes[other]);
Chris@106 314 }
Chris@106 315 }
Chris@106 316 }
Chris@106 317 int home = 2;
Chris@106 318 while (taken.contains(home)) {
Chris@268 319 if (home > 0) {
Chris@268 320 if (home % 2 == 1) {
Chris@268 321 home = -home;
Chris@268 322 } else {
Chris@268 323 home = home + 1;
Chris@268 324 }
Chris@268 325 } else {
Chris@268 326 if ((-home) % 2 == 1) {
Chris@268 327 home = home + 1;
Chris@268 328 } else {
Chris@268 329 home = -(home-2);
Chris@268 330 }
Chris@268 331 }
Chris@106 332 }
Chris@106 333 m_branchHomes[branch] = home;
Chris@46 334 }
Chris@46 335
Chris@268 336 foreach (QString branch, m_branchRanges.keys()) {
Chris@268 337 DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl;
Chris@46 338 }
Chris@46 339 }
Chris@46 340
Chris@52 341 static bool
Chris@145 342 compareChangesetsByDate(Changeset *const &a, Changeset *const &b)
Chris@52 343 {
Chris@52 344 return a->timestamp() < b->timestamp();
Chris@52 345 }
Chris@52 346
Chris@53 347 ChangesetItem *
Chris@145 348 Grapher::getItemFor(Changeset *cs)
Chris@53 349 {
Chris@53 350 if (!cs || !m_items.contains(cs->id())) return 0;
Chris@53 351 return m_items[cs->id()];
Chris@53 352 }
Chris@53 353
Chris@153 354 void Grapher::layout(Changesets csets,
Chris@153 355 QStringList uncommittedParents,
Chris@153 356 QString uncommittedBranch)
cannam@45 357 {
Chris@46 358 m_changesets.clear();
cannam@45 359 m_items.clear();
cannam@45 360 m_alloc.clear();
Chris@46 361 m_branchHomes.clear();
cannam@45 362
Chris@153 363 m_uncommittedParents = uncommittedParents;
Chris@145 364 m_haveAllocatedUncommittedColumn = false;
Chris@145 365 m_uncommittedParentRow = 0;
Chris@145 366 m_uncommitted = 0;
Chris@145 367
Chris@139 368 DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl;
Chris@139 369
Chris@53 370 if (csets.empty()) return;
Chris@53 371
Chris@145 372 // Create (but don't yet position) the changeset items
Chris@145 373
Chris@44 374 foreach (Changeset *cs, csets) {
cannam@45 375
Chris@106 376 QString id = cs->id();
cannam@45 377
Chris@106 378 if (id == "") {
Chris@106 379 throw LayoutException("Changeset has no ID");
Chris@106 380 }
Chris@106 381 if (m_changesets.contains(id)) {
Chris@145 382 DEBUG << "Duplicate changeset ID " << id
Chris@145 383 << " in Grapher::layout()" << endl;
Chris@106 384 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
Chris@106 385 }
cannam@45 386
Chris@106 387 m_changesets[id] = cs;
cannam@45 388
cannam@45 389 ChangesetItem *item = new ChangesetItem(cs);
cannam@45 390 item->setX(0);
cannam@45 391 item->setY(0);
Chris@250 392 item->setZValue(0);
Chris@106 393 m_items[id] = item;
Chris@141 394 m_scene->addChangesetItem(item);
cannam@45 395 }
cannam@45 396
Chris@74 397 // Add the connecting lines
Chris@74 398
Chris@46 399 foreach (Changeset *cs, csets) {
Chris@106 400 QString id = cs->id();
Chris@106 401 ChangesetItem *item = m_items[id];
Chris@106 402 bool merge = (cs->parents().size() > 1);
Chris@106 403 foreach (QString parentId, cs->parents()) {
Chris@106 404 if (!m_changesets.contains(parentId)) continue;
Chris@106 405 Changeset *parent = m_changesets[parentId];
Chris@106 406 parent->addChild(id);
Chris@106 407 ConnectionItem *conn = new ConnectionItem();
Chris@106 408 if (merge) conn->setConnectionType(ConnectionItem::Merge);
Chris@106 409 conn->setChild(item);
Chris@106 410 conn->setParent(m_items[parentId]);
Chris@250 411 conn->setZValue(-1);
Chris@106 412 m_scene->addItem(conn);
Chris@106 413 }
Chris@46 414 }
Chris@145 415
Chris@145 416 // Add uncommitted item and connecting line as necessary
Chris@145 417
Chris@153 418 if (!m_uncommittedParents.empty()) {
Chris@145 419 m_uncommitted = new UncommittedItem();
Chris@153 420 m_uncommitted->setBranch(uncommittedBranch);
Chris@250 421 m_uncommitted->setZValue(10);
Chris@149 422 m_scene->addUncommittedItem(m_uncommitted);
Chris@153 423 foreach (QString p, m_uncommittedParents) {
Chris@153 424 ConnectionItem *conn = new ConnectionItem();
Chris@153 425 conn->setConnectionType(ConnectionItem::Merge);
Chris@153 426 conn->setParent(m_items[p]);
Chris@153 427 conn->setChild(m_uncommitted);
Chris@250 428 conn->setZValue(0);
Chris@153 429 m_scene->addItem(conn);
Chris@153 430 }
Chris@145 431 }
Chris@46 432
Chris@74 433 // Add the branch labels
Chris@145 434
Chris@74 435 foreach (Changeset *cs, csets) {
Chris@74 436 QString id = cs->id();
Chris@74 437 ChangesetItem *item = m_items[id];
Chris@74 438 bool haveChildOnSameBranch = false;
Chris@74 439 foreach (QString childId, cs->children()) {
Chris@74 440 Changeset *child = m_changesets[childId];
Chris@74 441 if (child->branch() == cs->branch()) {
Chris@74 442 haveChildOnSameBranch = true;
Chris@74 443 break;
Chris@74 444 }
Chris@74 445 }
Chris@74 446 item->setShowBranch(!haveChildOnSameBranch);
Chris@74 447 }
Chris@74 448
Chris@52 449 // We need to lay out the changesets in forward chronological
Chris@52 450 // order. We have no guarantees about the order in which
Chris@52 451 // changesets appear in the list -- in a simple repository they
Chris@52 452 // will generally be reverse chronological, but that's far from
Chris@52 453 // guaranteed. So, sort explicitly using the date comparator
Chris@52 454 // above
Chris@51 455
Chris@52 456 qStableSort(csets.begin(), csets.end(), compareChangesetsByDate);
Chris@51 457
Chris@53 458 foreach (Changeset *cs, csets) {
Chris@145 459 DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp()
Chris@145 460 << ", date " << cs->datetime().toStdString() << endl;
Chris@53 461 }
Chris@53 462
cannam@45 463 m_handled.clear();
Chris@53 464 foreach (Changeset *cs, csets) {
Chris@106 465 layoutRow(cs->id());
cannam@45 466 }
Chris@46 467
Chris@46 468 allocateBranchHomes(csets);
Chris@46 469
cannam@45 470 m_handled.clear();
Chris@53 471 foreach (Changeset *cs, csets) {
Chris@106 472 foreach (QString parentId, cs->parents()) {
Chris@106 473 if (!m_handled.contains(parentId) &&
Chris@106 474 m_changesets.contains(parentId)) {
Chris@106 475 layoutCol(parentId);
Chris@106 476 }
Chris@106 477 }
Chris@106 478 layoutCol(cs->id());
Chris@53 479 }
Chris@53 480
Chris@145 481 // Find row and column extents. We know that 0 is an upper bound
Chris@145 482 // on row, and that mincol must be <= 0 and maxcol >= 0, so these
Chris@145 483 // initial values are good
Chris@55 484
Chris@53 485 int minrow = 0, maxrow = 0;
Chris@53 486 int mincol = 0, maxcol = 0;
Chris@53 487
Chris@53 488 foreach (int r, m_alloc.keys()) {
Chris@106 489 if (r < minrow) minrow = r;
Chris@106 490 if (r > maxrow) maxrow = r;
Chris@106 491 ColumnSet &c = m_alloc[r];
Chris@106 492 foreach (int i, c) {
Chris@106 493 if (i < mincol) mincol = i;
Chris@106 494 if (i > maxcol) maxcol = i;
Chris@106 495 }
Chris@53 496 }
Chris@53 497
Chris@153 498 int datemincol = mincol, datemaxcol = maxcol;
Chris@153 499
Chris@153 500 if (mincol == maxcol) {
Chris@153 501 --datemincol;
Chris@153 502 ++datemaxcol;
Chris@153 503 } else if (m_alloc[minrow].contains(mincol)) {
Chris@153 504 --datemincol;
Chris@153 505 }
Chris@153 506
Chris@145 507 // We've given the uncommitted item a column, but not a row yet --
Chris@145 508 // it always goes at the top
Chris@145 509
Chris@145 510 if (m_uncommitted) {
Chris@145 511 --minrow;
Chris@145 512 DEBUG << "putting uncommitted item at row " << minrow << endl;
Chris@145 513 m_uncommitted->setRow(minrow);
Chris@145 514 }
Chris@145 515
Chris@145 516 // Changeset items that have nothing to either side of them can be
Chris@145 517 // made double-width
Chris@145 518
Chris@145 519 foreach (Changeset *cs, csets) {
Chris@145 520 ChangesetItem *item = m_items[cs->id()];
Chris@145 521 if (isAvailable(item->row(), item->column()-1) &&
Chris@145 522 isAvailable(item->row(), item->column()+1)) {
Chris@145 523 item->setWide(true);
Chris@145 524 }
Chris@145 525 }
Chris@145 526
Chris@145 527 if (m_uncommitted) {
Chris@145 528 if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) &&
Chris@145 529 isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) {
Chris@145 530 m_uncommitted->setWide(true);
Chris@145 531 }
Chris@145 532 }
Chris@145 533
Chris@53 534 QString prevDate;
Chris@53 535 int changeRow = 0;
Chris@53 536
Chris@53 537 bool even = false;
Chris@53 538 int n = 0;
Chris@53 539
Chris@53 540 for (int row = minrow; row <= maxrow; ++row) {
Chris@53 541
Chris@106 542 QString date = m_rowDates[row];
Chris@106 543 n++;
Chris@106 544
Chris@106 545 if (date != prevDate) {
Chris@106 546 if (prevDate != "") {
Chris@106 547 DateItem *item = new DateItem();
Chris@106 548 item->setDateString(prevDate);
Chris@153 549 item->setCols(datemincol, datemaxcol - datemincol + 1);
Chris@106 550 item->setRows(changeRow, n);
Chris@106 551 item->setEven(even);
Chris@250 552 item->setZValue(-2);
Chris@168 553 m_scene->addDateItem(item);
Chris@106 554 even = !even;
Chris@106 555 }
Chris@106 556 prevDate = date;
Chris@106 557 changeRow = row;
Chris@106 558 n = 0;
Chris@106 559 }
Chris@53 560 }
Chris@53 561
Chris@53 562 if (n > 0) {
Chris@106 563 DateItem *item = new DateItem();
Chris@106 564 item->setDateString(prevDate);
Chris@153 565 item->setCols(datemincol, datemaxcol - datemincol + 1);
Chris@106 566 item->setRows(changeRow, n+1);
Chris@106 567 item->setEven(even);
Chris@250 568 item->setZValue(-2);
Chris@168 569 m_scene->addDateItem(item);
Chris@106 570 even = !even;
cannam@45 571 }
Chris@44 572 }
Chris@44 573