Mercurial > hg > svcore
comparison data/model/ImageModel.h @ 1663:a77a7e8c085c single-point
Update ImageModel
author | Chris Cannam |
---|---|
date | Fri, 22 Mar 2019 17:49:10 +0000 |
parents | ad5f892c0c4d |
children | 69ab62d378bf |
comparison
equal
deleted
inserted
replaced
1662:628ffbb05856 | 1663:a77a7e8c085c |
---|---|
14 */ | 14 */ |
15 | 15 |
16 #ifndef SV_IMAGE_MODEL_H | 16 #ifndef SV_IMAGE_MODEL_H |
17 #define SV_IMAGE_MODEL_H | 17 #define SV_IMAGE_MODEL_H |
18 | 18 |
19 #include "SparseModel.h" | 19 #include "EventCommands.h" |
20 #include "TabularModel.h" | |
21 #include "Model.h" | |
22 #include "DeferredNotifier.h" | |
23 | |
24 #include "base/EventSeries.h" | |
20 #include "base/XmlExportable.h" | 25 #include "base/XmlExportable.h" |
21 #include "base/RealTime.h" | 26 #include "base/RealTime.h" |
22 | 27 |
28 #include "system/System.h" | |
29 | |
23 #include <QStringList> | 30 #include <QStringList> |
24 | 31 |
25 /** | 32 /** |
26 * Image point type for use in a SparseModel. This represents an | 33 * A model representing image annotations, identified by filename or |
27 * image, identified by filename, at a given time. The filename can | 34 * URI, at a given time, with an optional label. The filename can be |
28 * be empty, in which case we instead have a space to put an image in. | 35 * empty, in which case we instead have a space to put an image in. |
29 */ | 36 */ |
30 | 37 |
31 struct ImagePoint : public XmlExportable | 38 class ImageModel : public Model, |
32 { | 39 public TabularModel, |
33 public: | 40 public EventEditable |
34 ImagePoint(sv_frame_t _frame) : frame(_frame) { } | |
35 ImagePoint(sv_frame_t _frame, QString _image, QString _label) : | |
36 frame(_frame), image(_image), label(_label) { } | |
37 | |
38 int getDimensions() const { return 1; } | |
39 | |
40 sv_frame_t frame; | |
41 QString image; | |
42 QString label; | |
43 | |
44 QString getLabel() const { return label; } | |
45 | |
46 void toXml(QTextStream &stream, | |
47 QString indent = "", | |
48 QString extraAttributes = "") const override | |
49 { | |
50 stream << | |
51 QString("%1<point frame=\"%2\" image=\"%3\" label=\"%4\" %5/>\n") | |
52 .arg(indent).arg(frame) | |
53 .arg(encodeEntities(image)) | |
54 .arg(encodeEntities(label)) | |
55 .arg(extraAttributes); | |
56 } | |
57 | |
58 QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const | |
59 { | |
60 QStringList list; | |
61 list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); | |
62 list << image; | |
63 if (label != "") list << label; | |
64 return list.join(delimiter); | |
65 } | |
66 | |
67 struct Comparator { | |
68 bool operator()(const ImagePoint &p1, | |
69 const ImagePoint &p2) const { | |
70 if (p1.frame != p2.frame) return p1.frame < p2.frame; | |
71 if (p1.label != p2.label) return p1.label < p2.label; | |
72 return p1.image < p2.image; | |
73 } | |
74 }; | |
75 | |
76 struct OrderComparator { | |
77 bool operator()(const ImagePoint &p1, | |
78 const ImagePoint &p2) const { | |
79 return p1.frame < p2.frame; | |
80 } | |
81 }; | |
82 }; | |
83 | |
84 | |
85 // Make this a class rather than a typedef so it can be predeclared. | |
86 | |
87 class ImageModel : public SparseModel<ImagePoint> | |
88 { | 41 { |
89 Q_OBJECT | 42 Q_OBJECT |
90 | 43 |
91 public: | 44 public: |
92 ImageModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) : | 45 ImageModel(sv_samplerate_t sampleRate, |
93 SparseModel<ImagePoint>(sampleRate, resolution, notifyOnAdd) | 46 int resolution, |
94 { } | 47 bool notifyOnAdd = true) : |
48 m_sampleRate(sampleRate), | |
49 m_resolution(resolution), | |
50 m_notifier(this, | |
51 notifyOnAdd ? | |
52 DeferredNotifier::NOTIFY_ALWAYS : | |
53 DeferredNotifier::NOTIFY_DEFERRED), | |
54 m_completion(100) { | |
55 } | |
95 | 56 |
96 QString getTypeName() const override { return tr("Image"); } | 57 QString getTypeName() const override { return tr("Image"); } |
97 | 58 bool isSparse() const { return true; } |
98 void toXml(QTextStream &out, | 59 bool isOK() const override { return true; } |
99 QString indent = "", | 60 |
100 QString extraAttributes = "") const override | 61 sv_frame_t getStartFrame() const override { |
101 { | 62 return m_events.getStartFrame(); |
102 SparseModel<ImagePoint>::toXml | 63 } |
103 (out, | 64 sv_frame_t getEndFrame() const override { |
104 indent, | 65 if (m_events.isEmpty()) return 0; |
105 QString("%1 subtype=\"image\"") | 66 sv_frame_t e = m_events.getEndFrame() + 1; |
106 .arg(extraAttributes)); | 67 if (e % m_resolution == 0) return e; |
107 } | 68 else return (e / m_resolution + 1) * m_resolution; |
108 | 69 } |
109 /** | 70 |
110 * Command to change the image for a point. | 71 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } |
111 */ | 72 int getResolution() const { return m_resolution; } |
112 class ChangeImageCommand : public Command | 73 |
113 { | 74 int getCompletion() const { return m_completion; } |
114 public: | 75 |
115 ChangeImageCommand(ImageModel *model, | 76 void setCompletion(int completion, bool update = true) { |
116 const ImagePoint &point, | 77 |
117 QString newImage, | 78 { QMutexLocker locker(&m_mutex); |
118 QString newLabel) : | 79 if (m_completion == completion) return; |
119 m_model(model), m_oldPoint(point), m_newPoint(point) { | 80 m_completion = completion; |
120 m_newPoint.image = newImage; | 81 } |
121 m_newPoint.label = newLabel; | 82 |
122 } | 83 if (update) { |
123 | 84 m_notifier.makeDeferredNotifications(); |
124 QString getName() const override { return tr("Edit Image"); } | 85 } |
125 | 86 |
126 void execute() override { | 87 emit completionChanged(); |
127 m_model->deletePoint(m_oldPoint); | 88 |
128 m_model->addPoint(m_newPoint); | 89 if (completion == 100) { |
129 std::swap(m_oldPoint, m_newPoint); | 90 // henceforth: |
130 } | 91 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); |
131 | 92 emit modelChanged(); |
132 void unexecute() override { execute(); } | 93 } |
133 | 94 } |
134 private: | 95 |
135 ImageModel *m_model; | 96 /** |
136 ImagePoint m_oldPoint; | 97 * Query methods. |
137 ImagePoint m_newPoint; | 98 */ |
138 }; | 99 |
100 //!!! todo: go through all models, weeding out the methods here | |
101 //!!! that are not used | |
102 | |
103 int getEventCount() const { | |
104 return m_events.count(); | |
105 } | |
106 bool isEmpty() const { | |
107 return m_events.isEmpty(); | |
108 } | |
109 bool containsEvent(const Event &e) const { | |
110 return m_events.contains(e); | |
111 } | |
112 EventVector getAllEvents() const { | |
113 return m_events.getAllEvents(); | |
114 } | |
115 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | |
116 return m_events.getEventsSpanning(f, duration); | |
117 } | |
118 EventVector getEventsCovering(sv_frame_t f) const { | |
119 return m_events.getEventsCovering(f); | |
120 } | |
121 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration, | |
122 int overspill = 0) const { | |
123 return m_events.getEventsWithin(f, duration, overspill); | |
124 } | |
125 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | |
126 return m_events.getEventsStartingWithin(f, duration); | |
127 } | |
128 EventVector getEventsStartingAt(sv_frame_t f) const { | |
129 return m_events.getEventsStartingAt(f); | |
130 } | |
131 bool getNearestEventMatching(sv_frame_t startSearchAt, | |
132 std::function<bool(Event)> predicate, | |
133 EventSeries::Direction direction, | |
134 Event &found) const { | |
135 return m_events.getNearestEventMatching | |
136 (startSearchAt, predicate, direction, found); | |
137 } | |
138 | |
139 /** | |
140 * Editing methods. | |
141 */ | |
142 void add(Event e) override { | |
143 | |
144 { QMutexLocker locker(&m_mutex); | |
145 m_events.add(e.withoutDuration()); | |
146 } | |
147 | |
148 m_notifier.update(e.getFrame(), m_resolution); | |
149 } | |
150 | |
151 void remove(Event e) override { | |
152 { QMutexLocker locker(&m_mutex); | |
153 m_events.remove(e); | |
154 } | |
155 emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); | |
156 } | |
139 | 157 |
140 /** | 158 /** |
141 * TabularModel methods. | 159 * TabularModel methods. |
142 */ | 160 */ |
143 | 161 |
144 int getColumnCount() const override | 162 int getRowCount() const override { |
145 { | 163 return m_events.count(); |
164 } | |
165 | |
166 int getColumnCount() const override { | |
146 return 4; | 167 return 4; |
147 } | 168 } |
148 | 169 |
149 QString getHeading(int column) const override | 170 bool isColumnTimeValue(int column) const override { |
150 { | 171 return (column < 2); |
172 } | |
173 | |
174 sv_frame_t getFrameForRow(int row) const override { | |
175 if (row < 0 || row >= m_events.count()) { | |
176 return 0; | |
177 } | |
178 Event e = m_events.getEventByIndex(row); | |
179 return e.getFrame(); | |
180 } | |
181 | |
182 int getRowForFrame(sv_frame_t frame) const override { | |
183 return m_events.getIndexForEvent(Event(frame)); | |
184 } | |
185 | |
186 QString getHeading(int column) const override { | |
151 switch (column) { | 187 switch (column) { |
152 case 0: return tr("Time"); | 188 case 0: return tr("Time"); |
153 case 1: return tr("Frame"); | 189 case 1: return tr("Frame"); |
154 case 2: return tr("Image"); | 190 case 2: return tr("Image"); |
155 case 3: return tr("Label"); | 191 case 3: return tr("Label"); |
156 default: return tr("Unknown"); | 192 default: return tr("Unknown"); |
157 } | 193 } |
158 } | 194 } |
159 | 195 |
160 QVariant getData(int row, int column, int role) const override | 196 SortType getSortType(int column) const override { |
161 { | 197 if (column >= 2) return SortAlphabetical; |
162 if (column < 2) { | 198 return SortNumeric; |
163 return SparseModel<ImagePoint>::getData | 199 } |
164 (row, column, role); | 200 |
165 } | 201 QVariant getData(int row, int column, int role) const override { |
166 | 202 |
167 PointListConstIterator i = getPointListIteratorForRow(row); | 203 if (row < 0 || row >= m_events.count()) { |
168 if (i == m_points.end()) return QVariant(); | 204 return QVariant(); |
205 } | |
206 | |
207 Event e = m_events.getEventByIndex(row); | |
169 | 208 |
170 switch (column) { | 209 switch (column) { |
171 case 2: return i->image; | 210 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); |
172 case 3: return i->label; | 211 case 1: return int(e.getFrame()); |
212 case 2: return e.getURI(); | |
213 case 3: return e.getLabel(); | |
173 default: return QVariant(); | 214 default: return QVariant(); |
174 } | 215 } |
175 } | 216 } |
176 | 217 |
177 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override | 218 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { |
178 { | 219 |
179 if (column < 2) { | 220 if (row < 0 || row >= m_events.count()) return nullptr; |
180 return SparseModel<ImagePoint>::getSetDataCommand | 221 if (role != Qt::EditRole) return nullptr; |
181 (row, column, value, role); | 222 |
182 } | 223 Event e0 = m_events.getEventByIndex(row); |
183 | 224 Event e1; |
184 if (role != Qt::EditRole) return 0; | |
185 PointListIterator i = getPointListIteratorForRow(row); | |
186 if (i == m_points.end()) return 0; | |
187 EditCommand *command = new EditCommand(this, tr("Edit Data")); | |
188 | |
189 Point point(*i); | |
190 command->deletePoint(point); | |
191 | 225 |
192 switch (column) { | 226 switch (column) { |
193 case 2: point.image = value.toString(); break; | 227 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * |
194 case 3: point.label = value.toString(); break; | 228 getSampleRate()))); break; |
195 } | 229 case 1: e1 = e0.withFrame(value.toInt()); break; |
196 | 230 case 2: e1 = e0.withURI(value.toString()); break; |
197 command->addPoint(point); | 231 case 3: e1 = e0.withLabel(value.toString()); break; |
232 } | |
233 | |
234 ChangeEventsCommand *command = | |
235 new ChangeEventsCommand(this, tr("Edit Data")); | |
236 command->remove(e0); | |
237 command->add(e1); | |
198 return command->finish(); | 238 return command->finish(); |
199 } | 239 } |
200 | 240 |
201 bool isColumnTimeValue(int column) const override | 241 /** |
202 { | 242 * XmlExportable methods. |
203 return (column < 2); | 243 */ |
204 } | 244 void toXml(QTextStream &out, |
205 | 245 QString indent = "", |
206 SortType getSortType(int column) const override | 246 QString extraAttributes = "") const override { |
207 { | 247 |
208 if (column > 2) return SortAlphabetical; | 248 Model::toXml |
209 return SortNumeric; | 249 (out, |
210 } | 250 indent, |
251 QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" " | |
252 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"image\" %4") | |
253 .arg(m_resolution) | |
254 .arg("true") // always true after model reaches 100% - | |
255 // subsequent events are always notified | |
256 .arg(getObjectExportId(&m_events)) | |
257 .arg(extraAttributes)); | |
258 | |
259 m_events.toXml(out, indent, QString("dimensions=\"1\"")); | |
260 } | |
261 | |
262 protected: | |
263 sv_samplerate_t m_sampleRate; | |
264 int m_resolution; | |
265 | |
266 DeferredNotifier m_notifier; | |
267 int m_completion; | |
268 | |
269 EventSeries m_events; | |
270 | |
271 mutable QMutex m_mutex; | |
211 }; | 272 }; |
212 | 273 |
213 | 274 |
214 #endif | 275 #endif |
215 | 276 |
216 | |
217 |