annotate src/grapher.cpp @ 425:ad106f5fe75f

Add "Ignore Files" and "Edit Ignored List" to Work menu (latter is subsumed from Advanced menu formerly). Also subsume Serve via HTTP into File menu as Share Repository, and add a more helpful description of it. Remove Advanced menu
author Chris Cannam
date Thu, 23 Jun 2011 10:58:32 +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