annotate data/model/ImageModel.h @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents c546429d4c2f
children
rev   line source
Chris@302 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@302 2
Chris@302 3 /*
Chris@302 4 Sonic Visualiser
Chris@302 5 An audio file viewer and annotation editor.
Chris@302 6 Centre for Digital Music, Queen Mary, University of London.
Chris@302 7 This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@302 8
Chris@302 9 This program is free software; you can redistribute it and/or
Chris@302 10 modify it under the terms of the GNU General Public License as
Chris@302 11 published by the Free Software Foundation; either version 2 of the
Chris@302 12 License, or (at your option) any later version. See the file
Chris@302 13 COPYING included with this distribution for more information.
Chris@302 14 */
Chris@302 15
Chris@1581 16 #ifndef SV_IMAGE_MODEL_H
Chris@1581 17 #define SV_IMAGE_MODEL_H
Chris@302 18
Chris@1663 19 #include "EventCommands.h"
Chris@1663 20 #include "TabularModel.h"
Chris@1663 21 #include "Model.h"
Chris@1663 22 #include "DeferredNotifier.h"
Chris@1663 23
Chris@1663 24 #include "base/EventSeries.h"
Chris@302 25 #include "base/XmlExportable.h"
Chris@302 26 #include "base/RealTime.h"
Chris@302 27
Chris@1663 28 #include "system/System.h"
Chris@1663 29
Chris@423 30 #include <QStringList>
Chris@423 31
Chris@302 32 /**
Chris@1663 33 * A model representing image annotations, identified by filename or
Chris@1663 34 * URI, at a given time, with an optional label. The filename can be
Chris@1663 35 * empty, in which case we instead have a space to put an image in.
Chris@302 36 */
Chris@302 37
Chris@1663 38 class ImageModel : public Model,
Chris@1663 39 public TabularModel,
Chris@1663 40 public EventEditable
Chris@302 41 {
Chris@423 42 Q_OBJECT
Chris@423 43
Chris@302 44 public:
Chris@1663 45 ImageModel(sv_samplerate_t sampleRate,
Chris@1663 46 int resolution,
Chris@1663 47 bool notifyOnAdd = true) :
Chris@1663 48 m_sampleRate(sampleRate),
Chris@1663 49 m_resolution(resolution),
Chris@1663 50 m_notifier(this,
Chris@1752 51 getId(),
Chris@1663 52 notifyOnAdd ?
Chris@1663 53 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1663 54 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1663 55 m_completion(100) {
Chris@1663 56 }
Chris@302 57
Chris@1580 58 QString getTypeName() const override { return tr("Image"); }
Chris@1692 59 bool isSparse() const override { return true; }
Chris@1663 60 bool isOK() const override { return true; }
Chris@345 61
Chris@1663 62 sv_frame_t getStartFrame() const override {
Chris@1663 63 return m_events.getStartFrame();
Chris@1663 64 }
Chris@1725 65 sv_frame_t getTrueEndFrame() const override {
Chris@1663 66 if (m_events.isEmpty()) return 0;
Chris@1663 67 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1663 68 if (e % m_resolution == 0) return e;
Chris@1663 69 else return (e / m_resolution + 1) * m_resolution;
Chris@1663 70 }
Chris@1663 71
Chris@1663 72 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1663 73 int getResolution() const { return m_resolution; }
Chris@1663 74
Chris@1692 75 int getCompletion() const override { return m_completion; }
Chris@1663 76
Chris@1663 77 void setCompletion(int completion, bool update = true) {
Chris@1663 78
Chris@1798 79 if (m_completion == completion) return;
Chris@1798 80 m_completion = completion;
Chris@1663 81
Chris@1663 82 if (update) {
Chris@1663 83 m_notifier.makeDeferredNotifications();
Chris@1663 84 }
Chris@1663 85
Chris@1752 86 emit completionChanged(getId());
Chris@1663 87
Chris@1663 88 if (completion == 100) {
Chris@1663 89 // henceforth:
Chris@1663 90 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 91 emit modelChanged(getId());
Chris@1663 92 }
Chris@1663 93 }
Chris@1663 94
Chris@1663 95 /**
Chris@1663 96 * Query methods.
Chris@1663 97 */
Chris@1663 98
Chris@1663 99 //!!! todo: go through all models, weeding out the methods here
Chris@1663 100 //!!! that are not used
Chris@1663 101
Chris@1663 102 int getEventCount() const {
Chris@1663 103 return m_events.count();
Chris@1663 104 }
Chris@1663 105 bool isEmpty() const {
Chris@1663 106 return m_events.isEmpty();
Chris@1663 107 }
Chris@1663 108 bool containsEvent(const Event &e) const {
Chris@1663 109 return m_events.contains(e);
Chris@1663 110 }
Chris@1663 111 EventVector getAllEvents() const {
Chris@1663 112 return m_events.getAllEvents();
Chris@1663 113 }
Chris@1663 114 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1663 115 return m_events.getEventsSpanning(f, duration);
Chris@1663 116 }
Chris@1663 117 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1663 118 return m_events.getEventsCovering(f);
Chris@1663 119 }
Chris@1663 120 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1663 121 int overspill = 0) const {
Chris@1663 122 return m_events.getEventsWithin(f, duration, overspill);
Chris@1663 123 }
Chris@1663 124 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1663 125 return m_events.getEventsStartingWithin(f, duration);
Chris@1663 126 }
Chris@1663 127 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1663 128 return m_events.getEventsStartingAt(f);
Chris@1663 129 }
Chris@1663 130 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1663 131 std::function<bool(Event)> predicate,
Chris@1663 132 EventSeries::Direction direction,
Chris@1663 133 Event &found) const {
Chris@1663 134 return m_events.getNearestEventMatching
Chris@1663 135 (startSearchAt, predicate, direction, found);
Chris@302 136 }
Chris@302 137
Chris@302 138 /**
Chris@1663 139 * Editing methods.
Chris@302 140 */
Chris@1663 141 void add(Event e) override {
Chris@1798 142 m_events.add(e.withoutDuration().withoutValue().withoutLevel());
Chris@1663 143 m_notifier.update(e.getFrame(), m_resolution);
Chris@1663 144 }
Chris@1663 145
Chris@1663 146 void remove(Event e) override {
Chris@1798 147 m_events.remove(e);
Chris@1752 148 emit modelChangedWithin(getId(),
Chris@1752 149 e.getFrame(), e.getFrame() + m_resolution);
Chris@1663 150 }
Chris@424 151
Chris@424 152 /**
Chris@424 153 * TabularModel methods.
Chris@424 154 */
Chris@424 155
Chris@1663 156 int getRowCount() const override {
Chris@1663 157 return m_events.count();
Chris@1663 158 }
Chris@1663 159
Chris@1663 160 int getColumnCount() const override {
Chris@424 161 return 4;
Chris@424 162 }
Chris@424 163
Chris@1663 164 bool isColumnTimeValue(int column) const override {
Chris@1663 165 return (column < 2);
Chris@1663 166 }
Chris@1663 167
Chris@1663 168 sv_frame_t getFrameForRow(int row) const override {
Chris@1663 169 if (row < 0 || row >= m_events.count()) {
Chris@1663 170 return 0;
Chris@1663 171 }
Chris@1663 172 Event e = m_events.getEventByIndex(row);
Chris@1663 173 return e.getFrame();
Chris@1663 174 }
Chris@1663 175
Chris@1663 176 int getRowForFrame(sv_frame_t frame) const override {
Chris@1663 177 return m_events.getIndexForEvent(Event(frame));
Chris@1663 178 }
Chris@1663 179
Chris@1663 180 QString getHeading(int column) const override {
Chris@424 181 switch (column) {
Chris@424 182 case 0: return tr("Time");
Chris@424 183 case 1: return tr("Frame");
Chris@424 184 case 2: return tr("Image");
Chris@424 185 case 3: return tr("Label");
Chris@424 186 default: return tr("Unknown");
Chris@424 187 }
Chris@424 188 }
Chris@424 189
Chris@1663 190 SortType getSortType(int column) const override {
Chris@1663 191 if (column >= 2) return SortAlphabetical;
Chris@1663 192 return SortNumeric;
Chris@1663 193 }
Chris@1663 194
Chris@1663 195 QVariant getData(int row, int column, int role) const override {
Chris@1663 196
Chris@1663 197 if (row < 0 || row >= m_events.count()) {
Chris@1663 198 return QVariant();
Chris@425 199 }
Chris@425 200
Chris@1663 201 Event e = m_events.getEventByIndex(row);
Chris@424 202
Chris@424 203 switch (column) {
Chris@1663 204 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1663 205 case 1: return int(e.getFrame());
Chris@1663 206 case 2: return e.getURI();
Chris@1663 207 case 3: return e.getLabel();
Chris@424 208 default: return QVariant();
Chris@424 209 }
Chris@424 210 }
Chris@424 211
Chris@1663 212 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1663 213
Chris@1663 214 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1663 215 if (role != Qt::EditRole) return nullptr;
Chris@1663 216
Chris@1663 217 Event e0 = m_events.getEventByIndex(row);
Chris@1663 218 Event e1;
Chris@1663 219
Chris@1663 220 switch (column) {
Chris@1663 221 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1663 222 getSampleRate()))); break;
Chris@1663 223 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1663 224 case 2: e1 = e0.withURI(value.toString()); break;
Chris@1663 225 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 226 }
Chris@425 227
Chris@1742 228 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1663 229 command->remove(e0);
Chris@1663 230 command->add(e1);
Chris@424 231 return command->finish();
Chris@424 232 }
Chris@1804 233
Chris@1804 234 bool isEditable() const override { return true; }
Chris@1804 235
Chris@1804 236 Command *getInsertRowCommand(int row) override {
Chris@1804 237 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 238 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 239 tr("Add Image"));
Chris@1804 240 Event e = m_events.getEventByIndex(row);
Chris@1804 241 command->add(e);
Chris@1804 242 return command->finish();
Chris@1804 243 }
Chris@1804 244
Chris@1804 245 Command *getRemoveRowCommand(int row) override {
Chris@1804 246 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 247 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 248 tr("Delete Image"));
Chris@1804 249 Event e = m_events.getEventByIndex(row);
Chris@1804 250 command->remove(e);
Chris@1804 251 return command->finish();
Chris@1804 252 }
Chris@1663 253
Chris@1663 254 /**
Chris@1663 255 * XmlExportable methods.
Chris@1663 256 */
Chris@1663 257 void toXml(QTextStream &out,
Chris@1663 258 QString indent = "",
Chris@1663 259 QString extraAttributes = "") const override {
Chris@424 260
Chris@1663 261 Model::toXml
Chris@1663 262 (out,
Chris@1663 263 indent,
Chris@1663 264 QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" "
Chris@1663 265 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"image\" %4")
Chris@1663 266 .arg(m_resolution)
Chris@1663 267 .arg("true") // always true after model reaches 100% -
Chris@1663 268 // subsequent events are always notified
Chris@1677 269 .arg(m_events.getExportId())
Chris@1663 270 .arg(extraAttributes));
Chris@1674 271
Chris@1674 272 Event::ExportNameOptions options;
Chris@1674 273 options.uriAttributeName = "image";
Chris@1663 274
Chris@1674 275 m_events.toXml(out, indent, QString("dimensions=\"1\""), options);
Chris@424 276 }
Chris@1679 277
Chris@1833 278 QVector<QString>
Chris@1833 279 getStringExportHeaders(DataExportOptions options) const override {
Chris@1815 280 Event::ExportNameOptions nameOpts;
Chris@1815 281 nameOpts.uriAttributeName = "image";
Chris@1833 282 return m_events.getStringExportHeaders(options, nameOpts);
Chris@1815 283 }
Chris@1815 284
Chris@1833 285 QVector<QVector<QString>>
Chris@1833 286 toStringExportRows(DataExportOptions options,
Chris@1833 287 sv_frame_t startFrame,
Chris@1833 288 sv_frame_t duration) const override {
Chris@1833 289 return m_events.toStringExportRows(options,
Chris@1833 290 startFrame,
Chris@1833 291 duration,
Chris@1833 292 m_sampleRate,
Chris@1833 293 m_resolution,
Chris@1833 294 Event().withValue(0.f));
Chris@1679 295 }
Chris@1679 296
Chris@1663 297 protected:
Chris@1663 298 sv_samplerate_t m_sampleRate;
Chris@1663 299 int m_resolution;
Chris@424 300
Chris@1663 301 DeferredNotifier m_notifier;
Chris@1798 302 std::atomic<int> m_completion;
Chris@1663 303
Chris@1663 304 EventSeries m_events;
Chris@302 305 };
Chris@302 306
Chris@302 307
Chris@302 308 #endif
Chris@302 309