annotate data/model/SparseTimeValueModel.h @ 1671:82d03c9661f9 single-point

Rework isReady()/getCompletion() on models. Previously the new overhauled models were implementing getCompletion() but inheriting a version of isReady() (from the Model base) that didn't call it, referring only to isOK(). So they were reporting completion as soon as they had begun. Instead hoist getCompletion() to abstract base and call it from Model::isReady().
author Chris Cannam
date Wed, 27 Mar 2019 13:15:16 +0000
parents 353a2d15f213
children f97d64b8674f
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@1651 51 notifyOnAdd ?
Chris@1651 52 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 53 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 54 m_completion(100) {
Chris@868 55 // Model is playable, but may not sound (if units not Hz or
Chris@868 56 // range unsuitable)
Chris@1429 57 PlayParameterRepository::getInstance()->addPlayable(this);
Chris@256 58 }
Chris@256 59
Chris@1040 60 SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429 61 float valueMinimum, float valueMaximum,
Chris@1429 62 bool notifyOnAdd = true) :
Chris@1651 63 m_sampleRate(sampleRate),
Chris@1651 64 m_resolution(resolution),
Chris@1651 65 m_valueMinimum(valueMinimum),
Chris@1651 66 m_valueMaximum(valueMaximum),
Chris@1651 67 m_haveExtents(false),
Chris@1651 68 m_haveTextLabels(false),
Chris@1651 69 m_notifier(this,
Chris@1651 70 notifyOnAdd ?
Chris@1651 71 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 72 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 73 m_completion(100) {
Chris@868 74 // Model is playable, but may not sound (if units not Hz or
Chris@868 75 // range unsuitable)
Chris@1429 76 PlayParameterRepository::getInstance()->addPlayable(this);
Chris@868 77 }
Chris@868 78
Chris@1651 79 virtual ~SparseTimeValueModel() {
Chris@1429 80 PlayParameterRepository::getInstance()->removePlayable(this);
Chris@147 81 }
Chris@345 82
Chris@1580 83 QString getTypeName() const override { return tr("Sparse Time-Value"); }
Chris@1659 84 bool isSparse() const { return true; }
Chris@1659 85 bool isOK() const override { return true; }
Chris@420 86
Chris@1659 87 sv_frame_t getStartFrame() const override {
Chris@1659 88 return m_events.getStartFrame();
Chris@1659 89 }
Chris@1659 90 sv_frame_t getEndFrame() const override {
Chris@1659 91 if (m_events.isEmpty()) return 0;
Chris@1659 92 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1659 93 if (e % m_resolution == 0) return e;
Chris@1659 94 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 95 }
Chris@1659 96
Chris@1651 97 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1651 98 int getResolution() const { return m_resolution; }
Chris@1651 99
Chris@1580 100 bool canPlay() const override { return true; }
Chris@1580 101 bool getDefaultPlayAudible() const override { return false; } // user must unmute
Chris@868 102
Chris@1651 103 QString getScaleUnits() const { return m_units; }
Chris@1651 104 void setScaleUnits(QString units) {
Chris@1651 105 m_units = units;
Chris@1651 106 UnitDatabase::getInstance()->registerUnit(units);
Chris@1651 107 }
Chris@1651 108
Chris@1651 109 bool hasTextLabels() const { return m_haveTextLabels; }
Chris@1651 110
Chris@1651 111 float getValueMinimum() const { return m_valueMinimum; }
Chris@1651 112 float getValueMaximum() const { return m_valueMaximum; }
Chris@1651 113
Chris@1671 114 int getCompletion() const override { return m_completion; }
Chris@1651 115
Chris@1651 116 void setCompletion(int completion, bool update = true) {
Chris@1655 117
Chris@1651 118 { QMutexLocker locker(&m_mutex);
Chris@1651 119 if (m_completion == completion) return;
Chris@1651 120 m_completion = completion;
Chris@1651 121 }
Chris@1651 122
Chris@1651 123 if (update) {
Chris@1651 124 m_notifier.makeDeferredNotifications();
Chris@1651 125 }
Chris@1651 126
Chris@1651 127 emit completionChanged();
Chris@1651 128
Chris@1651 129 if (completion == 100) {
Chris@1651 130 // henceforth:
Chris@1651 131 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1651 132 emit modelChanged();
Chris@1651 133 }
Chris@1651 134 }
Chris@1651 135
Chris@1651 136 /**
Chris@1651 137 * Query methods.
Chris@1651 138 */
Chris@1651 139
Chris@1651 140 int getEventCount() const {
Chris@1651 141 return m_events.count();
Chris@1651 142 }
Chris@1651 143 bool isEmpty() const {
Chris@1651 144 return m_events.isEmpty();
Chris@1651 145 }
Chris@1651 146 bool containsEvent(const Event &e) const {
Chris@1651 147 return m_events.contains(e);
Chris@1651 148 }
Chris@1651 149 EventVector getAllEvents() const {
Chris@1651 150 return m_events.getAllEvents();
Chris@1651 151 }
Chris@1651 152 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 153 return m_events.getEventsSpanning(f, duration);
Chris@1651 154 }
Chris@1656 155 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656 156 return m_events.getEventsCovering(f);
Chris@1656 157 }
Chris@1655 158 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1655 159 int overspill = 0) const {
Chris@1655 160 return m_events.getEventsWithin(f, duration, overspill);
Chris@1651 161 }
Chris@1651 162 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 163 return m_events.getEventsStartingWithin(f, duration);
Chris@1651 164 }
Chris@1656 165 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656 166 return m_events.getEventsStartingAt(f);
Chris@1651 167 }
Chris@1655 168 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1655 169 std::function<bool(Event)> predicate,
Chris@1655 170 EventSeries::Direction direction,
Chris@1655 171 Event &found) const {
Chris@1655 172 return m_events.getNearestEventMatching
Chris@1655 173 (startSearchAt, predicate, direction, found);
Chris@1655 174 }
Chris@1651 175
Chris@1651 176 /**
Chris@1651 177 * Editing methods.
Chris@1651 178 */
Chris@1651 179 void add(Event e) override {
Chris@1651 180
Chris@1651 181 bool allChange = false;
Chris@1651 182
Chris@1658 183 { QMutexLocker locker(&m_mutex);
Chris@1655 184 m_events.add(e.withoutDuration()); // can't have duration here
Chris@1651 185
Chris@1651 186 if (e.getLabel() != "") {
Chris@1651 187 m_haveTextLabels = true;
Chris@1651 188 }
Chris@1651 189
Chris@1651 190 float v = e.getValue();
Chris@1651 191 if (!ISNAN(v) && !ISINF(v)) {
Chris@1651 192 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1651 193 m_valueMinimum = v; allChange = true;
Chris@1651 194 }
Chris@1651 195 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1651 196 m_valueMaximum = v; allChange = true;
Chris@1651 197 }
Chris@1651 198 m_haveExtents = true;
Chris@1651 199 }
Chris@1651 200 }
Chris@1651 201
Chris@1651 202 m_notifier.update(e.getFrame(), m_resolution);
Chris@1651 203
Chris@1651 204 if (allChange) {
Chris@1651 205 emit modelChanged();
Chris@1651 206 }
Chris@1651 207 }
Chris@1651 208
Chris@1651 209 void remove(Event e) override {
Chris@1651 210 {
Chris@1651 211 QMutexLocker locker(&m_mutex);
Chris@1651 212 m_events.remove(e);
Chris@1651 213 }
Chris@1651 214 emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution);
Chris@1651 215 }
Chris@1651 216
Chris@420 217 /**
Chris@420 218 * TabularModel methods.
Chris@420 219 */
Chris@420 220
Chris@1651 221 int getRowCount() const override {
Chris@1651 222 return m_events.count();
Chris@1651 223 }
Chris@1651 224
Chris@1651 225 int getColumnCount() const override {
Chris@420 226 return 4;
Chris@420 227 }
Chris@420 228
Chris@1651 229 bool isColumnTimeValue(int column) const override {
Chris@1651 230 return (column < 2);
Chris@1651 231 }
Chris@1651 232
Chris@1651 233 sv_frame_t getFrameForRow(int row) const override {
Chris@1651 234 if (row < 0 || row >= m_events.count()) {
Chris@1651 235 return 0;
Chris@1651 236 }
Chris@1651 237 Event e = m_events.getEventByIndex(row);
Chris@1651 238 return e.getFrame();
Chris@1651 239 }
Chris@1651 240
Chris@1651 241 int getRowForFrame(sv_frame_t frame) const override {
Chris@1651 242 return m_events.getIndexForEvent(Event(frame));
Chris@1651 243 }
Chris@1651 244
Chris@1651 245 QString getHeading(int column) const override {
Chris@420 246 switch (column) {
Chris@420 247 case 0: return tr("Time");
Chris@420 248 case 1: return tr("Frame");
Chris@420 249 case 2: return tr("Value");
Chris@420 250 case 3: return tr("Label");
Chris@420 251 default: return tr("Unknown");
Chris@420 252 }
Chris@420 253 }
Chris@420 254
Chris@1651 255 SortType getSortType(int column) const override {
Chris@1651 256 if (column == 3) return SortAlphabetical;
Chris@1651 257 return SortNumeric;
Chris@1651 258 }
Chris@1651 259
Chris@1651 260 QVariant getData(int row, int column, int role) const override {
Chris@1651 261
Chris@1651 262 if (row < 0 || row >= m_events.count()) {
Chris@1651 263 return QVariant();
Chris@425 264 }
Chris@425 265
Chris@1651 266 Event e = m_events.getEventByIndex(row);
Chris@420 267
Chris@420 268 switch (column) {
Chris@1651 269 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1651 270 case 1: return int(e.getFrame());
Chris@1651 271 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1651 272 case 3: return e.getLabel();
Chris@420 273 default: return QVariant();
Chris@420 274 }
Chris@420 275 }
Chris@420 276
Chris@1651 277 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1651 278 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1651 279 if (role != Qt::EditRole) return nullptr;
Chris@1651 280
Chris@1651 281 Event e0 = m_events.getEventByIndex(row);
Chris@1651 282 Event e1;
Chris@1651 283
Chris@1651 284 switch (column) {
Chris@1651 285 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1651 286 getSampleRate()))); break;
Chris@1651 287 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1651 288 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1651 289 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 290 }
Chris@425 291
Chris@1651 292 ChangeEventsCommand *command =
Chris@1651 293 new ChangeEventsCommand(this, tr("Edit Data"));
Chris@1651 294 command->remove(e0);
Chris@1651 295 command->add(e1);
Chris@420 296 return command->finish();
Chris@420 297 }
Chris@1651 298
Chris@1651 299 /**
Chris@1651 300 * XmlExportable methods.
Chris@1651 301 */
Chris@1651 302 void toXml(QTextStream &out,
Chris@1651 303 QString indent = "",
Chris@1651 304 QString extraAttributes = "") const override {
Chris@420 305
Chris@1651 306 Model::toXml
Chris@1651 307 (out,
Chris@1651 308 indent,
Chris@1651 309 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1651 310 "notifyOnAdd=\"%2\" dataset=\"%3\" "
Chris@1651 311 "minimum=\"%4\" maximum=\"%5\" "
Chris@1651 312 "units=\"%6\" %7")
Chris@1651 313 .arg(m_resolution)
Chris@1651 314 .arg("true") // always true after model reaches 100% -
Chris@1651 315 // subsequent events are always notified
Chris@1651 316 .arg(getObjectExportId(&m_events))
Chris@1651 317 .arg(m_valueMinimum)
Chris@1651 318 .arg(m_valueMaximum)
Chris@1651 319 .arg(encodeEntities(m_units))
Chris@1651 320 .arg(extraAttributes));
Chris@1651 321
Chris@1651 322 m_events.toXml(out, indent, QString("dimensions=\"2\""));
Chris@420 323 }
Chris@1651 324
Chris@1651 325 protected:
Chris@1651 326 sv_samplerate_t m_sampleRate;
Chris@1651 327 int m_resolution;
Chris@422 328
Chris@1651 329 float m_valueMinimum;
Chris@1651 330 float m_valueMaximum;
Chris@1651 331 bool m_haveExtents;
Chris@1651 332 bool m_haveTextLabels;
Chris@1651 333 QString m_units;
Chris@1651 334 DeferredNotifier m_notifier;
Chris@1651 335 int m_completion;
Chris@1651 336
Chris@1651 337 EventSeries m_events;
Chris@1651 338
Chris@1651 339 mutable QMutex m_mutex;
Chris@147 340 };
Chris@147 341
Chris@147 342
Chris@147 343 #endif
Chris@147 344
Chris@147 345
Chris@147 346