Mercurial > hg > easyhg
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 |