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