annotate data/model/BoxModel.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@1785 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1785 2
Chris@1785 3 /*
Chris@1785 4 Sonic Visualiser
Chris@1785 5 An audio file viewer and annotation editor.
Chris@1785 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1785 7
Chris@1785 8 This program is free software; you can redistribute it and/or
Chris@1785 9 modify it under the terms of the GNU General Public License as
Chris@1785 10 published by the Free Software Foundation; either version 2 of the
Chris@1785 11 License, or (at your option) any later version. See the file
Chris@1785 12 COPYING included with this distribution for more information.
Chris@1785 13 */
Chris@1785 14
Chris@1791 15 #ifndef SV_BOX_MODEL_H
Chris@1791 16 #define SV_BOX_MODEL_H
Chris@1785 17
Chris@1785 18 #include "EventCommands.h"
Chris@1785 19 #include "TabularModel.h"
Chris@1785 20 #include "Model.h"
Chris@1785 21 #include "DeferredNotifier.h"
Chris@1785 22
Chris@1785 23 #include "base/RealTime.h"
Chris@1785 24 #include "base/EventSeries.h"
Chris@1785 25 #include "base/UnitDatabase.h"
Chris@1785 26
Chris@1785 27 #include "system/System.h"
Chris@1785 28
Chris@1785 29 #include <QMutex>
Chris@1785 30
Chris@1785 31 /**
Chris@1791 32 * BoxModel -- a model for annotations having start time, duration,
Chris@1791 33 * and a value range. We use Events as usual for these, but treat the
Chris@1791 34 * "value" as the lower value and "level" as the difference between
Chris@1791 35 * lower and upper values, which is expected to be non-negative (if it
Chris@1791 36 * is negative, abs(level) will be used).
Chris@1791 37 *
Chris@1791 38 * This is expected to be used most often for time-frequency boxes.
Chris@1785 39 */
Chris@1791 40 class BoxModel : public Model,
Chris@1791 41 public TabularModel,
Chris@1791 42 public EventEditable
Chris@1785 43 {
Chris@1785 44 Q_OBJECT
Chris@1785 45
Chris@1785 46 public:
Chris@1791 47 BoxModel(sv_samplerate_t sampleRate,
Chris@1792 48 int resolution,
Chris@1792 49 bool notifyOnAdd = true) :
Chris@1785 50 m_sampleRate(sampleRate),
Chris@1785 51 m_resolution(resolution),
Chris@1791 52 m_valueMinimum(0.f),
Chris@1791 53 m_valueMaximum(0.f),
Chris@1785 54 m_haveExtents(false),
Chris@1785 55 m_notifier(this,
Chris@1785 56 getId(),
Chris@1785 57 notifyOnAdd ?
Chris@1785 58 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1785 59 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1785 60 m_completion(100) {
Chris@1785 61 }
Chris@1785 62
Chris@1791 63 BoxModel(sv_samplerate_t sampleRate, int resolution,
Chris@1792 64 float valueMinimum, float valueMaximum,
Chris@1792 65 bool notifyOnAdd = true) :
Chris@1785 66 m_sampleRate(sampleRate),
Chris@1785 67 m_resolution(resolution),
Chris@1791 68 m_valueMinimum(valueMinimum),
Chris@1791 69 m_valueMaximum(valueMaximum),
Chris@1785 70 m_haveExtents(true),
Chris@1785 71 m_notifier(this,
Chris@1785 72 getId(),
Chris@1785 73 notifyOnAdd ?
Chris@1785 74 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1785 75 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1785 76 m_completion(100) {
Chris@1785 77 }
Chris@1785 78
Chris@1791 79 virtual ~BoxModel() {
Chris@1785 80 }
Chris@1785 81
Chris@1791 82 QString getTypeName() const override { return tr("Box"); }
Chris@1785 83 bool isSparse() const override { return true; }
Chris@1785 84 bool isOK() const override { return true; }
Chris@1785 85
Chris@1785 86 sv_frame_t getStartFrame() const override {
Chris@1785 87 return m_events.getStartFrame();
Chris@1785 88 }
Chris@1785 89 sv_frame_t getTrueEndFrame() const override {
Chris@1785 90 if (m_events.isEmpty()) return 0;
Chris@1785 91 sv_frame_t e = m_events.getEndFrame();
Chris@1785 92 if (e % m_resolution == 0) return e;
Chris@1785 93 else return (e / m_resolution + 1) * m_resolution;
Chris@1785 94 }
Chris@1785 95
Chris@1785 96 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1785 97 int getResolution() const { return m_resolution; }
Chris@1785 98
Chris@1791 99 QString getScaleUnits() const { return m_units; }
Chris@1791 100 void setScaleUnits(QString units) {
Chris@1791 101 m_units = units;
Chris@1791 102 UnitDatabase::getInstance()->registerUnit(units);
Chris@1791 103 }
Chris@1785 104
Chris@1791 105 float getValueMinimum() const { return m_valueMinimum; }
Chris@1791 106 float getValueMaximum() const { return m_valueMaximum; }
Chris@1785 107
Chris@1785 108 int getCompletion() const override { return m_completion; }
Chris@1785 109
Chris@1785 110 void setCompletion(int completion, bool update = true) {
Chris@1785 111
Chris@1785 112 { QMutexLocker locker(&m_mutex);
Chris@1785 113 if (m_completion == completion) return;
Chris@1785 114 m_completion = completion;
Chris@1785 115 }
Chris@1785 116
Chris@1785 117 if (update) {
Chris@1785 118 m_notifier.makeDeferredNotifications();
Chris@1785 119 }
Chris@1785 120
Chris@1785 121 emit completionChanged(getId());
Chris@1785 122
Chris@1785 123 if (completion == 100) {
Chris@1785 124 // henceforth:
Chris@1785 125 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1785 126 emit modelChanged(getId());
Chris@1785 127 }
Chris@1785 128 }
Chris@1785 129
Chris@1785 130 /**
Chris@1785 131 * Query methods.
Chris@1785 132 */
Chris@1785 133 int getEventCount() const {
Chris@1785 134 return m_events.count();
Chris@1785 135 }
Chris@1785 136 bool isEmpty() const {
Chris@1785 137 return m_events.isEmpty();
Chris@1785 138 }
Chris@1785 139 bool containsEvent(const Event &e) const {
Chris@1785 140 return m_events.contains(e);
Chris@1785 141 }
Chris@1785 142 EventVector getAllEvents() const {
Chris@1785 143 return m_events.getAllEvents();
Chris@1785 144 }
Chris@1785 145 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1785 146 return m_events.getEventsSpanning(f, duration);
Chris@1785 147 }
Chris@1785 148 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1785 149 return m_events.getEventsCovering(f);
Chris@1785 150 }
Chris@1785 151 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1785 152 return m_events.getEventsWithin(f, duration);
Chris@1785 153 }
Chris@1785 154 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1785 155 return m_events.getEventsStartingWithin(f, duration);
Chris@1785 156 }
Chris@1785 157 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1785 158 return m_events.getEventsStartingAt(f);
Chris@1785 159 }
Chris@1785 160 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1785 161 std::function<bool(Event)> predicate,
Chris@1785 162 EventSeries::Direction direction,
Chris@1785 163 Event &found) const {
Chris@1785 164 return m_events.getNearestEventMatching
Chris@1785 165 (startSearchAt, predicate, direction, found);
Chris@1785 166 }
Chris@1785 167
Chris@1785 168 /**
Chris@1785 169 * Editing methods.
Chris@1785 170 */
Chris@1785 171 void add(Event e) override {
Chris@1785 172
Chris@1785 173 bool allChange = false;
Chris@1785 174
Chris@1785 175 {
Chris@1785 176 QMutexLocker locker(&m_mutex);
Chris@1785 177 m_events.add(e);
Chris@1785 178
Chris@1785 179 float f0 = e.getValue();
Chris@1785 180 float f1 = f0 + fabsf(e.getLevel());
Chris@1785 181
Chris@1791 182 if (!m_haveExtents || f0 < m_valueMinimum) {
Chris@1791 183 m_valueMinimum = f0; allChange = true;
Chris@1785 184 }
Chris@1791 185 if (!m_haveExtents || f1 > m_valueMaximum) {
Chris@1791 186 m_valueMaximum = f1; allChange = true;
Chris@1785 187 }
Chris@1785 188 m_haveExtents = true;
Chris@1785 189 }
Chris@1785 190
Chris@1785 191 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution);
Chris@1785 192
Chris@1785 193 if (allChange) {
Chris@1785 194 emit modelChanged(getId());
Chris@1785 195 }
Chris@1785 196 }
Chris@1785 197
Chris@1785 198 void remove(Event e) override {
Chris@1785 199 {
Chris@1785 200 QMutexLocker locker(&m_mutex);
Chris@1785 201 m_events.remove(e);
Chris@1785 202 }
Chris@1785 203 emit modelChangedWithin(getId(),
Chris@1785 204 e.getFrame(),
Chris@1785 205 e.getFrame() + e.getDuration() + m_resolution);
Chris@1785 206 }
Chris@1785 207
Chris@1785 208 /**
Chris@1785 209 * TabularModel methods.
Chris@1785 210 */
Chris@1785 211
Chris@1785 212 int getRowCount() const override {
Chris@1785 213 return m_events.count();
Chris@1785 214 }
Chris@1785 215
Chris@1785 216 int getColumnCount() const override {
Chris@1786 217 return 6;
Chris@1785 218 }
Chris@1785 219
Chris@1785 220 bool isColumnTimeValue(int column) const override {
Chris@1785 221 // NB duration is not a "time value" -- that's for columns
Chris@1785 222 // whose sort ordering is exactly that of the frame time
Chris@1785 223 return (column < 2);
Chris@1785 224 }
Chris@1785 225
Chris@1785 226 sv_frame_t getFrameForRow(int row) const override {
Chris@1785 227 if (row < 0 || row >= m_events.count()) {
Chris@1785 228 return 0;
Chris@1785 229 }
Chris@1785 230 Event e = m_events.getEventByIndex(row);
Chris@1785 231 return e.getFrame();
Chris@1785 232 }
Chris@1785 233
Chris@1785 234 int getRowForFrame(sv_frame_t frame) const override {
Chris@1785 235 return m_events.getIndexForEvent(Event(frame));
Chris@1785 236 }
Chris@1785 237
Chris@1785 238 QString getHeading(int column) const override {
Chris@1785 239 switch (column) {
Chris@1785 240 case 0: return tr("Time");
Chris@1785 241 case 1: return tr("Frame");
Chris@1785 242 case 2: return tr("Duration");
Chris@1786 243 case 3: return tr("Min Freq");
Chris@1786 244 case 4: return tr("Max Freq");
Chris@1785 245 case 5: return tr("Label");
Chris@1785 246 default: return tr("Unknown");
Chris@1785 247 }
Chris@1785 248 }
Chris@1785 249
Chris@1785 250 SortType getSortType(int column) const override {
Chris@1785 251 if (column == 5) return SortAlphabetical;
Chris@1785 252 return SortNumeric;
Chris@1785 253 }
Chris@1785 254
Chris@1785 255 QVariant getData(int row, int column, int role) const override {
Chris@1785 256
Chris@1785 257 if (row < 0 || row >= m_events.count()) {
Chris@1785 258 return QVariant();
Chris@1785 259 }
Chris@1785 260
Chris@1785 261 Event e = m_events.getEventByIndex(row);
Chris@1785 262
Chris@1785 263 switch (column) {
Chris@1785 264 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1785 265 case 1: return int(e.getFrame());
Chris@1785 266 case 2: return int(e.getDuration());
Chris@1785 267 case 3: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1785 268 case 4: return adaptValueForRole(e.getValue() + fabsf(e.getLevel()),
Chris@1785 269 getScaleUnits(), role);
Chris@1785 270 case 5: return e.getLabel();
Chris@1785 271 default: return QVariant();
Chris@1785 272 }
Chris@1785 273 }
Chris@1785 274
Chris@1804 275 bool isEditable() const override { return true; }
Chris@1804 276
Chris@1785 277 Command *getSetDataCommand(int row, int column, const QVariant &value,
Chris@1785 278 int role) override {
Chris@1785 279
Chris@1785 280 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1785 281 if (role != Qt::EditRole) return nullptr;
Chris@1785 282
Chris@1785 283 Event e0 = m_events.getEventByIndex(row);
Chris@1785 284 Event e1;
Chris@1785 285
Chris@1785 286 switch (column) {
Chris@1785 287 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1785 288 getSampleRate()))); break;
Chris@1785 289 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1785 290 case 2: e1 = e0.withDuration(value.toInt()); break;
Chris@1785 291 case 3: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1785 292 case 4: e1 = e0.withLevel(fabsf(float(value.toDouble()) -
Chris@1785 293 e0.getValue())); break;
Chris@1785 294 case 5: e1 = e0.withLabel(value.toString()); break;
Chris@1785 295 }
Chris@1785 296
Chris@1785 297 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1785 298 command->remove(e0);
Chris@1785 299 command->add(e1);
Chris@1785 300 return command->finish();
Chris@1785 301 }
Chris@1785 302
Chris@1804 303 Command *getInsertRowCommand(int row) override {
Chris@1804 304 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 305 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 306 tr("Add Box"));
Chris@1804 307 Event e = m_events.getEventByIndex(row);
Chris@1804 308 command->add(e);
Chris@1804 309 return command->finish();
Chris@1804 310 }
Chris@1804 311
Chris@1804 312 Command *getRemoveRowCommand(int row) override {
Chris@1804 313 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 314 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 315 tr("Delete Box"));
Chris@1804 316 Event e = m_events.getEventByIndex(row);
Chris@1804 317 command->remove(e);
Chris@1804 318 return command->finish();
Chris@1804 319 }
Chris@1804 320
Chris@1785 321
Chris@1785 322 /**
Chris@1785 323 * XmlExportable methods.
Chris@1785 324 */
Chris@1785 325 void toXml(QTextStream &out,
Chris@1785 326 QString indent = "",
Chris@1785 327 QString extraAttributes = "") const override {
Chris@1785 328
Chris@1785 329 Model::toXml
Chris@1785 330 (out,
Chris@1785 331 indent,
Chris@1789 332 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1785 333 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
Chris@1785 334 "minimum=\"%5\" maximum=\"%6\" units=\"%7\" %8")
Chris@1785 335 .arg(m_resolution)
Chris@1785 336 .arg("true") // always true after model reaches 100% -
Chris@1785 337 // subsequent events are always notified
Chris@1785 338 .arg(m_events.getExportId())
Chris@1791 339 .arg("box")
Chris@1791 340 .arg(m_valueMinimum)
Chris@1791 341 .arg(m_valueMaximum)
Chris@1785 342 .arg(encodeEntities(m_units))
Chris@1785 343 .arg(extraAttributes));
Chris@1789 344
Chris@1789 345 Event::ExportNameOptions options;
Chris@1789 346 options.levelAttributeName = "extent";
Chris@1785 347
Chris@1789 348 m_events.toXml(out, indent, QString("dimensions=\"2\""), options);
Chris@1785 349 }
Chris@1785 350
Chris@1833 351 QVector<QString>
Chris@1833 352 getStringExportHeaders(DataExportOptions opts) const override {
Chris@1833 353
Chris@1815 354 QStringList list;
Chris@1815 355
Chris@1815 356 // These are considered API rather than human-readable text -
Chris@1815 357 // they shouldn't be translated
Chris@1815 358
Chris@1815 359 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 360 list << "startframe" << "endframe";
Chris@1815 361 } else {
Chris@1815 362 list << "start" << "end";
Chris@1815 363 }
Chris@1815 364
Chris@1815 365 list << "extent start" << "extent end" << "label";
Chris@1815 366
Chris@1833 367 QVector<QString> sv;
Chris@1833 368 for (QString s: list) {
Chris@1833 369 sv.push_back(s.toUpper());
Chris@1833 370 }
Chris@1833 371 return sv;
Chris@1815 372 }
Chris@1815 373
Chris@1833 374 QVector<QVector<QString>>
Chris@1833 375 toStringExportRows(DataExportOptions opts,
Chris@1833 376 sv_frame_t startFrame,
Chris@1833 377 sv_frame_t duration) const override {
Chris@1786 378
Chris@1786 379 // We need a custom format here
Chris@1786 380
Chris@1786 381 EventVector ee = m_events.getEventsSpanning(startFrame, duration);
Chris@1786 382
Chris@1833 383 QVector<QVector<QString>> rows;
Chris@1786 384
Chris@1786 385 for (auto e: ee) {
Chris@1786 386
Chris@1833 387 QVector<QString> list;
Chris@1786 388
Chris@1815 389 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 390
Chris@1815 391 list << QString("%1").arg(e.getFrame());
Chris@1815 392 list << QString("%1").arg(e.getFrame() + e.getDuration());
Chris@1815 393
Chris@1815 394 } else {
Chris@1815 395
Chris@1815 396 list << RealTime::frame2RealTime
Chris@1815 397 (e.getFrame(), getSampleRate())
Chris@1815 398 .toString().c_str();
Chris@1815 399
Chris@1815 400 list << RealTime::frame2RealTime
Chris@1815 401 (e.getFrame() + e.getDuration(), getSampleRate())
Chris@1815 402 .toString().c_str();
Chris@1815 403 }
Chris@1815 404
Chris@1815 405 list << QString("%1").arg(e.getValue());
Chris@1815 406
Chris@1815 407 list << QString("%1").arg(e.getValue() + fabsf(e.getLevel()));
Chris@1786 408
Chris@1786 409 if (e.getLabel() != "") {
Chris@1786 410 list << e.getLabel();
Chris@1786 411 }
Chris@1786 412
Chris@1833 413 rows.push_back(list);
Chris@1786 414 }
Chris@1786 415
Chris@1833 416 return rows;
Chris@1785 417 }
Chris@1785 418
Chris@1785 419 protected:
Chris@1785 420 sv_samplerate_t m_sampleRate;
Chris@1785 421 int m_resolution;
Chris@1785 422
Chris@1791 423 float m_valueMinimum;
Chris@1791 424 float m_valueMaximum;
Chris@1785 425 bool m_haveExtents;
Chris@1785 426 QString m_units;
Chris@1785 427 DeferredNotifier m_notifier;
Chris@1785 428 int m_completion;
Chris@1785 429
Chris@1785 430 EventSeries m_events;
Chris@1785 431
Chris@1785 432 mutable QMutex m_mutex;
Chris@1785 433 };
Chris@1785 434
Chris@1785 435 #endif