comparison data/model/TextModel.h @ 1661:353a2d15f213 single-point

Update TextModel
author Chris Cannam
date Fri, 22 Mar 2019 11:04:51 +0000
parents ad5f892c0c4d
children 82d03c9661f9
comparison
equal deleted inserted replaced
1660:b234d4d011df 1661:353a2d15f213
2 2
3 /* 3 /*
4 Sonic Visualiser 4 Sonic Visualiser
5 An audio file viewer and annotation editor. 5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London. 6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006 Chris Cannam.
8 7
9 This program is free software; you can redistribute it and/or 8 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as 9 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the 10 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file 11 License, or (at your option) any later version. See the file
14 */ 13 */
15 14
16 #ifndef SV_TEXT_MODEL_H 15 #ifndef SV_TEXT_MODEL_H
17 #define SV_TEXT_MODEL_H 16 #define SV_TEXT_MODEL_H
18 17
19 #include "SparseModel.h" 18 #include "EventCommands.h"
19 #include "TabularModel.h"
20 #include "Model.h"
21 #include "DeferredNotifier.h"
22
23 #include "base/EventSeries.h"
20 #include "base/XmlExportable.h" 24 #include "base/XmlExportable.h"
21 #include "base/RealTime.h" 25 #include "base/RealTime.h"
22 26
27 #include "system/System.h"
28
23 #include <QStringList> 29 #include <QStringList>
24 30
25 /** 31 /**
26 * Text point type for use in a SparseModel. This represents a piece 32 * A model representing casual textual annotations. A piece of text
27 * of text at a given time and y-value in the [0,1) range (indicative 33 * has a given time and y-value in the [0,1) range (indicative of
28 * of height on the window). Intended for casual textual annotations. 34 * height on the window).
29 */ 35 */
30 36 class TextModel : public Model,
31 struct TextPoint : public XmlExportable 37 public TabularModel,
32 { 38 public EventEditable
33 public:
34 TextPoint(sv_frame_t _frame) : frame(_frame), height(0.0f) { }
35 TextPoint(sv_frame_t _frame, float _height, QString _label) :
36 frame(_frame), height(_height), label(_label) { }
37
38 int getDimensions() const { return 2; }
39
40 sv_frame_t frame;
41 float height;
42 QString label;
43
44 QString getLabel() const { return label; }
45
46 void toXml(QTextStream &stream, QString indent = "",
47 QString extraAttributes = "") const override
48 {
49 stream << QString("%1<point frame=\"%2\" height=\"%3\" label=\"%4\" %5/>\n")
50 .arg(indent).arg(frame).arg(height)
51 .arg(encodeEntities(label)).arg(extraAttributes);
52 }
53
54 QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
55 {
56 QStringList list;
57 list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
58 list << QString("%1").arg(height);
59 if (label != "") list << label;
60 return list.join(delimiter);
61 }
62
63 struct Comparator {
64 bool operator()(const TextPoint &p1,
65 const TextPoint &p2) const {
66 if (p1.frame != p2.frame) return p1.frame < p2.frame;
67 if (p1.height != p2.height) return p1.height < p2.height;
68 return p1.label < p2.label;
69 }
70 };
71
72 struct OrderComparator {
73 bool operator()(const TextPoint &p1,
74 const TextPoint &p2) const {
75 return p1.frame < p2.frame;
76 }
77 };
78 };
79
80
81 // Make this a class rather than a typedef so it can be predeclared.
82
83 class TextModel : public SparseModel<TextPoint>
84 { 39 {
85 Q_OBJECT 40 Q_OBJECT
86 41
87 public: 42 public:
88 TextModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) : 43 TextModel(sv_samplerate_t sampleRate,
89 SparseModel<TextPoint>(sampleRate, resolution, notifyOnAdd) 44 int resolution,
90 { } 45 bool notifyOnAdd = true) :
91 46 m_sampleRate(sampleRate),
92 void toXml(QTextStream &out, 47 m_resolution(resolution),
93 QString indent = "", 48 m_notifier(this,
94 QString extraAttributes = "") const override 49 notifyOnAdd ?
95 { 50 DeferredNotifier::NOTIFY_ALWAYS :
96 SparseModel<TextPoint>::toXml 51 DeferredNotifier::NOTIFY_DEFERRED),
97 (out, 52 m_completion(100) {
98 indent,
99 QString("%1 subtype=\"text\"")
100 .arg(extraAttributes));
101 } 53 }
102 54
103 QString getTypeName() const override { return tr("Text"); } 55 QString getTypeName() const override { return tr("Text"); }
56 bool isSparse() const { return true; }
57 bool isOK() const override { return true; }
58
59 sv_frame_t getStartFrame() const override {
60 return m_events.getStartFrame();
61 }
62 sv_frame_t getEndFrame() const override {
63 if (m_events.isEmpty()) return 0;
64 sv_frame_t e = m_events.getEndFrame() + 1;
65 if (e % m_resolution == 0) return e;
66 else return (e / m_resolution + 1) * m_resolution;
67 }
68
69 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
70 int getResolution() const { return m_resolution; }
71
72 int getCompletion() const { return m_completion; }
73
74 void setCompletion(int completion, bool update = true) {
75
76 { QMutexLocker locker(&m_mutex);
77 if (m_completion == completion) return;
78 m_completion = completion;
79 }
80
81 if (update) {
82 m_notifier.makeDeferredNotifications();
83 }
84
85 emit completionChanged();
86
87 if (completion == 100) {
88 // henceforth:
89 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
90 emit modelChanged();
91 }
92 }
93
94 /**
95 * Query methods.
96 */
97
98 int getEventCount() const {
99 return m_events.count();
100 }
101 bool isEmpty() const {
102 return m_events.isEmpty();
103 }
104 bool containsEvent(const Event &e) const {
105 return m_events.contains(e);
106 }
107 EventVector getAllEvents() const {
108 return m_events.getAllEvents();
109 }
110 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
111 return m_events.getEventsSpanning(f, duration);
112 }
113 EventVector getEventsCovering(sv_frame_t f) const {
114 return m_events.getEventsCovering(f);
115 }
116 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
117 int overspill = 0) const {
118 return m_events.getEventsWithin(f, duration, overspill);
119 }
120 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
121 return m_events.getEventsStartingWithin(f, duration);
122 }
123 EventVector getEventsStartingAt(sv_frame_t f) const {
124 return m_events.getEventsStartingAt(f);
125 }
126 bool getNearestEventMatching(sv_frame_t startSearchAt,
127 std::function<bool(Event)> predicate,
128 EventSeries::Direction direction,
129 Event &found) const {
130 return m_events.getNearestEventMatching
131 (startSearchAt, predicate, direction, found);
132 }
133
134 /**
135 * Editing methods.
136 */
137 void add(Event e) override {
138
139 { QMutexLocker locker(&m_mutex);
140 m_events.add(e.withoutDuration());
141 }
142
143 m_notifier.update(e.getFrame(), m_resolution);
144 }
145
146 void remove(Event e) override {
147 { QMutexLocker locker(&m_mutex);
148 m_events.remove(e);
149 }
150 emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution);
151 }
104 152
105 /** 153 /**
106 * TabularModel methods. 154 * TabularModel methods.
107 */ 155 */
108 156
109 int getColumnCount() const override 157 int getRowCount() const override {
110 { 158 return m_events.count();
159 }
160
161 int getColumnCount() const override {
111 return 4; 162 return 4;
112 } 163 }
113 164
114 QString getHeading(int column) const override 165 bool isColumnTimeValue(int column) const override {
115 { 166 return (column < 2);
167 }
168
169 sv_frame_t getFrameForRow(int row) const override {
170 if (row < 0 || row >= m_events.count()) {
171 return 0;
172 }
173 Event e = m_events.getEventByIndex(row);
174 return e.getFrame();
175 }
176
177 int getRowForFrame(sv_frame_t frame) const override {
178 return m_events.getIndexForEvent(Event(frame));
179 }
180
181 QString getHeading(int column) const override {
116 switch (column) { 182 switch (column) {
117 case 0: return tr("Time"); 183 case 0: return tr("Time");
118 case 1: return tr("Frame"); 184 case 1: return tr("Frame");
119 case 2: return tr("Height"); 185 case 2: return tr("Height");
120 case 3: return tr("Label"); 186 case 3: return tr("Label");
121 default: return tr("Unknown"); 187 default: return tr("Unknown");
122 } 188 }
123 } 189 }
124 190
125 QVariant getData(int row, int column, int role) const override 191 SortType getSortType(int column) const override {
126 {
127 if (column < 2) {
128 return SparseModel<TextPoint>::getData
129 (row, column, role);
130 }
131
132 PointListConstIterator i = getPointListIteratorForRow(row);
133 if (i == m_points.end()) return QVariant();
134
135 switch (column) {
136 case 2: return i->height;
137 case 3: return i->label;
138 default: return QVariant();
139 }
140 }
141
142 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override
143 {
144 if (column < 2) {
145 return SparseModel<TextPoint>::getSetDataCommand
146 (row, column, value, role);
147 }
148
149 if (role != Qt::EditRole) return 0;
150 PointListIterator i = getPointListIteratorForRow(row);
151 if (i == m_points.end()) return 0;
152 EditCommand *command = new EditCommand(this, tr("Edit Data"));
153
154 Point point(*i);
155 command->deletePoint(point);
156
157 switch (column) {
158 case 2: point.height = float(value.toDouble()); break;
159 case 3: point.label = value.toString(); break;
160 }
161
162 command->addPoint(point);
163 return command->finish();
164 }
165
166 bool isColumnTimeValue(int column) const override
167 {
168 return (column < 2);
169 }
170
171 SortType getSortType(int column) const override
172 {
173 if (column == 3) return SortAlphabetical; 192 if (column == 3) return SortAlphabetical;
174 return SortNumeric; 193 return SortNumeric;
175 } 194 }
176 195
196 QVariant getData(int row, int column, int role) const override {
197
198 if (row < 0 || row >= m_events.count()) {
199 return QVariant();
200 }
201
202 Event e = m_events.getEventByIndex(row);
203
204 switch (column) {
205 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
206 case 1: return int(e.getFrame());
207 case 2: return e.getValue();
208 case 3: return e.getLabel();
209 default: return QVariant();
210 }
211 }
212
213 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
214
215 if (row < 0 || row >= m_events.count()) return nullptr;
216 if (role != Qt::EditRole) return nullptr;
217
218 Event e0 = m_events.getEventByIndex(row);
219 Event e1;
220
221 switch (column) {
222 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
223 getSampleRate()))); break;
224 case 1: e1 = e0.withFrame(value.toInt()); break;
225 case 2: e1 = e0.withValue(float(value.toDouble())); break;
226 case 3: e1 = e0.withLabel(value.toString()); break;
227 }
228
229 ChangeEventsCommand *command =
230 new ChangeEventsCommand(this, tr("Edit Data"));
231 command->remove(e0);
232 command->add(e1);
233 return command->finish();
234 }
235
236 /**
237 * XmlExportable methods.
238 */
239 void toXml(QTextStream &out,
240 QString indent = "",
241 QString extraAttributes = "") const override {
242
243 Model::toXml
244 (out,
245 indent,
246 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
247 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"text\" %4")
248 .arg(m_resolution)
249 .arg("true") // always true after model reaches 100% -
250 // subsequent events are always notified
251 .arg(getObjectExportId(&m_events))
252 .arg(extraAttributes));
253
254 m_events.toXml(out, indent, QString("dimensions=\"2\""));
255 }
256
257 protected:
258 sv_samplerate_t m_sampleRate;
259 int m_resolution;
260
261 DeferredNotifier m_notifier;
262 int m_completion;
263
264 EventSeries m_events;
265
266 mutable QMutex m_mutex;
267
177 }; 268 };
178 269
179 270
180 #endif 271 #endif
181 272