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@106
|
26 int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
|
cannam@45
|
27 {
|
cannam@45
|
28 int col = parent;
|
cannam@45
|
29 if (preferParentCol) {
|
Chris@106
|
30 if (!m_alloc[row].contains(col)) {
|
Chris@106
|
31 return col;
|
Chris@106
|
32 }
|
cannam@45
|
33 }
|
cannam@45
|
34 while (col > 0) {
|
Chris@106
|
35 if (!m_alloc[row].contains(--col)) return col;
|
cannam@45
|
36 }
|
cannam@45
|
37 while (col < 0) {
|
Chris@106
|
38 if (!m_alloc[row].contains(++col)) return col;
|
cannam@45
|
39 }
|
cannam@45
|
40 col = parent;
|
cannam@45
|
41 int sign = (col < 0 ? -1 : 1);
|
cannam@45
|
42 while (1) {
|
Chris@106
|
43 col += sign;
|
Chris@106
|
44 if (!m_alloc[row].contains(col)) return col;
|
cannam@45
|
45 }
|
cannam@45
|
46 }
|
Chris@44
|
47
|
Chris@106
|
48 void Grapher::layoutRow(QString id)
|
Chris@44
|
49 {
|
cannam@45
|
50 if (m_handled.contains(id)) {
|
Chris@106
|
51 return;
|
Chris@44
|
52 }
|
Chris@46
|
53 if (!m_changesets.contains(id)) {
|
Chris@106
|
54 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
|
Chris@44
|
55 }
|
cannam@45
|
56 if (!m_items.contains(id)) {
|
Chris@106
|
57 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
|
Chris@44
|
58 }
|
Chris@46
|
59 Changeset *cs = m_changesets[id];
|
cannam@45
|
60 ChangesetItem *item = m_items[id];
|
Chris@114
|
61 DEBUG << "layoutRow: Looking at " << id.toStdString() << endl;
|
cannam@45
|
62
|
Chris@44
|
63 int row = 0;
|
cannam@45
|
64 int nparents = cs->parents().size();
|
cannam@45
|
65
|
cannam@45
|
66 if (nparents > 0) {
|
Chris@106
|
67 bool haveRow = false;
|
Chris@106
|
68 foreach (QString parentId, cs->parents()) {
|
cannam@45
|
69
|
Chris@106
|
70 if (!m_changesets.contains(parentId)) continue;
|
Chris@106
|
71 if (!m_items.contains(parentId)) continue;
|
cannam@45
|
72
|
Chris@106
|
73 if (!m_handled.contains(parentId)) {
|
Chris@106
|
74 layoutRow(parentId);
|
Chris@106
|
75 }
|
cannam@45
|
76
|
Chris@106
|
77 ChangesetItem *parentItem = m_items[parentId];
|
Chris@106
|
78 if (!haveRow || parentItem->row() < row) {
|
Chris@106
|
79 row = parentItem->row();
|
Chris@106
|
80 haveRow = true;
|
Chris@106
|
81 }
|
Chris@106
|
82 }
|
Chris@106
|
83 row = row - 1;
|
cannam@45
|
84 }
|
cannam@45
|
85
|
Chris@52
|
86 // row is now an upper bound on our eventual row (because we want
|
Chris@52
|
87 // to be above all parents). But we also want to ensure we are
|
Chris@52
|
88 // above all nodes that have earlier dates (to the nearest day).
|
Chris@52
|
89 // m_rowDates maps each row to a date: use that.
|
Chris@52
|
90
|
Chris@53
|
91 QString date = cs->age();
|
Chris@53
|
92 while (m_rowDates.contains(row) && m_rowDates[row] != date) {
|
Chris@106
|
93 --row;
|
Chris@52
|
94 }
|
Chris@52
|
95
|
Chris@52
|
96 // We have already laid out all nodes that have earlier timestamps
|
Chris@52
|
97 // than this one, so we know (among other things) that we can
|
Chris@52
|
98 // safely fill in this row has having this date, if it isn't in
|
Chris@52
|
99 // the map yet (it cannot have an earlier date)
|
Chris@52
|
100
|
Chris@52
|
101 if (!m_rowDates.contains(row)) {
|
Chris@106
|
102 m_rowDates[row] = date;
|
Chris@52
|
103 }
|
Chris@52
|
104
|
Chris@114
|
105 DEBUG << "putting " << cs->id().toStdString() << " at row " << row
|
Chris@114
|
106 << endl;
|
cannam@45
|
107
|
Chris@44
|
108 item->setRow(row);
|
cannam@45
|
109 m_handled.insert(id);
|
Chris@44
|
110 }
|
Chris@44
|
111
|
Chris@106
|
112 void Grapher::layoutCol(QString id)
|
Chris@44
|
113 {
|
cannam@45
|
114 if (m_handled.contains(id)) {
|
Chris@114
|
115 DEBUG << "Already looked at " << id.toStdString() << endl;
|
Chris@106
|
116 return;
|
cannam@45
|
117 }
|
Chris@46
|
118 if (!m_changesets.contains(id)) {
|
Chris@106
|
119 throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
|
cannam@45
|
120 }
|
cannam@45
|
121 if (!m_items.contains(id)) {
|
Chris@106
|
122 throw LayoutException(QString("Changeset %1 not in item map").arg(id));
|
cannam@45
|
123 }
|
Chris@53
|
124
|
Chris@46
|
125 Changeset *cs = m_changesets[id];
|
Chris@114
|
126 // DEBUG << "layoutCol: Looking at " << id.toStdString() << endl;
|
Chris@53
|
127
|
cannam@45
|
128 ChangesetItem *item = m_items[id];
|
Chris@47
|
129
|
cannam@45
|
130 int col = 0;
|
Chris@46
|
131 int row = item->row();
|
Chris@46
|
132 QString branch = cs->branch();
|
Chris@47
|
133
|
cannam@45
|
134 int nparents = cs->parents().size();
|
Chris@46
|
135 QString parentId;
|
Chris@46
|
136 int parentsOnSameBranch = 0;
|
cannam@45
|
137
|
Chris@46
|
138 switch (nparents) {
|
cannam@45
|
139
|
Chris@46
|
140 case 0:
|
Chris@106
|
141 col = m_branchHomes[cs->branch()];
|
Chris@106
|
142 col = findAvailableColumn(row, col, true);
|
Chris@106
|
143 break;
|
cannam@45
|
144
|
Chris@46
|
145 case 1:
|
Chris@106
|
146 parentId = cs->parents()[0];
|
cannam@45
|
147
|
Chris@106
|
148 if (!m_changesets.contains(parentId) ||
|
Chris@106
|
149 m_changesets[parentId]->branch() != branch) {
|
Chris@106
|
150 // new branch
|
Chris@106
|
151 col = m_branchHomes[branch];
|
Chris@106
|
152 } else {
|
Chris@106
|
153 col = m_items[parentId]->column();
|
Chris@106
|
154 }
|
cannam@45
|
155
|
Chris@106
|
156 col = findAvailableColumn(row, col, true);
|
Chris@106
|
157 break;
|
Chris@46
|
158
|
Chris@46
|
159 case 2:
|
Chris@106
|
160 // a merge: behave differently if parents are both on the same
|
Chris@106
|
161 // branch (we also want to behave differently for nodes that
|
Chris@106
|
162 // have multiple children on the same branch -- spreading them
|
Chris@106
|
163 // out rather than having affinity to a specific branch)
|
Chris@46
|
164
|
Chris@106
|
165 foreach (QString parentId, cs->parents()) {
|
Chris@106
|
166 if (!m_changesets.contains(parentId)) continue;
|
Chris@106
|
167 if (m_changesets[parentId]->branch() == branch) {
|
Chris@106
|
168 ChangesetItem *parentItem = m_items[parentId];
|
Chris@106
|
169 col += parentItem->column();
|
Chris@106
|
170 parentsOnSameBranch++;
|
Chris@106
|
171 }
|
Chris@106
|
172 }
|
Chris@46
|
173
|
Chris@106
|
174 if (parentsOnSameBranch > 0) {
|
Chris@106
|
175 col /= parentsOnSameBranch;
|
Chris@106
|
176 col = findAvailableColumn(item->row(), col, true);
|
Chris@106
|
177 } else {
|
Chris@106
|
178 col = findAvailableColumn(item->row(), col, false);
|
Chris@106
|
179 }
|
Chris@106
|
180 break;
|
Chris@44
|
181 }
|
Chris@44
|
182
|
Chris@114
|
183 // DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl;
|
cannam@45
|
184
|
Chris@46
|
185 m_alloc[row].insert(col);
|
cannam@45
|
186 item->setColumn(col);
|
cannam@45
|
187 m_handled.insert(id);
|
Chris@47
|
188
|
Chris@51
|
189 // Normally the children will lay out themselves, but we can do
|
Chris@51
|
190 // a better job in some special cases:
|
Chris@51
|
191
|
Chris@47
|
192 int nchildren = cs->children().size();
|
Chris@49
|
193
|
Chris@53
|
194 // look for merging children and children distant from us but in a
|
Chris@53
|
195 // straight line, and make sure nobody is going to overwrite their
|
Chris@53
|
196 // connection lines
|
Chris@51
|
197
|
Chris@49
|
198 foreach (QString childId, cs->children()) {
|
Chris@49
|
199 if (!m_changesets.contains(childId)) continue;
|
Chris@49
|
200 Changeset *child = m_changesets[childId];
|
Chris@106
|
201 int childRow = m_items[childId]->row();
|
Chris@55
|
202 if (child->parents().size() > 1 ||
|
Chris@106
|
203 child->branch() == cs->branch()) {
|
Chris@55
|
204 for (int r = row-1; r > childRow; --r) {
|
Chris@49
|
205 m_alloc[r].insert(col);
|
Chris@49
|
206 }
|
Chris@106
|
207 }
|
Chris@49
|
208 }
|
Chris@49
|
209
|
Chris@51
|
210 // look for the case where exactly two children have the same
|
Chris@51
|
211 // branch as us: split them to a little either side of our position
|
Chris@51
|
212
|
Chris@47
|
213 if (nchildren > 1) {
|
Chris@51
|
214
|
Chris@106
|
215 QList<QString> special;
|
Chris@106
|
216 foreach (QString childId, cs->children()) {
|
Chris@106
|
217 if (!m_changesets.contains(childId)) continue;
|
Chris@106
|
218 Changeset *child = m_changesets[childId];
|
Chris@106
|
219 if (child->branch() == branch &&
|
Chris@106
|
220 child->parents().size() == 1) {
|
Chris@106
|
221 special.push_back(childId);
|
Chris@106
|
222 }
|
Chris@106
|
223 }
|
Chris@106
|
224 if (special.size() == 2) {
|
Chris@106
|
225 for (int i = 0; i < 2; ++i) {
|
Chris@106
|
226 int off = i * 2 - 1; // 0 -> -1, 1 -> 1
|
Chris@106
|
227 ChangesetItem *it = m_items[special[i]];
|
Chris@106
|
228 it->setColumn(findAvailableColumn(it->row(), col + off, true));
|
Chris@106
|
229 for (int r = row-1; r >= it->row(); --r) {
|
Chris@106
|
230 m_alloc[r].insert(it->column());
|
Chris@106
|
231 }
|
Chris@106
|
232 m_handled.insert(special[i]);
|
Chris@106
|
233 }
|
Chris@106
|
234 }
|
Chris@47
|
235 }
|
cannam@45
|
236 }
|
cannam@45
|
237
|
Chris@106
|
238 bool Grapher::rangesConflict(const Range &r1, const Range &r2)
|
Chris@46
|
239 {
|
Chris@46
|
240 // allow some additional space at edges. we really ought also to
|
Chris@46
|
241 // permit space at the end of a branch up to the point where the
|
Chris@46
|
242 // merge happens
|
Chris@46
|
243 int a1 = r1.first - 2, b1 = r1.second + 2;
|
Chris@46
|
244 int a2 = r2.first - 2, b2 = r2.second + 2;
|
Chris@46
|
245 if (a1 > b2 || b1 < a2) return false;
|
Chris@46
|
246 if (a2 > b1 || b2 < a1) return false;
|
Chris@46
|
247 return true;
|
Chris@46
|
248 }
|
Chris@46
|
249
|
Chris@106
|
250 void Grapher::allocateBranchHomes(Changesets csets)
|
Chris@46
|
251 {
|
Chris@46
|
252 foreach (Changeset *cs, csets) {
|
Chris@106
|
253 QString branch = cs->branch();
|
Chris@106
|
254 ChangesetItem *item = m_items[cs->id()];
|
Chris@106
|
255 if (!item) continue;
|
Chris@106
|
256 int row = item->row();
|
Chris@106
|
257 if (!m_branchRanges.contains(branch)) {
|
Chris@106
|
258 m_branchRanges[branch] = Range(row, row);
|
Chris@106
|
259 } else {
|
Chris@106
|
260 Range p = m_branchRanges[branch];
|
Chris@106
|
261 if (row < p.first) p.first = row;
|
Chris@106
|
262 if (row > p.second) p.second = row;
|
Chris@106
|
263 m_branchRanges[branch] = p;
|
Chris@106
|
264 }
|
Chris@46
|
265 }
|
Chris@46
|
266
|
Chris@46
|
267 m_branchHomes[""] = 0;
|
Chris@46
|
268
|
Chris@46
|
269 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@106
|
270 if (branch == "") continue;
|
Chris@106
|
271 QSet<int> taken;
|
Chris@106
|
272 taken.insert(0);
|
Chris@106
|
273 Range myRange = m_branchRanges[branch];
|
Chris@106
|
274 foreach (QString other, m_branchRanges.keys()) {
|
Chris@106
|
275 if (other == branch || other == "") continue;
|
Chris@106
|
276 Range otherRange = m_branchRanges[other];
|
Chris@106
|
277 if (rangesConflict(myRange, otherRange)) {
|
Chris@106
|
278 if (m_branchHomes.contains(other)) {
|
Chris@106
|
279 taken.insert(m_branchHomes[other]);
|
Chris@106
|
280 }
|
Chris@106
|
281 }
|
Chris@106
|
282 }
|
Chris@106
|
283 int home = 2;
|
Chris@106
|
284 while (taken.contains(home)) {
|
Chris@106
|
285 if (home > 0) {
|
Chris@106
|
286 if (home % 2 == 1) {
|
Chris@106
|
287 home = -home;
|
Chris@106
|
288 } else {
|
Chris@106
|
289 home = home + 1;
|
Chris@106
|
290 }
|
Chris@106
|
291 } else {
|
Chris@106
|
292 if ((-home) % 2 == 1) {
|
Chris@106
|
293 home = home + 1;
|
Chris@106
|
294 } else {
|
Chris@106
|
295 home = -(home-2);
|
Chris@106
|
296 }
|
Chris@106
|
297 }
|
Chris@106
|
298 }
|
Chris@106
|
299 m_branchHomes[branch] = home;
|
Chris@46
|
300 }
|
Chris@46
|
301
|
Chris@46
|
302 foreach (QString branch, m_branchRanges.keys()) {
|
Chris@114
|
303 DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl;
|
Chris@46
|
304 }
|
Chris@46
|
305 }
|
Chris@46
|
306
|
Chris@52
|
307 static bool
|
Chris@106
|
308 compareChangesetsByDate(Changeset *const &a, Changeset *const &b)
|
Chris@52
|
309 {
|
Chris@52
|
310 return a->timestamp() < b->timestamp();
|
Chris@52
|
311 }
|
Chris@52
|
312
|
Chris@53
|
313 ChangesetItem *
|
Chris@106
|
314 Grapher::getItemFor(Changeset *cs)
|
Chris@53
|
315 {
|
Chris@53
|
316 if (!cs || !m_items.contains(cs->id())) return 0;
|
Chris@53
|
317 return m_items[cs->id()];
|
Chris@53
|
318 }
|
Chris@53
|
319
|
Chris@106
|
320 void Grapher::layout(Changesets csets)
|
cannam@45
|
321 {
|
Chris@46
|
322 m_changesets.clear();
|
cannam@45
|
323 m_items.clear();
|
cannam@45
|
324 m_alloc.clear();
|
Chris@46
|
325 m_branchHomes.clear();
|
cannam@45
|
326
|
Chris@53
|
327 if (csets.empty()) return;
|
Chris@53
|
328
|
Chris@44
|
329 foreach (Changeset *cs, csets) {
|
cannam@45
|
330
|
Chris@106
|
331 QString id = cs->id();
|
Chris@114
|
332 DEBUG << id.toStdString() << endl;
|
cannam@45
|
333
|
Chris@106
|
334 if (id == "") {
|
Chris@106
|
335 throw LayoutException("Changeset has no ID");
|
Chris@106
|
336 }
|
Chris@106
|
337 if (m_changesets.contains(id)) {
|
Chris@106
|
338 throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
|
Chris@106
|
339 }
|
cannam@45
|
340
|
Chris@106
|
341 m_changesets[id] = cs;
|
cannam@45
|
342
|
cannam@45
|
343 ChangesetItem *item = new ChangesetItem(cs);
|
cannam@45
|
344 item->setX(0);
|
cannam@45
|
345 item->setY(0);
|
Chris@106
|
346 m_items[id] = item;
|
cannam@45
|
347 m_scene->addItem(item);
|
Chris@119
|
348 QObject::connect(item, SIGNAL(detailShown()),
|
Chris@119
|
349 m_scene, SLOT(changesetDetailShown()));
|
cannam@45
|
350 }
|
cannam@45
|
351
|
Chris@74
|
352 // Add the connecting lines
|
Chris@74
|
353
|
Chris@46
|
354 foreach (Changeset *cs, csets) {
|
Chris@106
|
355 QString id = cs->id();
|
Chris@106
|
356 ChangesetItem *item = m_items[id];
|
Chris@106
|
357 bool merge = (cs->parents().size() > 1);
|
Chris@106
|
358 foreach (QString parentId, cs->parents()) {
|
Chris@106
|
359 if (!m_changesets.contains(parentId)) continue;
|
Chris@106
|
360 Changeset *parent = m_changesets[parentId];
|
Chris@106
|
361 parent->addChild(id);
|
Chris@106
|
362 ConnectionItem *conn = new ConnectionItem();
|
Chris@106
|
363 if (merge) conn->setConnectionType(ConnectionItem::Merge);
|
Chris@106
|
364 conn->setChild(item);
|
Chris@106
|
365 conn->setParent(m_items[parentId]);
|
Chris@106
|
366 m_scene->addItem(conn);
|
Chris@106
|
367 }
|
Chris@46
|
368 }
|
Chris@46
|
369
|
Chris@74
|
370 // Add the branch labels
|
Chris@74
|
371 foreach (Changeset *cs, csets) {
|
Chris@74
|
372 QString id = cs->id();
|
Chris@74
|
373 ChangesetItem *item = m_items[id];
|
Chris@74
|
374 bool haveChildOnSameBranch = false;
|
Chris@74
|
375 foreach (QString childId, cs->children()) {
|
Chris@74
|
376 Changeset *child = m_changesets[childId];
|
Chris@74
|
377 if (child->branch() == cs->branch()) {
|
Chris@74
|
378 haveChildOnSameBranch = true;
|
Chris@74
|
379 break;
|
Chris@74
|
380 }
|
Chris@74
|
381 }
|
Chris@74
|
382 item->setShowBranch(!haveChildOnSameBranch);
|
Chris@74
|
383 }
|
Chris@74
|
384
|
Chris@52
|
385 // We need to lay out the changesets in forward chronological
|
Chris@52
|
386 // order. We have no guarantees about the order in which
|
Chris@52
|
387 // changesets appear in the list -- in a simple repository they
|
Chris@52
|
388 // will generally be reverse chronological, but that's far from
|
Chris@52
|
389 // guaranteed. So, sort explicitly using the date comparator
|
Chris@52
|
390 // above
|
Chris@51
|
391
|
Chris@52
|
392 qStableSort(csets.begin(), csets.end(), compareChangesetsByDate);
|
Chris@51
|
393
|
Chris@53
|
394 foreach (Changeset *cs, csets) {
|
Chris@114
|
395 DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp() << ", date " << cs->datetime().toStdString() << endl;
|
Chris@53
|
396 }
|
Chris@53
|
397
|
cannam@45
|
398 m_handled.clear();
|
Chris@53
|
399 foreach (Changeset *cs, csets) {
|
Chris@106
|
400 layoutRow(cs->id());
|
cannam@45
|
401 }
|
Chris@46
|
402
|
Chris@46
|
403 allocateBranchHomes(csets);
|
Chris@46
|
404
|
cannam@45
|
405 m_handled.clear();
|
Chris@53
|
406 foreach (Changeset *cs, csets) {
|
Chris@106
|
407 foreach (QString parentId, cs->parents()) {
|
Chris@106
|
408 if (!m_handled.contains(parentId) &&
|
Chris@106
|
409 m_changesets.contains(parentId)) {
|
Chris@106
|
410 layoutCol(parentId);
|
Chris@106
|
411 }
|
Chris@106
|
412 }
|
Chris@106
|
413 layoutCol(cs->id());
|
Chris@53
|
414 }
|
Chris@53
|
415
|
Chris@55
|
416 foreach (Changeset *cs, csets) {
|
Chris@106
|
417 ChangesetItem *item = m_items[cs->id()];
|
Chris@106
|
418 if (!m_alloc[item->row()].contains(item->column()-1) &&
|
Chris@106
|
419 !m_alloc[item->row()].contains(item->column()+1)) {
|
Chris@106
|
420 item->setWide(true);
|
Chris@106
|
421 }
|
Chris@55
|
422 }
|
Chris@55
|
423
|
Chris@53
|
424 // we know that 0 is an upper bound on row, and that mincol must
|
Chris@53
|
425 // be <= 0 and maxcol >= 0, so these initial values are good
|
Chris@53
|
426 int minrow = 0, maxrow = 0;
|
Chris@53
|
427 int mincol = 0, maxcol = 0;
|
Chris@53
|
428
|
Chris@53
|
429 foreach (int r, m_alloc.keys()) {
|
Chris@106
|
430 if (r < minrow) minrow = r;
|
Chris@106
|
431 if (r > maxrow) maxrow = r;
|
Chris@106
|
432 ColumnSet &c = m_alloc[r];
|
Chris@106
|
433 foreach (int i, c) {
|
Chris@106
|
434 if (i < mincol) mincol = i;
|
Chris@106
|
435 if (i > maxcol) maxcol = i;
|
Chris@106
|
436 }
|
Chris@53
|
437 }
|
Chris@53
|
438
|
Chris@53
|
439 QString prevDate;
|
Chris@53
|
440 int changeRow = 0;
|
Chris@53
|
441
|
Chris@53
|
442 bool even = false;
|
Chris@53
|
443 int n = 0;
|
Chris@53
|
444
|
Chris@53
|
445 for (int row = minrow; row <= maxrow; ++row) {
|
Chris@53
|
446
|
Chris@106
|
447 QString date = m_rowDates[row];
|
Chris@106
|
448 n++;
|
Chris@106
|
449
|
Chris@106
|
450 if (date != prevDate) {
|
Chris@106
|
451 if (prevDate != "") {
|
Chris@106
|
452 DateItem *item = new DateItem();
|
Chris@106
|
453 item->setDateString(prevDate);
|
Chris@106
|
454 item->setCols(mincol, maxcol - mincol + 1);
|
Chris@106
|
455 item->setRows(changeRow, n);
|
Chris@106
|
456 item->setEven(even);
|
Chris@106
|
457 item->setZValue(-1);
|
Chris@106
|
458 m_scene->addItem(item);
|
Chris@106
|
459 even = !even;
|
Chris@106
|
460 }
|
Chris@106
|
461 prevDate = date;
|
Chris@106
|
462 changeRow = row;
|
Chris@106
|
463 n = 0;
|
Chris@106
|
464 }
|
Chris@53
|
465 }
|
Chris@53
|
466
|
Chris@53
|
467 if (n > 0) {
|
Chris@106
|
468 DateItem *item = new DateItem();
|
Chris@106
|
469 item->setDateString(prevDate);
|
Chris@106
|
470 item->setCols(mincol, maxcol - mincol + 1);
|
Chris@106
|
471 item->setRows(changeRow, n+1);
|
Chris@106
|
472 item->setEven(even);
|
Chris@106
|
473 item->setZValue(-1);
|
Chris@106
|
474 m_scene->addItem(item);
|
Chris@106
|
475 even = !even;
|
cannam@45
|
476 }
|
Chris@44
|
477 }
|
Chris@44
|
478
|