annotate grapher.cpp @ 51:bf3ab0ffb559

* some preliminaries for thinking about use of date in row layout
author Chris Cannam
date Thu, 11 Nov 2010 22:27:24 +0000
parents f9b53c10a3f6
children 384420567575
rev   line source
Chris@44 1
Chris@44 2 #include "grapher.h"
Chris@46 3 #include "connectionitem.h"
Chris@44 4
cannam@45 5 #include <QGraphicsScene>
Chris@44 6
Chris@44 7 #include <iostream>
Chris@44 8
cannam@45 9 int
cannam@45 10 Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
cannam@45 11 {
cannam@45 12 int col = parent;
cannam@45 13 if (preferParentCol) {
cannam@45 14 if (!m_alloc[row].contains(col)) {
cannam@45 15 return col;
cannam@45 16 }
cannam@45 17 }
cannam@45 18 while (col > 0) {
cannam@45 19 if (!m_alloc[row].contains(--col)) return col;
cannam@45 20 }
cannam@45 21 while (col < 0) {
cannam@45 22 if (!m_alloc[row].contains(++col)) return col;
cannam@45 23 }
cannam@45 24 col = parent;
cannam@45 25 int sign = (col < 0 ? -1 : 1);
cannam@45 26 while (1) {
cannam@45 27 col += sign;
cannam@45 28 if (!m_alloc[row].contains(col)) return col;
cannam@45 29 }
cannam@45 30 }
Chris@44 31
cannam@45 32 void
cannam@45 33 Grapher::layoutRow(QString id)
Chris@44 34 {
cannam@45 35 if (m_handled.contains(id)) {
cannam@45 36 return;
Chris@44 37 }
Chris@46 38 if (!m_changesets.contains(id)) {
cannam@45 39 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
Chris@44 40 }
cannam@45 41 if (!m_items.contains(id)) {
cannam@45 42 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
Chris@44 43 }
Chris@46 44 Changeset *cs = m_changesets[id];
cannam@45 45 ChangesetItem *item = m_items[id];
cannam@45 46 std::cerr << "Looking at " << id.toStdString() << std::endl;
cannam@45 47
Chris@44 48 int row = 0;
cannam@45 49 int nparents = cs->parents().size();
cannam@45 50
cannam@45 51 if (nparents > 0) {
Chris@44 52 bool haveRow = false;
Chris@44 53 foreach (QString parentId, cs->parents()) {
cannam@45 54
Chris@46 55 if (!m_changesets.contains(parentId)) continue;
cannam@45 56 if (!m_items.contains(parentId)) continue;
cannam@45 57
cannam@45 58 if (!m_handled.contains(parentId)) {
cannam@45 59 layoutRow(parentId);
cannam@45 60 }
cannam@45 61
cannam@45 62 ChangesetItem *parentItem = m_items[parentId];
Chris@44 63 if (!haveRow || parentItem->row() < row) {
Chris@44 64 row = parentItem->row();
Chris@44 65 haveRow = true;
Chris@44 66 }
Chris@44 67 }
Chris@44 68 row = row - 1;
cannam@45 69 }
cannam@45 70
cannam@45 71 std::cerr << "putting " << cs->id().toStdString() << " at row " << row
cannam@45 72 << std::endl;
cannam@45 73
Chris@44 74 item->setRow(row);
cannam@45 75 m_handled.insert(id);
Chris@44 76 }
Chris@44 77
Chris@44 78 void
cannam@45 79 Grapher::layoutCol(QString id)
Chris@44 80 {
cannam@45 81 if (m_handled.contains(id)) {
cannam@45 82 std::cerr << "Already looked at " << id.toStdString() << std::endl;
cannam@45 83 return;
cannam@45 84 }
Chris@46 85 if (!m_changesets.contains(id)) {
cannam@45 86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
cannam@45 87 }
cannam@45 88 if (!m_items.contains(id)) {
cannam@45 89 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
cannam@45 90 }
Chris@46 91 Changeset *cs = m_changesets[id];
cannam@45 92 ChangesetItem *item = m_items[id];
cannam@45 93 std::cerr << "Looking at " << id.toStdString() << std::endl;
cannam@45 94
Chris@46 95 foreach (QString parentId, cs->parents()) {
Chris@46 96 if (!m_changesets.contains(parentId)) continue;
Chris@46 97 if (!m_handled.contains(parentId)) {
Chris@46 98 layoutCol(parentId);
Chris@46 99 }
Chris@46 100 }
Chris@46 101
Chris@47 102 // Parent may have layed out child in the recursive call
Chris@47 103 if (m_handled.contains(id)) {
Chris@47 104 std::cerr << "Looks like we've dealt with " << id.toStdString() << std::endl;
Chris@47 105 return;
Chris@47 106 }
Chris@47 107
cannam@45 108 int col = 0;
Chris@46 109 int row = item->row();
Chris@46 110 QString branch = cs->branch();
Chris@47 111
cannam@45 112 int nparents = cs->parents().size();
Chris@46 113 QString parentId;
Chris@46 114 int parentsOnSameBranch = 0;
cannam@45 115
Chris@46 116 switch (nparents) {
cannam@45 117
Chris@46 118 case 0:
Chris@46 119 col = m_branchHomes[cs->branch()];
Chris@46 120 col = findAvailableColumn(row, col, true);
Chris@46 121 break;
cannam@45 122
Chris@46 123 case 1:
Chris@46 124 parentId = cs->parents()[0];
cannam@45 125
Chris@46 126 if (!m_changesets.contains(parentId) ||
Chris@46 127 m_changesets[parentId]->branch() != branch) {
Chris@46 128 // new branch
Chris@46 129 col = m_branchHomes[branch];
Chris@46 130 } else {
Chris@46 131 col = m_items[parentId]->column();
Chris@44 132 }
cannam@45 133
Chris@46 134 col = findAvailableColumn(row, col, true);
Chris@46 135 break;
Chris@46 136
Chris@46 137 case 2:
Chris@46 138 // a merge: behave differently if parents are both on the same
Chris@46 139 // branch (we also want to behave differently for nodes that
Chris@46 140 // have multiple children on the same branch -- spreading them
Chris@46 141 // out rather than having affinity to a specific branch)
Chris@46 142
Chris@46 143 foreach (QString parentId, cs->parents()) {
Chris@46 144 if (!m_changesets.contains(parentId)) continue;
Chris@46 145 if (m_changesets[parentId]->branch() == branch) {
Chris@46 146 ChangesetItem *parentItem = m_items[parentId];
Chris@46 147 col += parentItem->column();
Chris@46 148 parentsOnSameBranch++;
Chris@46 149 }
Chris@46 150 }
Chris@46 151
Chris@46 152 if (parentsOnSameBranch > 0) {
Chris@46 153 col /= parentsOnSameBranch;
Chris@46 154 col = findAvailableColumn(item->row(), col, true);
Chris@46 155 } else {
Chris@46 156 col = findAvailableColumn(item->row(), col, false);
Chris@46 157 }
Chris@46 158 break;
Chris@44 159 }
Chris@44 160
cannam@45 161 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
cannam@45 162
Chris@46 163 m_alloc[row].insert(col);
cannam@45 164 item->setColumn(col);
cannam@45 165 m_handled.insert(id);
Chris@47 166
Chris@51 167 // Normally the children will lay out themselves, but we can do
Chris@51 168 // a better job in some special cases:
Chris@51 169
Chris@47 170 int nchildren = cs->children().size();
Chris@49 171
Chris@49 172 // look for merging children and make sure nobody
Chris@51 173 // is going to overwrite their "merge lines" if they extend further
Chris@51 174 // than a single step
Chris@51 175
Chris@49 176 foreach (QString childId, cs->children()) {
Chris@49 177 if (!m_changesets.contains(childId)) continue;
Chris@49 178 Changeset *child = m_changesets[childId];
Chris@49 179 if (child->parents().size() > 1) {
Chris@49 180 int childRow = m_items[childId]->row();
Chris@49 181 std::cerr << "I'm at " << row << ", child with >1 parents is at " << childRow << std::endl;
Chris@51 182 for (int r = row; r > childRow; --r) {
Chris@49 183 std::cerr << "setting row " << r << ", col " << col << std::endl;
Chris@49 184 m_alloc[r].insert(col);
Chris@49 185 }
Chris@49 186 }
Chris@49 187 }
Chris@49 188
Chris@51 189 // look for the case where exactly two children have the same
Chris@51 190 // branch as us: split them to a little either side of our position
Chris@51 191
Chris@47 192 if (nchildren > 1) {
Chris@51 193
Chris@47 194 QList<QString> special;
Chris@47 195 foreach (QString childId, cs->children()) {
Chris@47 196 if (!m_changesets.contains(childId)) continue;
Chris@47 197 Changeset *child = m_changesets[childId];
Chris@47 198 if (child->branch() == branch &&
Chris@47 199 child->parents().size() == 1) {
Chris@47 200 special.push_back(childId);
Chris@47 201 }
Chris@47 202 }
Chris@47 203 if (special.size() == 2) {
Chris@47 204 m_items[special[0]]->setColumn
Chris@47 205 (findAvailableColumn(item->row() - 1, col - 1, true));
Chris@47 206 m_items[special[1]]->setColumn
Chris@47 207 (findAvailableColumn(item->row() - 1, col + 1, true));
Chris@47 208 m_handled.insert(special[0]);
Chris@47 209 m_handled.insert(special[1]);
Chris@47 210 }
Chris@47 211 }
cannam@45 212 }
cannam@45 213
Chris@46 214 bool
Chris@46 215 Grapher::rangesConflict(const Range &r1, const Range &r2)
Chris@46 216 {
Chris@46 217 // allow some additional space at edges. we really ought also to
Chris@46 218 // permit space at the end of a branch up to the point where the
Chris@46 219 // merge happens
Chris@46 220 int a1 = r1.first - 2, b1 = r1.second + 2;
Chris@46 221 int a2 = r2.first - 2, b2 = r2.second + 2;
Chris@46 222 if (a1 > b2 || b1 < a2) return false;
Chris@46 223 if (a2 > b1 || b2 < a1) return false;
Chris@46 224 return true;
Chris@46 225 }
Chris@46 226
Chris@46 227 void
Chris@46 228 Grapher::allocateBranchHomes(Changesets csets)
Chris@46 229 {
Chris@46 230 foreach (Changeset *cs, csets) {
Chris@46 231 QString branch = cs->branch();
Chris@46 232 ChangesetItem *item = m_items[cs->id()];
Chris@46 233 if (!item) continue;
Chris@46 234 int row = item->row();
Chris@46 235 if (!m_branchRanges.contains(branch)) {
Chris@46 236 m_branchRanges[branch] = Range(row, row);
Chris@46 237 } else {
Chris@46 238 Range p = m_branchRanges[branch];
Chris@46 239 if (row < p.first) p.first = row;
Chris@46 240 if (row > p.second) p.second = row;
Chris@46 241 m_branchRanges[branch] = p;
Chris@46 242 }
Chris@46 243 }
Chris@46 244
Chris@46 245 m_branchHomes[""] = 0;
Chris@46 246
Chris@46 247 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 248 if (branch == "") continue;
Chris@46 249 QSet<int> taken;
Chris@46 250 taken.insert(0);
Chris@46 251 Range myRange = m_branchRanges[branch];
Chris@46 252 foreach (QString other, m_branchRanges.keys()) {
Chris@46 253 if (other == branch || other == "") continue;
Chris@46 254 Range otherRange = m_branchRanges[other];
Chris@46 255 if (rangesConflict(myRange, otherRange)) {
Chris@46 256 if (m_branchHomes.contains(other)) {
Chris@46 257 taken.insert(m_branchHomes[other]);
Chris@46 258 }
Chris@46 259 }
Chris@46 260 }
Chris@47 261 int home = 3;
Chris@46 262 while (taken.contains(home)) {
Chris@46 263 if (home > 0) home = -home;
Chris@47 264 else home = -(home-3);
Chris@46 265 }
Chris@46 266 m_branchHomes[branch] = home;
Chris@46 267 }
Chris@46 268
Chris@46 269 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 270 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl;
Chris@46 271 }
Chris@46 272 }
Chris@46 273
cannam@45 274 void
cannam@45 275 Grapher::layout(Changesets csets)
cannam@45 276 {
Chris@46 277 m_changesets.clear();
cannam@45 278 m_items.clear();
cannam@45 279 m_alloc.clear();
Chris@46 280 m_branchHomes.clear();
cannam@45 281
Chris@44 282 foreach (Changeset *cs, csets) {
cannam@45 283
cannam@45 284 QString id = cs->id();
cannam@45 285 std::cerr << id.toStdString() << std::endl;
cannam@45 286
cannam@45 287 if (id == "") {
cannam@45 288 throw LayoutException("Changeset has no ID");
cannam@45 289 }
Chris@46 290 if (m_changesets.contains(id)) {
cannam@45 291 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
cannam@45 292 }
cannam@45 293
Chris@46 294 m_changesets[id] = cs;
cannam@45 295
cannam@45 296 ChangesetItem *item = new ChangesetItem(cs);
cannam@45 297 item->setX(0);
cannam@45 298 item->setY(0);
cannam@45 299 m_items[id] = item;
cannam@45 300 m_scene->addItem(item);
cannam@45 301 }
cannam@45 302
Chris@46 303 foreach (Changeset *cs, csets) {
Chris@46 304 QString id = cs->id();
Chris@46 305 ChangesetItem *item = m_items[id];
Chris@46 306 foreach (QString parentId, cs->parents()) {
Chris@47 307 if (!m_changesets.contains(parentId)) continue;
Chris@47 308 Changeset *parent = m_changesets[parentId];
Chris@47 309 parent->addChild(id);
Chris@46 310 ConnectionItem *conn = new ConnectionItem();
Chris@46 311 conn->setChild(item);
Chris@46 312 conn->setParent(m_items[parentId]);
Chris@46 313 m_scene->addItem(conn);
Chris@46 314 }
Chris@46 315 }
Chris@46 316
cannam@45 317 // Layout in reverse order, i.e. forward chronological order.
cannam@45 318 // This ensures that parents will normally be laid out before
cannam@45 319 // their children -- though we can recurse from layout() if we
cannam@45 320 // find any weird exceptions
Chris@51 321
Chris@51 322 //!!! changesets are not in any chronological order, necessarily:
Chris@51 323 // we need to sort by datetime() [or, better, numerical date]
Chris@51 324 // and then use m_rowDateMap
Chris@51 325
cannam@45 326 m_handled.clear();
cannam@45 327 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 328 layoutRow(csets[i]->id());
cannam@45 329 }
Chris@46 330
Chris@46 331 allocateBranchHomes(csets);
Chris@46 332
cannam@45 333 m_handled.clear();
cannam@45 334 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 335 layoutCol(csets[i]->id());
cannam@45 336 }
Chris@44 337 }
Chris@44 338