annotate data/model/RegionModel.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 8bf3a52a1604
children f97d64b8674f
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@1651 53 notifyOnAdd ?
Chris@1651 54 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 55 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 56 m_completion(100) {
Chris@441 57 }
Chris@441 58
Chris@1040 59 RegionModel(sv_samplerate_t sampleRate, int resolution,
Chris@459 60 float valueMinimum, float valueMaximum,
Chris@459 61 bool notifyOnAdd = true) :
Chris@1649 62 m_sampleRate(sampleRate),
Chris@1649 63 m_resolution(resolution),
Chris@1649 64 m_valueMinimum(valueMinimum),
Chris@1649 65 m_valueMaximum(valueMaximum),
Chris@1649 66 m_haveExtents(false),
Chris@1429 67 m_valueQuantization(0),
Chris@1649 68 m_haveDistinctValues(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@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@1659 80 bool isSparse() const { 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@1659 86 sv_frame_t getEndFrame() 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@1651 114 { QMutexLocker locker(&m_mutex);
Chris@1651 115 if (m_completion == completion) return;
Chris@1651 116 m_completion = completion;
Chris@1649 117 }
Chris@1649 118
Chris@1651 119 if (update) {
Chris@1651 120 m_notifier.makeDeferredNotifications();
Chris@1649 121 }
Chris@1651 122
Chris@1651 123 emit completionChanged();
Chris@1651 124
Chris@1651 125 if (completion == 100) {
Chris@1651 126 // henceforth:
Chris@1651 127 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1649 128 emit modelChanged();
Chris@1649 129 }
Chris@441 130 }
Chris@441 131
Chris@441 132 /**
Chris@1649 133 * Query methods.
Chris@1649 134 */
Chris@1649 135 int getEventCount() const {
Chris@1649 136 return m_events.count();
Chris@1649 137 }
Chris@1649 138 bool isEmpty() const {
Chris@1649 139 return m_events.isEmpty();
Chris@1649 140 }
Chris@1649 141 bool containsEvent(const Event &e) const {
Chris@1649 142 return m_events.contains(e);
Chris@1649 143 }
Chris@1649 144 EventVector getAllEvents() const {
Chris@1649 145 return m_events.getAllEvents();
Chris@1649 146 }
Chris@1649 147 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1649 148 return m_events.getEventsSpanning(f, duration);
Chris@1649 149 }
Chris@1656 150 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656 151 return m_events.getEventsCovering(f);
Chris@1656 152 }
Chris@1649 153 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1649 154 return m_events.getEventsWithin(f, duration);
Chris@1649 155 }
Chris@1649 156 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1649 157 return m_events.getEventsStartingWithin(f, duration);
Chris@1649 158 }
Chris@1656 159 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656 160 return m_events.getEventsStartingAt(f);
Chris@1649 161 }
Chris@1657 162 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1657 163 std::function<bool(Event)> predicate,
Chris@1657 164 EventSeries::Direction direction,
Chris@1657 165 Event &found) const {
Chris@1657 166 return m_events.getNearestEventMatching
Chris@1657 167 (startSearchAt, predicate, direction, found);
Chris@1657 168 }
Chris@1649 169
Chris@1649 170 /**
Chris@1649 171 * Editing methods.
Chris@1649 172 */
Chris@1649 173 void add(Event e) override {
Chris@1649 174
Chris@1649 175 bool allChange = false;
Chris@1649 176
Chris@1649 177 {
Chris@1649 178 QMutexLocker locker(&m_mutex);
Chris@1649 179 m_events.add(e);
Chris@1649 180
Chris@1649 181 float v = e.getValue();
Chris@1649 182 if (!ISNAN(v) && !ISINF(v)) {
Chris@1649 183 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1649 184 m_valueMinimum = v; allChange = true;
Chris@1649 185 }
Chris@1649 186 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1649 187 m_valueMaximum = v; allChange = true;
Chris@1649 188 }
Chris@1649 189 m_haveExtents = true;
Chris@1649 190 }
Chris@1649 191
Chris@1649 192 if (e.hasValue() && e.getValue() != 0.f) {
Chris@1649 193 m_haveDistinctValues = true;
Chris@1649 194 }
Chris@1649 195 }
Chris@1649 196
Chris@1651 197 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution);
Chris@1651 198
Chris@1649 199 if (allChange) {
Chris@1649 200 emit modelChanged();
Chris@1649 201 }
Chris@1649 202 }
Chris@1649 203
Chris@1649 204 void remove(Event e) override {
Chris@1649 205 {
Chris@1649 206 QMutexLocker locker(&m_mutex);
Chris@1649 207 m_events.remove(e);
Chris@1649 208 }
Chris@1649 209 emit modelChangedWithin(e.getFrame(),
Chris@1649 210 e.getFrame() + e.getDuration() + m_resolution);
Chris@1649 211 }
Chris@1649 212
Chris@1649 213 /**
Chris@441 214 * TabularModel methods.
Chris@441 215 */
Chris@1649 216
Chris@1649 217 int getRowCount() const override {
Chris@1649 218 return m_events.count();
Chris@1649 219 }
Chris@1649 220
Chris@1649 221 int getColumnCount() const override {
Chris@618 222 return 5;
Chris@441 223 }
Chris@441 224
Chris@1649 225 bool isColumnTimeValue(int column) const override {
Chris@1649 226 // NB duration is not a "time value" -- that's for columns
Chris@1649 227 // whose sort ordering is exactly that of the frame time
Chris@1649 228 return (column < 2);
Chris@1649 229 }
Chris@1649 230
Chris@1649 231 sv_frame_t getFrameForRow(int row) const override {
Chris@1649 232 if (row < 0 || row >= m_events.count()) {
Chris@1649 233 return 0;
Chris@1649 234 }
Chris@1649 235 Event e = m_events.getEventByIndex(row);
Chris@1649 236 return e.getFrame();
Chris@1649 237 }
Chris@1649 238
Chris@1649 239 int getRowForFrame(sv_frame_t frame) const override {
Chris@1649 240 return m_events.getIndexForEvent(Event(frame));
Chris@1649 241 }
Chris@1649 242
Chris@1649 243 QString getHeading(int column) const override {
Chris@441 244 switch (column) {
Chris@441 245 case 0: return tr("Time");
Chris@441 246 case 1: return tr("Frame");
Chris@441 247 case 2: return tr("Value");
Chris@441 248 case 3: return tr("Duration");
Chris@441 249 case 4: return tr("Label");
Chris@441 250 default: return tr("Unknown");
Chris@441 251 }
Chris@441 252 }
Chris@441 253
Chris@1651 254 SortType getSortType(int column) const override {
Chris@1651 255 if (column == 4) return SortAlphabetical;
Chris@1651 256 return SortNumeric;
Chris@1651 257 }
Chris@1651 258
Chris@1649 259 QVariant getData(int row, int column, int role) const override {
Chris@1649 260
Chris@1649 261 if (row < 0 || row >= m_events.count()) {
Chris@1649 262 return QVariant();
Chris@441 263 }
Chris@441 264
Chris@1649 265 Event e = m_events.getEventByIndex(row);
Chris@441 266
Chris@441 267 switch (column) {
Chris@1649 268 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1649 269 case 1: return int(e.getFrame());
Chris@1649 270 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1649 271 case 3: return int(e.getDuration());
Chris@1649 272 case 4: return e.getLabel();
Chris@441 273 default: return QVariant();
Chris@441 274 }
Chris@441 275 }
Chris@441 276
Chris@1649 277 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1649 278
Chris@1649 279 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1649 280 if (role != Qt::EditRole) return nullptr;
Chris@1649 281
Chris@1649 282 Event e0 = m_events.getEventByIndex(row);
Chris@1649 283 Event e1;
Chris@1649 284
Chris@1649 285 switch (column) {
Chris@1649 286 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1649 287 getSampleRate()))); break;
Chris@1649 288 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1649 289 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1649 290 case 3: e1 = e0.withDuration(value.toInt()); break;
Chris@1649 291 case 4: e1 = e0.withLabel(value.toString()); break;
Chris@441 292 }
Chris@441 293
Chris@1649 294 ChangeEventsCommand *command =
Chris@1649 295 new ChangeEventsCommand(this, tr("Edit Data"));
Chris@1649 296 command->remove(e0);
Chris@1649 297 command->add(e1);
Chris@441 298 return command->finish();
Chris@441 299 }
Chris@441 300
Chris@1649 301
Chris@1649 302 /**
Chris@1649 303 * XmlExportable methods.
Chris@1649 304 */
Chris@1649 305 void toXml(QTextStream &out,
Chris@1649 306 QString indent = "",
Chris@1649 307 QString extraAttributes = "") const override {
Chris@1649 308
Chris@1649 309 Model::toXml
Chris@1649 310 (out,
Chris@1649 311 indent,
Chris@1649 312 QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" "
Chris@1649 313 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
Chris@1649 314 "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" "
Chris@1651 315 "units=\"%8\" %9")
Chris@1649 316 .arg(m_resolution)
Chris@1651 317 .arg("true") // always true after model reaches 100% -
Chris@1651 318 // subsequent events are always notified
Chris@1649 319 .arg(getObjectExportId(&m_events))
Chris@1649 320 .arg("region")
Chris@1649 321 .arg(m_valueQuantization)
Chris@1649 322 .arg(m_valueMinimum)
Chris@1649 323 .arg(m_valueMaximum)
Chris@1651 324 .arg(encodeEntities(m_units))
Chris@1649 325 .arg(extraAttributes));
Chris@1649 326
Chris@1649 327 m_events.toXml(out, indent, QString("dimensions=\"3\""));
Chris@442 328 }
Chris@442 329
Chris@441 330 protected:
Chris@1649 331 sv_samplerate_t m_sampleRate;
Chris@1649 332 int m_resolution;
Chris@1649 333
Chris@1649 334 float m_valueMinimum;
Chris@1649 335 float m_valueMaximum;
Chris@1649 336 bool m_haveExtents;
Chris@441 337 float m_valueQuantization;
Chris@442 338 bool m_haveDistinctValues;
Chris@1649 339 QString m_units;
Chris@1651 340 DeferredNotifier m_notifier;
Chris@1651 341 int m_completion;
Chris@1649 342
Chris@1649 343 EventSeries m_events;
Chris@1649 344
Chris@1649 345 mutable QMutex m_mutex;
Chris@441 346 };
Chris@441 347
Chris@441 348 #endif