annotate grapher.cpp @ 344:ccc55539e066

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