annotate src/grapher.cpp @ 494:41a156d439d0

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