annotate data/model/RegionModel.h @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +0100
parents 52705a328b34
children 13bd41bd8a17
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@1649 29 #include <QMutex>
Chris@441 30
Chris@441 31 /**
Chris@1649 32 * RegionModel -- a model for intervals associated with a value, which
Chris@1649 33 * we call regions for no very compelling reason.
Chris@441 34 */
Chris@1649 35 class RegionModel : public Model,
Chris@1649 36 public TabularModel,
Chris@1649 37 public EventEditable
Chris@441 38 {
Chris@441 39 Q_OBJECT
Chris@441 40
Chris@441 41 public:
Chris@1649 42 RegionModel(sv_samplerate_t sampleRate,
Chris@1649 43 int resolution,
Chris@441 44 bool notifyOnAdd = true) :
Chris@1649 45 m_sampleRate(sampleRate),
Chris@1649 46 m_resolution(resolution),
Chris@1649 47 m_valueMinimum(0.f),
Chris@1649 48 m_valueMaximum(0.f),
Chris@1649 49 m_haveExtents(false),
Chris@1429 50 m_valueQuantization(0),
Chris@1649 51 m_haveDistinctValues(false),
Chris@1651 52 m_notifier(this,
Chris@1752 53 getId(),
Chris@1651 54 notifyOnAdd ?
Chris@1651 55 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 56 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 57 m_completion(100) {
Chris@441 58 }
Chris@441 59
Chris@1040 60 RegionModel(sv_samplerate_t sampleRate, int resolution,
Chris@459 61 float valueMinimum, float valueMaximum,
Chris@459 62 bool notifyOnAdd = true) :
Chris@1649 63 m_sampleRate(sampleRate),
Chris@1649 64 m_resolution(resolution),
Chris@1649 65 m_valueMinimum(valueMinimum),
Chris@1649 66 m_valueMaximum(valueMaximum),
Chris@1684 67 m_haveExtents(true),
Chris@1429 68 m_valueQuantization(0),
Chris@1649 69 m_haveDistinctValues(false),
Chris@1651 70 m_notifier(this,
Chris@1752 71 getId(),
Chris@1651 72 notifyOnAdd ?
Chris@1651 73 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 74 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 75 m_completion(100) {
Chris@441 76 }
Chris@441 77
Chris@1649 78 virtual ~RegionModel() {
Chris@1649 79 }
Chris@1649 80
Chris@1649 81 QString getTypeName() const override { return tr("Region"); }
Chris@1692 82 bool isSparse() const override { return true; }
Chris@1659 83 bool isOK() const override { return true; }
Chris@1649 84
Chris@1659 85 sv_frame_t getStartFrame() const override {
Chris@1659 86 return m_events.getStartFrame();
Chris@1659 87 }
Chris@1725 88 sv_frame_t getTrueEndFrame() const override {
Chris@1659 89 if (m_events.isEmpty()) return 0;
Chris@1659 90 sv_frame_t e = m_events.getEndFrame();
Chris@1659 91 if (e % m_resolution == 0) return e;
Chris@1659 92 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 93 }
Chris@1659 94
Chris@1649 95 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1649 96 int getResolution() const { return m_resolution; }
Chris@1649 97
Chris@1649 98 QString getScaleUnits() const { return m_units; }
Chris@1649 99 void setScaleUnits(QString units) {
Chris@1649 100 m_units = units;
Chris@1649 101 UnitDatabase::getInstance()->registerUnit(units);
Chris@441 102 }
Chris@441 103
Chris@441 104 float getValueQuantization() const { return m_valueQuantization; }
Chris@441 105 void setValueQuantization(float q) { m_valueQuantization = q; }
Chris@441 106
Chris@442 107 bool haveDistinctValues() const { return m_haveDistinctValues; }
Chris@442 108
Chris@1649 109 float getValueMinimum() const { return m_valueMinimum; }
Chris@1649 110 float getValueMaximum() const { return m_valueMaximum; }
Chris@1649 111
Chris@1671 112 int getCompletion() const override { return m_completion; }
Chris@441 113
Chris@1649 114 void setCompletion(int completion, bool update = true) {
Chris@441 115
Chris@1651 116 { QMutexLocker locker(&m_mutex);
Chris@1651 117 if (m_completion == completion) return;
Chris@1651 118 m_completion = completion;
Chris@1649 119 }
Chris@1649 120
Chris@1651 121 if (update) {
Chris@1651 122 m_notifier.makeDeferredNotifications();
Chris@1649 123 }
Chris@1651 124
Chris@1752 125 emit completionChanged(getId());
Chris@1651 126
Chris@1651 127 if (completion == 100) {
Chris@1651 128 // henceforth:
Chris@1651 129 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 130 emit modelChanged(getId());
Chris@1649 131 }
Chris@441 132 }
Chris@441 133
Chris@441 134 /**
Chris@1649 135 * Query methods.
Chris@1649 136 */
Chris@1649 137 int getEventCount() const {
Chris@1649 138 return m_events.count();
Chris@1649 139 }
Chris@1649 140 bool isEmpty() const {
Chris@1649 141 return m_events.isEmpty();
Chris@1649 142 }
Chris@1649 143 bool containsEvent(const Event &e) const {
Chris@1649 144 return m_events.contains(e);
Chris@1649 145 }
Chris@1649 146 EventVector getAllEvents() const {
Chris@1649 147 return m_events.getAllEvents();
Chris@1649 148 }
Chris@1649 149 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1649 150 return m_events.getEventsSpanning(f, duration);
Chris@1649 151 }
Chris@1656 152 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656 153 return m_events.getEventsCovering(f);
Chris@1656 154 }
Chris@1649 155 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1649 156 return m_events.getEventsWithin(f, duration);
Chris@1649 157 }
Chris@1649 158 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1649 159 return m_events.getEventsStartingWithin(f, duration);
Chris@1649 160 }
Chris@1656 161 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656 162 return m_events.getEventsStartingAt(f);
Chris@1649 163 }
Chris@1657 164 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1657 165 std::function<bool(Event)> predicate,
Chris@1657 166 EventSeries::Direction direction,
Chris@1657 167 Event &found) const {
Chris@1657 168 return m_events.getNearestEventMatching
Chris@1657 169 (startSearchAt, predicate, direction, found);
Chris@1657 170 }
Chris@1649 171
Chris@1649 172 /**
Chris@1649 173 * Editing methods.
Chris@1649 174 */
Chris@1649 175 void add(Event e) override {
Chris@1649 176
Chris@1649 177 bool allChange = false;
Chris@1649 178
Chris@1649 179 {
Chris@1649 180 QMutexLocker locker(&m_mutex);
Chris@1649 181 m_events.add(e);
Chris@1649 182
Chris@1649 183 float v = e.getValue();
Chris@1649 184 if (!ISNAN(v) && !ISINF(v)) {
Chris@1649 185 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1649 186 m_valueMinimum = v; allChange = true;
Chris@1649 187 }
Chris@1649 188 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1649 189 m_valueMaximum = v; allChange = true;
Chris@1649 190 }
Chris@1649 191 m_haveExtents = true;
Chris@1649 192 }
Chris@1649 193
Chris@1649 194 if (e.hasValue() && e.getValue() != 0.f) {
Chris@1649 195 m_haveDistinctValues = true;
Chris@1649 196 }
Chris@1649 197 }
Chris@1649 198
Chris@1651 199 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution);
Chris@1651 200
Chris@1649 201 if (allChange) {
Chris@1752 202 emit modelChanged(getId());
Chris@1649 203 }
Chris@1649 204 }
Chris@1649 205
Chris@1649 206 void remove(Event e) override {
Chris@1649 207 {
Chris@1649 208 QMutexLocker locker(&m_mutex);
Chris@1649 209 m_events.remove(e);
Chris@1649 210 }
Chris@1752 211 emit modelChangedWithin(getId(),
Chris@1752 212 e.getFrame(),
Chris@1649 213 e.getFrame() + e.getDuration() + m_resolution);
Chris@1649 214 }
Chris@1649 215
Chris@1649 216 /**
Chris@441 217 * TabularModel methods.
Chris@441 218 */
Chris@1649 219
Chris@1649 220 int getRowCount() const override {
Chris@1649 221 return m_events.count();
Chris@1649 222 }
Chris@1649 223
Chris@1649 224 int getColumnCount() const override {
Chris@618 225 return 5;
Chris@441 226 }
Chris@441 227
Chris@1649 228 bool isColumnTimeValue(int column) const override {
Chris@1649 229 // NB duration is not a "time value" -- that's for columns
Chris@1649 230 // whose sort ordering is exactly that of the frame time
Chris@1649 231 return (column < 2);
Chris@1649 232 }
Chris@1649 233
Chris@1649 234 sv_frame_t getFrameForRow(int row) const override {
Chris@1649 235 if (row < 0 || row >= m_events.count()) {
Chris@1649 236 return 0;
Chris@1649 237 }
Chris@1649 238 Event e = m_events.getEventByIndex(row);
Chris@1649 239 return e.getFrame();
Chris@1649 240 }
Chris@1649 241
Chris@1649 242 int getRowForFrame(sv_frame_t frame) const override {
Chris@1649 243 return m_events.getIndexForEvent(Event(frame));
Chris@1649 244 }
Chris@1649 245
Chris@1649 246 QString getHeading(int column) const override {
Chris@441 247 switch (column) {
Chris@441 248 case 0: return tr("Time");
Chris@441 249 case 1: return tr("Frame");
Chris@441 250 case 2: return tr("Value");
Chris@441 251 case 3: return tr("Duration");
Chris@441 252 case 4: return tr("Label");
Chris@441 253 default: return tr("Unknown");
Chris@441 254 }
Chris@441 255 }
Chris@441 256
Chris@1651 257 SortType getSortType(int column) const override {
Chris@1651 258 if (column == 4) return SortAlphabetical;
Chris@1651 259 return SortNumeric;
Chris@1651 260 }
Chris@1651 261
Chris@1649 262 QVariant getData(int row, int column, int role) const override {
Chris@1649 263
Chris@1649 264 if (row < 0 || row >= m_events.count()) {
Chris@1649 265 return QVariant();
Chris@441 266 }
Chris@441 267
Chris@1649 268 Event e = m_events.getEventByIndex(row);
Chris@441 269
Chris@441 270 switch (column) {
Chris@1649 271 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1649 272 case 1: return int(e.getFrame());
Chris@1649 273 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1649 274 case 3: return int(e.getDuration());
Chris@1649 275 case 4: return e.getLabel();
Chris@441 276 default: return QVariant();
Chris@441 277 }
Chris@441 278 }
Chris@441 279
Chris@1649 280 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1649 281
Chris@1649 282 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1649 283 if (role != Qt::EditRole) return nullptr;
Chris@1649 284
Chris@1649 285 Event e0 = m_events.getEventByIndex(row);
Chris@1649 286 Event e1;
Chris@1649 287
Chris@1649 288 switch (column) {
Chris@1649 289 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1649 290 getSampleRate()))); break;
Chris@1649 291 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1649 292 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1649 293 case 3: e1 = e0.withDuration(value.toInt()); break;
Chris@1649 294 case 4: e1 = e0.withLabel(value.toString()); break;
Chris@441 295 }
Chris@441 296
Chris@1742 297 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1649 298 command->remove(e0);
Chris@1649 299 command->add(e1);
Chris@441 300 return command->finish();
Chris@441 301 }
Chris@441 302
Chris@1649 303
Chris@1649 304 /**
Chris@1649 305 * XmlExportable methods.
Chris@1649 306 */
Chris@1649 307 void toXml(QTextStream &out,
Chris@1649 308 QString indent = "",
Chris@1649 309 QString extraAttributes = "") const override {
Chris@1649 310
Chris@1649 311 Model::toXml
Chris@1649 312 (out,
Chris@1649 313 indent,
Chris@1649 314 QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" "
Chris@1649 315 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
Chris@1649 316 "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" "
Chris@1651 317 "units=\"%8\" %9")
Chris@1649 318 .arg(m_resolution)
Chris@1651 319 .arg("true") // always true after model reaches 100% -
Chris@1651 320 // subsequent events are always notified
Chris@1677 321 .arg(m_events.getExportId())
Chris@1649 322 .arg("region")
Chris@1649 323 .arg(m_valueQuantization)
Chris@1649 324 .arg(m_valueMinimum)
Chris@1649 325 .arg(m_valueMaximum)
Chris@1651 326 .arg(encodeEntities(m_units))
Chris@1649 327 .arg(extraAttributes));
Chris@1649 328
Chris@1649 329 m_events.toXml(out, indent, QString("dimensions=\"3\""));
Chris@442 330 }
Chris@1679 331
Chris@1679 332 QString toDelimitedDataString(QString delimiter,
Chris@1679 333 DataExportOptions options,
Chris@1679 334 sv_frame_t startFrame,
Chris@1679 335 sv_frame_t duration) const override {
Chris@1679 336 return m_events.toDelimitedDataString
Chris@1679 337 (delimiter,
Chris@1679 338 options,
Chris@1679 339 startFrame,
Chris@1679 340 duration,
Chris@1679 341 m_sampleRate,
Chris@1679 342 m_resolution,
Chris@1679 343 Event().withValue(0.f).withDuration(m_resolution));
Chris@1679 344 }
Chris@1679 345
Chris@441 346 protected:
Chris@1649 347 sv_samplerate_t m_sampleRate;
Chris@1649 348 int m_resolution;
Chris@1649 349
Chris@1649 350 float m_valueMinimum;
Chris@1649 351 float m_valueMaximum;
Chris@1649 352 bool m_haveExtents;
Chris@441 353 float m_valueQuantization;
Chris@442 354 bool m_haveDistinctValues;
Chris@1649 355 QString m_units;
Chris@1651 356 DeferredNotifier m_notifier;
Chris@1651 357 int m_completion;
Chris@1649 358
Chris@1649 359 EventSeries m_events;
Chris@1649 360
Chris@1649 361 mutable QMutex m_mutex;
Chris@441 362 };
Chris@441 363
Chris@441 364 #endif