annotate grapher.cpp @ 202:3d4291d4226c

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