Mercurial > hg > svcore
comparison data/model/SparseModel.h @ 147:3a13b0d4934e
* Reorganising code base. This revision will not compile.
author | Chris Cannam |
---|---|
date | Mon, 31 Jul 2006 11:44:37 +0000 |
parents | |
children | 4b2ea82fd0ed |
comparison
equal
deleted
inserted
replaced
146:f90fad823cea | 147:3a13b0d4934e |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 This file copyright 2006 Chris Cannam. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #ifndef _SPARSE_MODEL_H_ | |
17 #define _SPARSE_MODEL_H_ | |
18 | |
19 #include "base/Model.h" | |
20 #include "base/Command.h" | |
21 #include "base/CommandHistory.h" | |
22 | |
23 #include <iostream> | |
24 | |
25 #include <set> | |
26 #include <QMutex> | |
27 #include <QTextStream> | |
28 | |
29 | |
30 /** | |
31 * Model containing sparse data (points with some properties). The | |
32 * properties depend on the point type. | |
33 */ | |
34 | |
35 template <typename PointType> | |
36 class SparseModel : public Model | |
37 { | |
38 public: | |
39 SparseModel(size_t sampleRate, size_t resolution, | |
40 bool notifyOnAdd = true); | |
41 virtual ~SparseModel() { } | |
42 | |
43 virtual bool isOK() const { return true; } | |
44 virtual size_t getStartFrame() const; | |
45 virtual size_t getEndFrame() const; | |
46 virtual size_t getSampleRate() const { return m_sampleRate; } | |
47 | |
48 virtual Model *clone() const; | |
49 | |
50 // Number of frames of the underlying sample rate that this model | |
51 // is capable of resolving to. For example, if m_resolution == 10 | |
52 // then every point in this model will be at a multiple of 10 | |
53 // sample frames and should be considered to cover a window ending | |
54 // 10 sample frames later. | |
55 virtual size_t getResolution() const { | |
56 return m_resolution ? m_resolution : 1; | |
57 } | |
58 virtual void setResolution(size_t resolution); | |
59 | |
60 typedef PointType Point; | |
61 typedef std::multiset<PointType, | |
62 typename PointType::OrderComparator> PointList; | |
63 typedef typename PointList::iterator PointListIterator; | |
64 | |
65 /** | |
66 * Return whether the model is empty or not. | |
67 */ | |
68 virtual bool isEmpty() const; | |
69 | |
70 /** | |
71 * Get the total number of points in the model. | |
72 */ | |
73 virtual size_t getPointCount() const; | |
74 | |
75 /** | |
76 * Get all of the points in this model between the given | |
77 * boundaries (in frames), as well as up to two points before and | |
78 * after the boundaries. If you need exact boundaries, check the | |
79 * point coordinates in the returned list. | |
80 */ | |
81 virtual PointList getPoints(long start, long end) const; | |
82 | |
83 /** | |
84 * Get all points that cover the given frame number, taking the | |
85 * resolution of the model into account. | |
86 */ | |
87 virtual PointList getPoints(long frame) const; | |
88 | |
89 /** | |
90 * Return all points that share the nearest frame number prior to | |
91 * the given one at which there are any points. | |
92 */ | |
93 virtual PointList getPreviousPoints(long frame) const; | |
94 | |
95 /** | |
96 * Return all points that share the nearest frame number | |
97 * subsequent to the given one at which there are any points. | |
98 */ | |
99 virtual PointList getNextPoints(long frame) const; | |
100 | |
101 /** | |
102 * Remove all points. | |
103 */ | |
104 virtual void clear(); | |
105 | |
106 /** | |
107 * Add a point. | |
108 */ | |
109 virtual void addPoint(const PointType &point); | |
110 | |
111 /** | |
112 * Remove a point. Points are not necessarily unique, so this | |
113 * function will remove the first point that compares equal to the | |
114 * supplied one using Point::Comparator. Other identical points | |
115 * may remain in the model. | |
116 */ | |
117 virtual void deletePoint(const PointType &point); | |
118 | |
119 virtual void setCompletion(int completion); | |
120 virtual int getCompletion() const { return m_completion; } | |
121 | |
122 virtual bool hasTextLabels() const { return m_hasTextLabels; } | |
123 | |
124 virtual void toXml(QTextStream &out, | |
125 QString indent = "", | |
126 QString extraAttributes = "") const; | |
127 | |
128 virtual QString toXmlString(QString indent = "", | |
129 QString extraAttributes = "") const; | |
130 | |
131 virtual QString toDelimitedDataString(QString delimiter) const | |
132 { | |
133 QString s; | |
134 for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) { | |
135 s += i->toDelimitedDataString(delimiter, m_sampleRate) + "\n"; | |
136 } | |
137 return s; | |
138 } | |
139 | |
140 /** | |
141 * Command to add a point, with undo. | |
142 */ | |
143 class AddPointCommand : public Command | |
144 { | |
145 public: | |
146 AddPointCommand(SparseModel<PointType> *model, | |
147 const PointType &point, | |
148 QString name = "") : | |
149 m_model(model), m_point(point), m_name(name) { } | |
150 | |
151 virtual QString getName() const { | |
152 return (m_name == "" ? tr("Add Point") : m_name); | |
153 } | |
154 | |
155 virtual void execute() { m_model->addPoint(m_point); } | |
156 virtual void unexecute() { m_model->deletePoint(m_point); } | |
157 | |
158 const PointType &getPoint() const { return m_point; } | |
159 | |
160 private: | |
161 SparseModel<PointType> *m_model; | |
162 PointType m_point; | |
163 QString m_name; | |
164 }; | |
165 | |
166 | |
167 /** | |
168 * Command to remove a point, with undo. | |
169 */ | |
170 class DeletePointCommand : public Command | |
171 { | |
172 public: | |
173 DeletePointCommand(SparseModel<PointType> *model, | |
174 const PointType &point) : | |
175 m_model(model), m_point(point) { } | |
176 | |
177 virtual QString getName() const { return tr("Delete Point"); } | |
178 | |
179 virtual void execute() { m_model->deletePoint(m_point); } | |
180 virtual void unexecute() { m_model->addPoint(m_point); } | |
181 | |
182 const PointType &getPoint() const { return m_point; } | |
183 | |
184 private: | |
185 SparseModel<PointType> *m_model; | |
186 PointType m_point; | |
187 }; | |
188 | |
189 | |
190 /** | |
191 * Command to add or remove a series of points, with undo. | |
192 * Consecutive add/remove pairs for the same point are collapsed. | |
193 */ | |
194 class EditCommand : public MacroCommand | |
195 { | |
196 public: | |
197 EditCommand(SparseModel<PointType> *model, QString commandName); | |
198 | |
199 virtual void addPoint(const PointType &point); | |
200 virtual void deletePoint(const PointType &point); | |
201 | |
202 /** | |
203 * Stack an arbitrary other command in the same sequence. | |
204 */ | |
205 virtual void addCommand(Command *command) { addCommand(command, true); } | |
206 | |
207 /** | |
208 * If any points have been added or deleted, add this command | |
209 * to the command history. Otherwise delete the command. | |
210 */ | |
211 virtual void finish(); | |
212 | |
213 protected: | |
214 virtual void addCommand(Command *command, bool executeFirst); | |
215 | |
216 SparseModel<PointType> *m_model; | |
217 }; | |
218 | |
219 | |
220 /** | |
221 * Command to relabel a point. | |
222 */ | |
223 class RelabelCommand : public Command | |
224 { | |
225 public: | |
226 RelabelCommand(SparseModel<PointType> *model, | |
227 const PointType &point, | |
228 QString newLabel) : | |
229 m_model(model), m_oldPoint(point), m_newPoint(point) { | |
230 m_newPoint.label = newLabel; | |
231 } | |
232 | |
233 virtual QString getName() const { return tr("Re-Label Point"); } | |
234 | |
235 virtual void execute() { | |
236 m_model->deletePoint(m_oldPoint); | |
237 m_model->addPoint(m_newPoint); | |
238 std::swap(m_oldPoint, m_newPoint); | |
239 } | |
240 | |
241 virtual void unexecute() { execute(); } | |
242 | |
243 private: | |
244 SparseModel<PointType> *m_model; | |
245 PointType m_oldPoint; | |
246 PointType m_newPoint; | |
247 }; | |
248 | |
249 | |
250 | |
251 protected: | |
252 size_t m_sampleRate; | |
253 size_t m_resolution; | |
254 bool m_notifyOnAdd; | |
255 long m_sinceLastNotifyMin; | |
256 long m_sinceLastNotifyMax; | |
257 bool m_hasTextLabels; | |
258 | |
259 PointList m_points; | |
260 size_t m_pointCount; | |
261 mutable QMutex m_mutex; | |
262 int m_completion; | |
263 }; | |
264 | |
265 | |
266 template <typename PointType> | |
267 SparseModel<PointType>::SparseModel(size_t sampleRate, | |
268 size_t resolution, | |
269 bool notifyOnAdd) : | |
270 m_sampleRate(sampleRate), | |
271 m_resolution(resolution), | |
272 m_notifyOnAdd(notifyOnAdd), | |
273 m_sinceLastNotifyMin(-1), | |
274 m_sinceLastNotifyMax(-1), | |
275 m_hasTextLabels(false), | |
276 m_pointCount(0), | |
277 m_completion(100) | |
278 { | |
279 } | |
280 | |
281 template <typename PointType> | |
282 size_t | |
283 SparseModel<PointType>::getStartFrame() const | |
284 { | |
285 QMutexLocker locker(&m_mutex); | |
286 size_t f = 0; | |
287 if (!m_points.empty()) { | |
288 f = m_points.begin()->frame; | |
289 } | |
290 return f; | |
291 } | |
292 | |
293 template <typename PointType> | |
294 size_t | |
295 SparseModel<PointType>::getEndFrame() const | |
296 { | |
297 QMutexLocker locker(&m_mutex); | |
298 size_t f = 0; | |
299 if (!m_points.empty()) { | |
300 PointListIterator i(m_points.end()); | |
301 f = (--i)->frame; | |
302 } | |
303 return f; | |
304 } | |
305 | |
306 template <typename PointType> | |
307 Model * | |
308 SparseModel<PointType>::clone() const | |
309 { | |
310 SparseModel<PointType> *model = | |
311 new SparseModel<PointType>(m_sampleRate, m_resolution, m_notifyOnAdd); | |
312 model->m_points = m_points; | |
313 model->m_pointCount = m_pointCount; | |
314 return model; | |
315 } | |
316 | |
317 template <typename PointType> | |
318 bool | |
319 SparseModel<PointType>::isEmpty() const | |
320 { | |
321 return m_pointCount == 0; | |
322 } | |
323 | |
324 template <typename PointType> | |
325 size_t | |
326 SparseModel<PointType>::getPointCount() const | |
327 { | |
328 return m_pointCount; | |
329 } | |
330 | |
331 template <typename PointType> | |
332 typename SparseModel<PointType>::PointList | |
333 SparseModel<PointType>::getPoints(long start, long end) const | |
334 { | |
335 if (start > end) return PointList(); | |
336 QMutexLocker locker(&m_mutex); | |
337 | |
338 PointType startPoint(start), endPoint(end); | |
339 | |
340 PointListIterator startItr = m_points.lower_bound(startPoint); | |
341 PointListIterator endItr = m_points.upper_bound(endPoint); | |
342 | |
343 if (startItr != m_points.begin()) --startItr; | |
344 if (startItr != m_points.begin()) --startItr; | |
345 if (endItr != m_points.end()) ++endItr; | |
346 if (endItr != m_points.end()) ++endItr; | |
347 | |
348 PointList rv; | |
349 | |
350 for (PointListIterator i = startItr; i != endItr; ++i) { | |
351 rv.insert(*i); | |
352 } | |
353 | |
354 return rv; | |
355 } | |
356 | |
357 template <typename PointType> | |
358 typename SparseModel<PointType>::PointList | |
359 SparseModel<PointType>::getPoints(long frame) const | |
360 { | |
361 QMutexLocker locker(&m_mutex); | |
362 | |
363 if (m_resolution == 0) return PointList(); | |
364 | |
365 long start = (frame / m_resolution) * m_resolution; | |
366 long end = start + m_resolution; | |
367 | |
368 PointType startPoint(start), endPoint(end); | |
369 | |
370 PointListIterator startItr = m_points.lower_bound(startPoint); | |
371 PointListIterator endItr = m_points.upper_bound(endPoint); | |
372 | |
373 PointList rv; | |
374 | |
375 for (PointListIterator i = startItr; i != endItr; ++i) { | |
376 rv.insert(*i); | |
377 } | |
378 | |
379 return rv; | |
380 } | |
381 | |
382 template <typename PointType> | |
383 typename SparseModel<PointType>::PointList | |
384 SparseModel<PointType>::getPreviousPoints(long originFrame) const | |
385 { | |
386 QMutexLocker locker(&m_mutex); | |
387 | |
388 PointType lookupPoint(originFrame); | |
389 PointList rv; | |
390 | |
391 PointListIterator i = m_points.lower_bound(lookupPoint); | |
392 if (i == m_points.begin()) return rv; | |
393 | |
394 --i; | |
395 long frame = i->frame; | |
396 while (i->frame == frame) { | |
397 rv.insert(*i); | |
398 if (i == m_points.begin()) break; | |
399 --i; | |
400 } | |
401 | |
402 return rv; | |
403 } | |
404 | |
405 template <typename PointType> | |
406 typename SparseModel<PointType>::PointList | |
407 SparseModel<PointType>::getNextPoints(long originFrame) const | |
408 { | |
409 QMutexLocker locker(&m_mutex); | |
410 | |
411 PointType lookupPoint(originFrame); | |
412 PointList rv; | |
413 | |
414 PointListIterator i = m_points.upper_bound(lookupPoint); | |
415 if (i == m_points.end()) return rv; | |
416 | |
417 long frame = i->frame; | |
418 while (i != m_points.end() && i->frame == frame) { | |
419 rv.insert(*i); | |
420 ++i; | |
421 } | |
422 | |
423 return rv; | |
424 } | |
425 | |
426 template <typename PointType> | |
427 void | |
428 SparseModel<PointType>::setResolution(size_t resolution) | |
429 { | |
430 { | |
431 QMutexLocker locker(&m_mutex); | |
432 m_resolution = resolution; | |
433 } | |
434 emit modelChanged(); | |
435 } | |
436 | |
437 template <typename PointType> | |
438 void | |
439 SparseModel<PointType>::clear() | |
440 { | |
441 { | |
442 QMutexLocker locker(&m_mutex); | |
443 m_points.clear(); | |
444 m_pointCount = 0; | |
445 } | |
446 emit modelChanged(); | |
447 } | |
448 | |
449 template <typename PointType> | |
450 void | |
451 SparseModel<PointType>::addPoint(const PointType &point) | |
452 { | |
453 // std::cout << "SparseModel<Point>::addPoint(" << point.frame << ", " | |
454 // << point.value << ")" << std::endl; | |
455 | |
456 { | |
457 QMutexLocker locker(&m_mutex); | |
458 m_points.insert(point); | |
459 m_pointCount++; | |
460 if (point.label != "") m_hasTextLabels = true; | |
461 } | |
462 | |
463 // Even though this model is nominally sparse, there may still be | |
464 // too many signals going on here (especially as they'll probably | |
465 // be queued from one thread to another), which is why we need the | |
466 // notifyOnAdd as an option rather than a necessity (the | |
467 // alternative is to notify on setCompletion). | |
468 | |
469 if (m_notifyOnAdd) { | |
470 emit modelChanged(point.frame, point.frame + m_resolution); | |
471 } else { | |
472 if (m_sinceLastNotifyMin == -1 || | |
473 point.frame < m_sinceLastNotifyMin) { | |
474 m_sinceLastNotifyMin = point.frame; | |
475 } | |
476 if (m_sinceLastNotifyMax == -1 || | |
477 point.frame > m_sinceLastNotifyMax) { | |
478 m_sinceLastNotifyMax = point.frame; | |
479 } | |
480 } | |
481 } | |
482 | |
483 template <typename PointType> | |
484 void | |
485 SparseModel<PointType>::deletePoint(const PointType &point) | |
486 { | |
487 { | |
488 QMutexLocker locker(&m_mutex); | |
489 | |
490 PointListIterator i = m_points.lower_bound(point); | |
491 typename PointType::Comparator comparator; | |
492 while (i != m_points.end()) { | |
493 if (i->frame > point.frame) break; | |
494 if (!comparator(*i, point) && !comparator(point, *i)) { | |
495 m_points.erase(i); | |
496 m_pointCount--; | |
497 break; | |
498 } | |
499 ++i; | |
500 } | |
501 } | |
502 // std::cout << "SparseOneDimensionalModel: emit modelChanged(" | |
503 // << point.frame << ")" << std::endl; | |
504 emit modelChanged(point.frame, point.frame + m_resolution); | |
505 } | |
506 | |
507 template <typename PointType> | |
508 void | |
509 SparseModel<PointType>::setCompletion(int completion) | |
510 { | |
511 if (m_completion != completion) { | |
512 m_completion = completion; | |
513 | |
514 if (completion == 100) { | |
515 | |
516 m_notifyOnAdd = true; // henceforth | |
517 emit modelChanged(); | |
518 | |
519 } else if (!m_notifyOnAdd) { | |
520 | |
521 if (m_sinceLastNotifyMin >= 0 && | |
522 m_sinceLastNotifyMax >= 0) { | |
523 emit modelChanged(m_sinceLastNotifyMin, m_sinceLastNotifyMax); | |
524 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; | |
525 } else { | |
526 emit completionChanged(); | |
527 } | |
528 } else { | |
529 emit completionChanged(); | |
530 } | |
531 } | |
532 } | |
533 | |
534 template <typename PointType> | |
535 void | |
536 SparseModel<PointType>::toXml(QTextStream &out, | |
537 QString indent, | |
538 QString extraAttributes) const | |
539 { | |
540 Model::toXml | |
541 (out, | |
542 indent, | |
543 QString("type=\"sparse\" dimensions=\"%1\" resolution=\"%2\" notifyOnAdd=\"%3\" dataset=\"%4\" %5") | |
544 .arg(PointType(0).getDimensions()) | |
545 .arg(m_resolution) | |
546 .arg(m_notifyOnAdd ? "true" : "false") | |
547 .arg(getObjectExportId(&m_points)) | |
548 .arg(extraAttributes)); | |
549 | |
550 out << indent; | |
551 out << QString("<dataset id=\"%1\" dimensions=\"%2\">\n") | |
552 .arg(getObjectExportId(&m_points)) | |
553 .arg(PointType(0).getDimensions()); | |
554 | |
555 for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) { | |
556 out << i->toXmlString(indent + " "); | |
557 } | |
558 | |
559 out << indent; | |
560 out << "</dataset>\n"; | |
561 } | |
562 | |
563 template <typename PointType> | |
564 QString | |
565 SparseModel<PointType>::toXmlString(QString indent, | |
566 QString extraAttributes) const | |
567 { | |
568 QString s; | |
569 | |
570 { | |
571 QTextStream out(&s); | |
572 toXml(out, indent, extraAttributes); | |
573 } | |
574 | |
575 return s; | |
576 } | |
577 | |
578 template <typename PointType> | |
579 SparseModel<PointType>::EditCommand::EditCommand(SparseModel *model, | |
580 QString commandName) : | |
581 MacroCommand(commandName), | |
582 m_model(model) | |
583 { | |
584 } | |
585 | |
586 template <typename PointType> | |
587 void | |
588 SparseModel<PointType>::EditCommand::addPoint(const PointType &point) | |
589 { | |
590 addCommand(new AddPointCommand(m_model, point), true); | |
591 } | |
592 | |
593 template <typename PointType> | |
594 void | |
595 SparseModel<PointType>::EditCommand::deletePoint(const PointType &point) | |
596 { | |
597 addCommand(new DeletePointCommand(m_model, point), true); | |
598 } | |
599 | |
600 template <typename PointType> | |
601 void | |
602 SparseModel<PointType>::EditCommand::finish() | |
603 { | |
604 if (!m_commands.empty()) { | |
605 CommandHistory::getInstance()->addCommand(this, false); | |
606 } else { | |
607 delete this; | |
608 } | |
609 } | |
610 | |
611 template <typename PointType> | |
612 void | |
613 SparseModel<PointType>::EditCommand::addCommand(Command *command, | |
614 bool executeFirst) | |
615 { | |
616 if (executeFirst) command->execute(); | |
617 | |
618 if (!m_commands.empty()) { | |
619 DeletePointCommand *dpc = dynamic_cast<DeletePointCommand *>(command); | |
620 if (dpc) { | |
621 AddPointCommand *apc = dynamic_cast<AddPointCommand *> | |
622 (m_commands[m_commands.size() - 1]); | |
623 typename PointType::Comparator comparator; | |
624 if (apc) { | |
625 if (!comparator(apc->getPoint(), dpc->getPoint()) && | |
626 !comparator(dpc->getPoint(), apc->getPoint())) { | |
627 deleteCommand(apc); | |
628 return; | |
629 } | |
630 } | |
631 } | |
632 } | |
633 | |
634 MacroCommand::addCommand(command); | |
635 } | |
636 | |
637 | |
638 #endif | |
639 | |
640 | |
641 |