comparison grapher.cpp @ 45:4286836bb3c9

* Some more work on graph layout; ensure LANG is set for parseable UTF8 output when running Hg
author Chris Cannam <cannam@all-day-breakfast.com>
date Wed, 10 Nov 2010 12:44:11 +0000
parents bed7ab59f62e
children bd3accba9b3f
comparison
equal deleted inserted replaced
44:bed7ab59f62e 45:4286836bb3c9
1 1
2 #include "grapher.h" 2 #include "grapher.h"
3 3
4 #include <QSet> 4 #include <QGraphicsScene>
5 #include <QMap>
6 5
7 #include <iostream> 6 #include <iostream>
8 7
9 typedef QSet<int> ColumnSet; 8 int
10 typedef QMap<int, ColumnSet> GridAlloc; 9 Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
11 typedef QMap<QString, Changeset *> IdChangesetMap; 10 {
12 typedef QSet<Changeset *> ChangesetSet; 11 int col = parent;
12 if (preferParentCol) {
13 if (!m_alloc[row].contains(col)) {
14 return col;
15 }
16 }
17 while (col > 0) {
18 if (!m_alloc[row].contains(--col)) return col;
19 }
20 while (col < 0) {
21 if (!m_alloc[row].contains(++col)) return col;
22 }
23 col = parent;
24 int sign = (col < 0 ? -1 : 1);
25 while (1) {
26 col += sign;
27 if (!m_alloc[row].contains(col)) return col;
28 }
29 }
13 30
14 ChangesetItem * 31 void
15 layout(Changeset *cs, 32 Grapher::layoutRow(QString id)
16 IdChangesetMap idCsetMap,
17 ChangesetItemMap items,
18 GridAlloc &alloc,
19 ChangesetSet &handled)
20 { 33 {
21 if (!cs) { 34 if (m_handled.contains(id)) {
22 throw std::string("Null Changeset"); 35 return;
23 } 36 }
24 if (!items.contains(cs)) { 37 if (!m_idCsetMap.contains(id)) {
25 throw std::string("Changeset not in item map"); 38 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
26 } 39 }
27 ChangesetItem *item = items[cs]; 40 if (!m_items.contains(id)) {
28 if (handled.contains(cs)) { 41 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
29 return item;
30 } 42 }
43 Changeset *cs = m_idCsetMap[id];
44 ChangesetItem *item = m_items[id];
45 std::cerr << "Looking at " << id.toStdString() << std::endl;
46
31 int row = 0; 47 int row = 0;
32 int col = 0; 48 int nparents = cs->parents().size();
33 if (!cs->parents().empty()) { 49
50 if (nparents > 0) {
34 bool haveRow = false; 51 bool haveRow = false;
35 foreach (QString parentId, cs->parents()) { 52 foreach (QString parentId, cs->parents()) {
36 if (parentId == "") continue; //!!! 53
37 std::cerr << "recursing to parent \"" << parentId.toStdString() << "\" of \"" << cs->id().toStdString() << "\"" << std::endl; 54 if (!m_idCsetMap.contains(parentId)) continue;
38 ChangesetItem *parentItem = 55 if (!m_items.contains(parentId)) continue;
39 layout(idCsetMap[parentId], 56
40 idCsetMap, 57 if (!m_handled.contains(parentId)) {
41 items, 58 layoutRow(parentId);
42 alloc, 59 }
43 handled); 60
61 ChangesetItem *parentItem = m_items[parentId];
44 if (!haveRow || parentItem->row() < row) { 62 if (!haveRow || parentItem->row() < row) {
45 row = parentItem->row(); 63 row = parentItem->row();
46 haveRow = true; 64 haveRow = true;
47 } 65 }
48 col += parentItem->column();
49 } 66 }
50 col /= cs->parents().size();
51 row = row - 1; 67 row = row - 1;
52 while (alloc[row].contains(col)) { 68 }
53 if (col > 0) col = -col; 69
54 else col = -col + 1; 70 std::cerr << "putting " << cs->id().toStdString() << " at row " << row
55 } 71 << std::endl;
56 alloc[row].insert(col); 72
57 }
58 item->setColumn(col);
59 item->setRow(row); 73 item->setRow(row);
60 item->setX(col * 100);
61 item->setY(row * 100); 74 item->setY(row * 100);
62 handled.insert(cs); 75 m_handled.insert(id);
63 return item;
64 } 76 }
65 77
66 void 78 void
67 Grapher::layout(Changesets csets, ChangesetItemMap items) 79 Grapher::layoutCol(QString id)
68 { 80 {
69 IdChangesetMap idCsetMap; 81 if (m_handled.contains(id)) {
70 foreach (Changeset *cs, csets) { 82 std::cerr << "Already looked at " << id.toStdString() << std::endl;
71 std::cerr << cs->id().toStdString() << std::endl; 83 return;
72 if (cs->id() == "") { 84 }
73 throw std::string("Changeset has no ID"); 85 if (!m_idCsetMap.contains(id)) {
86 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
87 }
88 if (!m_items.contains(id)) {
89 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
90 }
91 Changeset *cs = m_idCsetMap[id];
92 ChangesetItem *item = m_items[id];
93 std::cerr << "Looking at " << id.toStdString() << std::endl;
94
95 int col = 0;
96 int nparents = cs->parents().size();
97
98 if (nparents > 0) {
99 bool preferParentCol = true;
100 foreach (QString parentId, cs->parents()) {
101
102 if (!m_idCsetMap.contains(parentId)) continue;
103 if (!m_items.contains(parentId)) continue;
104
105 if (nparents == 1) {
106 // when introducing a new branch, aim _not_ to
107 // position child on the same column as parent
108 Changeset *parent = m_idCsetMap[parentId];
109 if (parent->branch() != cs->branch()) {
110 preferParentCol = false;
111 }
112 }
113
114 if (!m_handled.contains(parentId)) {
115 layoutCol(parentId);
116 }
117
118 ChangesetItem *parentItem = m_items[parentId];
119 col += parentItem->column();
74 } 120 }
75 if (idCsetMap.contains(cs->id())) { 121
76 throw std::string("Changeset ID is already in map"); 122 col /= cs->parents().size();
77 } 123 col = findAvailableColumn(item->row(), col, preferParentCol);
78 idCsetMap[cs->id()] = cs; 124 m_alloc[item->row()].insert(col);
79 } 125 }
80 126
81 GridAlloc alloc; 127 std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
82 ChangesetSet handled; 128
129 item->setColumn(col);
130 item->setX(col * 100);
131 m_handled.insert(id);
132 }
133
134 void
135 Grapher::layout(Changesets csets)
136 {
137 m_idCsetMap.clear();
138 m_items.clear();
139 m_alloc.clear();
140
83 foreach (Changeset *cs, csets) { 141 foreach (Changeset *cs, csets) {
84 ::layout(cs, idCsetMap, items, alloc, handled); 142
143 QString id = cs->id();
144 std::cerr << id.toStdString() << std::endl;
145
146 if (id == "") {
147 throw LayoutException("Changeset has no ID");
148 }
149 if (m_idCsetMap.contains(id)) {
150 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
151 }
152
153 m_idCsetMap[id] = cs;
154
155 ChangesetItem *item = new ChangesetItem(cs);
156 item->setX(0);
157 item->setY(0);
158 m_items[id] = item;
159 m_scene->addItem(item);
160 }
161
162 // Layout in reverse order, i.e. forward chronological order.
163 // This ensures that parents will normally be laid out before
164 // their children -- though we can recurse from layout() if we
165 // find any weird exceptions
166 m_handled.clear();
167 for (int i = csets.size() - 1; i >= 0; --i) {
168 layoutRow(csets[i]->id());
169 }
170 m_handled.clear();
171 for (int i = csets.size() - 1; i >= 0; --i) {
172 layoutCol(csets[i]->id());
173 }
174
175 foreach (Changeset *cs, csets) {
176 QString id = cs->id();
177 if (!m_items.contains(id)) continue;
178 ChangesetItem *me = m_items[id];
179 foreach (QString parentId, cs->parents()) {
180 if (!m_items.contains(parentId)) continue;
181 ChangesetItem *parent = m_items[parentId];
182 QGraphicsLineItem *line = new QGraphicsLineItem;
183 line->setLine(me->x() + 25, me->y() + 50,
184 parent->x() + 25, parent->y());
185 m_scene->addItem(line);
186 }
85 } 187 }
86 } 188 }
87 189