annotate grapher.cpp @ 48:996b3c4037ef

* Add IndirectPainting opt flag, makes a big difference to rendering Panner; rename project
author Chris Cannam
date Wed, 10 Nov 2010 22:07:03 +0000
parents 24efab584ee5
children f9b53c10a3f6
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@47 167 int nchildren = cs->children().size();
Chris@47 168 if (nchildren > 1) {
Chris@47 169 // Normally the children will lay out themselves. We just
Chris@47 170 // want to handle the case where exactly two children have the
Chris@47 171 // same branch as us, because we can handle that neatly
Chris@47 172 QList<QString> special;
Chris@47 173 foreach (QString childId, cs->children()) {
Chris@47 174 if (!m_changesets.contains(childId)) continue;
Chris@47 175 Changeset *child = m_changesets[childId];
Chris@47 176 if (child->branch() == branch &&
Chris@47 177 child->parents().size() == 1) {
Chris@47 178 special.push_back(childId);
Chris@47 179 }
Chris@47 180 }
Chris@47 181 if (special.size() == 2) {
Chris@47 182 m_items[special[0]]->setColumn
Chris@47 183 (findAvailableColumn(item->row() - 1, col - 1, true));
Chris@47 184 m_items[special[1]]->setColumn
Chris@47 185 (findAvailableColumn(item->row() - 1, col + 1, true));
Chris@47 186 m_handled.insert(special[0]);
Chris@47 187 m_handled.insert(special[1]);
Chris@47 188 }
Chris@47 189 }
cannam@45 190 }
cannam@45 191
Chris@46 192 bool
Chris@46 193 Grapher::rangesConflict(const Range &r1, const Range &r2)
Chris@46 194 {
Chris@46 195 // allow some additional space at edges. we really ought also to
Chris@46 196 // permit space at the end of a branch up to the point where the
Chris@46 197 // merge happens
Chris@46 198 int a1 = r1.first - 2, b1 = r1.second + 2;
Chris@46 199 int a2 = r2.first - 2, b2 = r2.second + 2;
Chris@46 200 if (a1 > b2 || b1 < a2) return false;
Chris@46 201 if (a2 > b1 || b2 < a1) return false;
Chris@46 202 return true;
Chris@46 203 }
Chris@46 204
Chris@46 205 void
Chris@46 206 Grapher::allocateBranchHomes(Changesets csets)
Chris@46 207 {
Chris@46 208 foreach (Changeset *cs, csets) {
Chris@46 209 QString branch = cs->branch();
Chris@46 210 ChangesetItem *item = m_items[cs->id()];
Chris@46 211 if (!item) continue;
Chris@46 212 int row = item->row();
Chris@46 213 if (!m_branchRanges.contains(branch)) {
Chris@46 214 m_branchRanges[branch] = Range(row, row);
Chris@46 215 } else {
Chris@46 216 Range p = m_branchRanges[branch];
Chris@46 217 if (row < p.first) p.first = row;
Chris@46 218 if (row > p.second) p.second = row;
Chris@46 219 m_branchRanges[branch] = p;
Chris@46 220 }
Chris@46 221 }
Chris@46 222
Chris@46 223 m_branchHomes[""] = 0;
Chris@46 224
Chris@46 225 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 226 if (branch == "") continue;
Chris@46 227 QSet<int> taken;
Chris@46 228 taken.insert(0);
Chris@46 229 Range myRange = m_branchRanges[branch];
Chris@46 230 foreach (QString other, m_branchRanges.keys()) {
Chris@46 231 if (other == branch || other == "") continue;
Chris@46 232 Range otherRange = m_branchRanges[other];
Chris@46 233 if (rangesConflict(myRange, otherRange)) {
Chris@46 234 if (m_branchHomes.contains(other)) {
Chris@46 235 taken.insert(m_branchHomes[other]);
Chris@46 236 }
Chris@46 237 }
Chris@46 238 }
Chris@47 239 int home = 3;
Chris@46 240 while (taken.contains(home)) {
Chris@46 241 if (home > 0) home = -home;
Chris@47 242 else home = -(home-3);
Chris@46 243 }
Chris@46 244 m_branchHomes[branch] = home;
Chris@46 245 }
Chris@46 246
Chris@46 247 foreach (QString branch, m_branchRanges.keys()) {
Chris@46 248 std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl;
Chris@46 249 }
Chris@46 250 }
Chris@46 251
cannam@45 252 void
cannam@45 253 Grapher::layout(Changesets csets)
cannam@45 254 {
Chris@46 255 m_changesets.clear();
cannam@45 256 m_items.clear();
cannam@45 257 m_alloc.clear();
Chris@46 258 m_branchHomes.clear();
cannam@45 259
Chris@44 260 foreach (Changeset *cs, csets) {
cannam@45 261
cannam@45 262 QString id = cs->id();
cannam@45 263 std::cerr << id.toStdString() << std::endl;
cannam@45 264
cannam@45 265 if (id == "") {
cannam@45 266 throw LayoutException("Changeset has no ID");
cannam@45 267 }
Chris@46 268 if (m_changesets.contains(id)) {
cannam@45 269 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
cannam@45 270 }
cannam@45 271
Chris@46 272 m_changesets[id] = cs;
cannam@45 273
cannam@45 274 ChangesetItem *item = new ChangesetItem(cs);
cannam@45 275 item->setX(0);
cannam@45 276 item->setY(0);
cannam@45 277 m_items[id] = item;
cannam@45 278 m_scene->addItem(item);
cannam@45 279 }
cannam@45 280
Chris@46 281 foreach (Changeset *cs, csets) {
Chris@46 282 QString id = cs->id();
Chris@46 283 ChangesetItem *item = m_items[id];
Chris@46 284 foreach (QString parentId, cs->parents()) {
Chris@47 285 if (!m_changesets.contains(parentId)) continue;
Chris@47 286 Changeset *parent = m_changesets[parentId];
Chris@47 287 parent->addChild(id);
Chris@46 288 ConnectionItem *conn = new ConnectionItem();
Chris@46 289 conn->setChild(item);
Chris@46 290 conn->setParent(m_items[parentId]);
Chris@46 291 m_scene->addItem(conn);
Chris@46 292 }
Chris@46 293 }
Chris@46 294
cannam@45 295 // Layout in reverse order, i.e. forward chronological order.
cannam@45 296 // This ensures that parents will normally be laid out before
cannam@45 297 // their children -- though we can recurse from layout() if we
cannam@45 298 // find any weird exceptions
cannam@45 299 m_handled.clear();
cannam@45 300 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 301 layoutRow(csets[i]->id());
cannam@45 302 }
Chris@46 303
Chris@46 304 allocateBranchHomes(csets);
Chris@46 305
cannam@45 306 m_handled.clear();
cannam@45 307 for (int i = csets.size() - 1; i >= 0; --i) {
cannam@45 308 layoutCol(csets[i]->id());
cannam@45 309 }
Chris@44 310 }
Chris@44 311