annotate data/model/BoxModel.h @ 1873:1d44fdc8196c csv-import-headers

Extend tests to include testing (at least some of) the actual data as well as the layout
author Chris Cannam
date Thu, 18 Jun 2020 13:42:48 +0100
parents 21c792334c2e
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