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