annotate data/model/SparseTimeValueModel.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@147 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@147 2
Chris@147 3 /*
Chris@147 4 Sonic Visualiser
Chris@147 5 An audio file viewer and annotation editor.
Chris@147 6 Centre for Digital Music, Queen Mary, University of London.
Chris@147 7
Chris@147 8 This program is free software; you can redistribute it and/or
Chris@147 9 modify it under the terms of the GNU General Public License as
Chris@147 10 published by the Free Software Foundation; either version 2 of the
Chris@147 11 License, or (at your option) any later version. See the file
Chris@147 12 COPYING included with this distribution for more information.
Chris@147 13 */
Chris@147 14
Chris@1581 15 #ifndef SV_SPARSE_TIME_VALUE_MODEL_H
Chris@1581 16 #define SV_SPARSE_TIME_VALUE_MODEL_H
Chris@147 17
Chris@1651 18 #include "EventCommands.h"
Chris@1651 19 #include "TabularModel.h"
Chris@1651 20 #include "Model.h"
Chris@1651 21 #include "DeferredNotifier.h"
Chris@1651 22
Chris@1651 23 #include "base/RealTime.h"
Chris@1651 24 #include "base/EventSeries.h"
Chris@1651 25 #include "base/UnitDatabase.h"
Chris@150 26 #include "base/PlayParameterRepository.h"
Chris@1651 27
Chris@1651 28 #include "system/System.h"
Chris@147 29
Chris@147 30 /**
Chris@1651 31 * A model representing a wiggly-line plot with points at arbitrary
Chris@1651 32 * intervals of the model resolution.
Chris@147 33 */
Chris@1651 34 class SparseTimeValueModel : public Model,
Chris@1651 35 public TabularModel,
Chris@1651 36 public EventEditable
Chris@147 37 {
Chris@423 38 Q_OBJECT
Chris@423 39
Chris@147 40 public:
Chris@1651 41 SparseTimeValueModel(sv_samplerate_t sampleRate,
Chris@1651 42 int resolution,
Chris@1429 43 bool notifyOnAdd = true) :
Chris@1651 44 m_sampleRate(sampleRate),
Chris@1651 45 m_resolution(resolution),
Chris@1651 46 m_valueMinimum(0.f),
Chris@1651 47 m_valueMaximum(0.f),
Chris@1651 48 m_haveExtents(false),
Chris@1651 49 m_haveTextLabels(false),
Chris@1651 50 m_notifier(this,
Chris@1752 51 getId(),
Chris@1651 52 notifyOnAdd ?
Chris@1651 53 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 54 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 55 m_completion(100) {
Chris@868 56 // Model is playable, but may not sound (if units not Hz or
Chris@868 57 // range unsuitable)
Chris@1751 58 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 59 (getId().untyped, this);
Chris@256 60 }
Chris@256 61
Chris@1040 62 SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429 63 float valueMinimum, float valueMaximum,
Chris@1429 64 bool notifyOnAdd = true) :
Chris@1651 65 m_sampleRate(sampleRate),
Chris@1651 66 m_resolution(resolution),
Chris@1651 67 m_valueMinimum(valueMinimum),
Chris@1651 68 m_valueMaximum(valueMaximum),
Chris@1684 69 m_haveExtents(true),
Chris@1651 70 m_haveTextLabels(false),
Chris@1651 71 m_notifier(this,
Chris@1752 72 getId(),
Chris@1651 73 notifyOnAdd ?
Chris@1651 74 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 75 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 76 m_completion(100) {
Chris@868 77 // Model is playable, but may not sound (if units not Hz or
Chris@868 78 // range unsuitable)
Chris@1751 79 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 80 (getId().untyped, this);
Chris@868 81 }
Chris@868 82
Chris@1651 83 virtual ~SparseTimeValueModel() {
Chris@1751 84 PlayParameterRepository::getInstance()->removePlayable
Chris@1751 85 (getId().untyped);
Chris@147 86 }
Chris@345 87
Chris@1580 88 QString getTypeName() const override { return tr("Sparse Time-Value"); }
Chris@1692 89 bool isSparse() const override { return true; }
Chris@1659 90 bool isOK() const override { return true; }
Chris@420 91
Chris@1659 92 sv_frame_t getStartFrame() const override {
Chris@1659 93 return m_events.getStartFrame();
Chris@1659 94 }
Chris@1725 95 sv_frame_t getTrueEndFrame() const override {
Chris@1659 96 if (m_events.isEmpty()) return 0;
Chris@1659 97 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1659 98 if (e % m_resolution == 0) return e;
Chris@1659 99 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 100 }
Chris@1659 101
Chris@1651 102 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1651 103 int getResolution() const { return m_resolution; }
Chris@1651 104
Chris@1580 105 bool canPlay() const override { return true; }
Chris@1580 106 bool getDefaultPlayAudible() const override { return false; } // user must unmute
Chris@868 107
Chris@1798 108 QString getScaleUnits() const {
Chris@1798 109 QMutexLocker locker(&m_mutex);
Chris@1798 110 return m_units;
Chris@1798 111 }
Chris@1651 112 void setScaleUnits(QString units) {
Chris@1798 113 QMutexLocker locker(&m_mutex);
Chris@1651 114 m_units = units;
Chris@1651 115 UnitDatabase::getInstance()->registerUnit(units);
Chris@1651 116 }
Chris@1651 117
Chris@1651 118 bool hasTextLabels() const { return m_haveTextLabels; }
Chris@1651 119
Chris@1651 120 float getValueMinimum() const { return m_valueMinimum; }
Chris@1651 121 float getValueMaximum() const { return m_valueMaximum; }
Chris@1651 122
Chris@1671 123 int getCompletion() const override { return m_completion; }
Chris@1651 124
Chris@1651 125 void setCompletion(int completion, bool update = true) {
Chris@1655 126
Chris@1798 127 {
Chris@1651 128 if (m_completion == completion) return;
Chris@1651 129 m_completion = completion;
Chris@1651 130 }
Chris@1651 131
Chris@1651 132 if (update) {
Chris@1651 133 m_notifier.makeDeferredNotifications();
Chris@1651 134 }
Chris@1651 135
Chris@1752 136 emit completionChanged(getId());
Chris@1651 137
Chris@1651 138 if (completion == 100) {
Chris@1651 139 // henceforth:
Chris@1651 140 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 141 emit modelChanged(getId());
Chris@1651 142 }
Chris@1651 143 }
Chris@1651 144
Chris@1651 145 /**
Chris@1651 146 * Query methods.
Chris@1651 147 */
Chris@1651 148
Chris@1651 149 int getEventCount() const {
Chris@1651 150 return m_events.count();
Chris@1651 151 }
Chris@1651 152 bool isEmpty() const {
Chris@1651 153 return m_events.isEmpty();
Chris@1651 154 }
Chris@1651 155 bool containsEvent(const Event &e) const {
Chris@1651 156 return m_events.contains(e);
Chris@1651 157 }
Chris@1651 158 EventVector getAllEvents() const {
Chris@1651 159 return m_events.getAllEvents();
Chris@1651 160 }
Chris@1651 161 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 162 return m_events.getEventsSpanning(f, duration);
Chris@1651 163 }
Chris@1656 164 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656 165 return m_events.getEventsCovering(f);
Chris@1656 166 }
Chris@1655 167 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1655 168 int overspill = 0) const {
Chris@1655 169 return m_events.getEventsWithin(f, duration, overspill);
Chris@1651 170 }
Chris@1651 171 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 172 return m_events.getEventsStartingWithin(f, duration);
Chris@1651 173 }
Chris@1656 174 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656 175 return m_events.getEventsStartingAt(f);
Chris@1651 176 }
Chris@1655 177 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1655 178 std::function<bool(Event)> predicate,
Chris@1655 179 EventSeries::Direction direction,
Chris@1655 180 Event &found) const {
Chris@1655 181 return m_events.getNearestEventMatching
Chris@1655 182 (startSearchAt, predicate, direction, found);
Chris@1655 183 }
Chris@1651 184
Chris@1651 185 /**
Chris@1651 186 * Editing methods.
Chris@1651 187 */
Chris@1651 188 void add(Event e) override {
Chris@1651 189
Chris@1651 190 bool allChange = false;
Chris@1651 191
Chris@1798 192 m_events.add(e.withoutDuration()); // can't have duration here
Chris@1651 193
Chris@1798 194 if (e.getLabel() != "") {
Chris@1798 195 m_haveTextLabels = true;
Chris@1798 196 }
Chris@1798 197
Chris@1798 198 float v = e.getValue();
Chris@1798 199 if (!ISNAN(v) && !ISINF(v)) {
Chris@1798 200 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1798 201 m_valueMinimum = v; allChange = true;
Chris@1651 202 }
Chris@1798 203 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1798 204 m_valueMaximum = v; allChange = true;
Chris@1651 205 }
Chris@1798 206 m_haveExtents = true;
Chris@1651 207 }
Chris@1651 208
Chris@1651 209 m_notifier.update(e.getFrame(), m_resolution);
Chris@1651 210
Chris@1651 211 if (allChange) {
Chris@1752 212 emit modelChanged(getId());
Chris@1651 213 }
Chris@1651 214 }
Chris@1651 215
Chris@1651 216 void remove(Event e) override {
Chris@1798 217 m_events.remove(e);
Chris@1752 218 emit modelChangedWithin(getId(),
Chris@1752 219 e.getFrame(), e.getFrame() + m_resolution);
Chris@1651 220 }
Chris@1651 221
Chris@420 222 /**
Chris@420 223 * TabularModel methods.
Chris@420 224 */
Chris@420 225
Chris@1651 226 int getRowCount() const override {
Chris@1651 227 return m_events.count();
Chris@1651 228 }
Chris@1651 229
Chris@1651 230 int getColumnCount() const override {
Chris@420 231 return 4;
Chris@420 232 }
Chris@420 233
Chris@1651 234 bool isColumnTimeValue(int column) const override {
Chris@1651 235 return (column < 2);
Chris@1651 236 }
Chris@1651 237
Chris@1651 238 sv_frame_t getFrameForRow(int row) const override {
Chris@1651 239 if (row < 0 || row >= m_events.count()) {
Chris@1651 240 return 0;
Chris@1651 241 }
Chris@1651 242 Event e = m_events.getEventByIndex(row);
Chris@1651 243 return e.getFrame();
Chris@1651 244 }
Chris@1651 245
Chris@1651 246 int getRowForFrame(sv_frame_t frame) const override {
Chris@1651 247 return m_events.getIndexForEvent(Event(frame));
Chris@1651 248 }
Chris@1651 249
Chris@1651 250 QString getHeading(int column) const override {
Chris@420 251 switch (column) {
Chris@420 252 case 0: return tr("Time");
Chris@420 253 case 1: return tr("Frame");
Chris@420 254 case 2: return tr("Value");
Chris@420 255 case 3: return tr("Label");
Chris@420 256 default: return tr("Unknown");
Chris@420 257 }
Chris@420 258 }
Chris@420 259
Chris@1651 260 SortType getSortType(int column) const override {
Chris@1651 261 if (column == 3) return SortAlphabetical;
Chris@1651 262 return SortNumeric;
Chris@1651 263 }
Chris@1651 264
Chris@1651 265 QVariant getData(int row, int column, int role) const override {
Chris@1651 266
Chris@1651 267 if (row < 0 || row >= m_events.count()) {
Chris@1651 268 return QVariant();
Chris@425 269 }
Chris@425 270
Chris@1651 271 Event e = m_events.getEventByIndex(row);
Chris@420 272
Chris@420 273 switch (column) {
Chris@1651 274 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1651 275 case 1: return int(e.getFrame());
Chris@1651 276 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1651 277 case 3: return e.getLabel();
Chris@420 278 default: return QVariant();
Chris@420 279 }
Chris@420 280 }
Chris@420 281
Chris@1804 282 bool isEditable() const override { return true; }
Chris@1804 283
Chris@1804 284 Command *getSetDataCommand(int row, int column, const QVariant &value,
Chris@1804 285 int role) override {
Chris@1651 286 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1651 287 if (role != Qt::EditRole) return nullptr;
Chris@1651 288
Chris@1651 289 Event e0 = m_events.getEventByIndex(row);
Chris@1651 290 Event e1;
Chris@1651 291
Chris@1651 292 switch (column) {
Chris@1651 293 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1651 294 getSampleRate()))); break;
Chris@1651 295 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1651 296 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1651 297 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 298 }
Chris@425 299
Chris@1742 300 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1651 301 command->remove(e0);
Chris@1651 302 command->add(e1);
Chris@420 303 return command->finish();
Chris@420 304 }
Chris@1804 305
Chris@1804 306 Command *getInsertRowCommand(int row) override {
Chris@1804 307 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 308 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 309 tr("Add Point"));
Chris@1804 310 Event e = m_events.getEventByIndex(row);
Chris@1804 311 command->add(e);
Chris@1804 312 return command->finish();
Chris@1804 313 }
Chris@1804 314
Chris@1804 315 Command *getRemoveRowCommand(int row) override {
Chris@1804 316 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 317 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 318 tr("Delete Point"));
Chris@1804 319 Event e = m_events.getEventByIndex(row);
Chris@1804 320 command->remove(e);
Chris@1804 321 return command->finish();
Chris@1804 322 }
Chris@1651 323
Chris@1651 324 /**
Chris@1651 325 * XmlExportable methods.
Chris@1651 326 */
Chris@1651 327 void toXml(QTextStream &out,
Chris@1651 328 QString indent = "",
Chris@1651 329 QString extraAttributes = "") const override {
Chris@420 330
Chris@1651 331 Model::toXml
Chris@1651 332 (out,
Chris@1651 333 indent,
Chris@1651 334 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1651 335 "notifyOnAdd=\"%2\" dataset=\"%3\" "
Chris@1651 336 "minimum=\"%4\" maximum=\"%5\" "
Chris@1651 337 "units=\"%6\" %7")
Chris@1651 338 .arg(m_resolution)
Chris@1651 339 .arg("true") // always true after model reaches 100% -
Chris@1651 340 // subsequent events are always notified
Chris@1677 341 .arg(m_events.getExportId())
Chris@1651 342 .arg(m_valueMinimum)
Chris@1651 343 .arg(m_valueMaximum)
Chris@1651 344 .arg(encodeEntities(m_units))
Chris@1651 345 .arg(extraAttributes));
Chris@1651 346
Chris@1651 347 m_events.toXml(out, indent, QString("dimensions=\"2\""));
Chris@420 348 }
Chris@1679 349
Chris@1833 350 QVector<QString>
Chris@1833 351 getStringExportHeaders(DataExportOptions options) const override {
Chris@1833 352 return m_events.getStringExportHeaders(options, {});
Chris@1815 353 }
Chris@1815 354
Chris@1833 355 QVector<QVector<QString>>
Chris@1833 356 toStringExportRows(DataExportOptions options,
Chris@1833 357 sv_frame_t startFrame,
Chris@1833 358 sv_frame_t duration) const override {
Chris@1833 359 return m_events.toStringExportRows(options,
Chris@1833 360 startFrame,
Chris@1833 361 duration,
Chris@1833 362 m_sampleRate,
Chris@1833 363 m_resolution,
Chris@1833 364 Event().withValue(0.f));
Chris@1679 365 }
Chris@1651 366
Chris@1651 367 protected:
Chris@1651 368 sv_samplerate_t m_sampleRate;
Chris@1651 369 int m_resolution;
Chris@422 370
Chris@1798 371 std::atomic<float> m_valueMinimum;
Chris@1798 372 std::atomic<float> m_valueMaximum;
Chris@1798 373 std::atomic<bool> m_haveExtents;
Chris@1798 374 std::atomic<bool> m_haveTextLabels;
Chris@1651 375 QString m_units;
Chris@1651 376 DeferredNotifier m_notifier;
Chris@1798 377 std::atomic<int> m_completion;
Chris@1651 378
Chris@1651 379 EventSeries m_events;
Chris@147 380 };
Chris@147 381
Chris@147 382 #endif
Chris@147 383
Chris@147 384
Chris@147 385