annotate data/model/SparseOneDimensionalModel.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_ONE_DIMENSIONAL_MODEL_H
Chris@1581 16 #define SV_SPARSE_ONE_DIMENSIONAL_MODEL_H
Chris@147 17
Chris@1658 18 #include "EventCommands.h"
Chris@1658 19 #include "TabularModel.h"
Chris@1658 20 #include "Model.h"
Chris@1658 21 #include "DeferredNotifier.h"
Chris@1658 22
Chris@1615 23 #include "base/NoteData.h"
Chris@1658 24 #include "base/EventSeries.h"
Chris@1643 25 #include "base/NoteExportable.h"
Chris@150 26 #include "base/PlayParameterRepository.h"
Chris@147 27 #include "base/RealTime.h"
Chris@147 28
Chris@1658 29 #include "system/System.h"
Chris@1658 30
Chris@387 31 #include <QStringList>
Chris@387 32
Chris@1658 33 /**
Chris@1658 34 * A model representing a series of time instants with optional labels
Chris@1658 35 * but without values.
Chris@1658 36 */
Chris@1658 37 class SparseOneDimensionalModel : public Model,
Chris@1658 38 public TabularModel,
Chris@1658 39 public EventEditable,
Chris@852 40 public NoteExportable
Chris@147 41 {
Chris@423 42 Q_OBJECT
Chris@423 43
Chris@147 44 public:
Chris@1658 45 SparseOneDimensionalModel(sv_samplerate_t sampleRate,
Chris@1658 46 int resolution,
Chris@1429 47 bool notifyOnAdd = true) :
Chris@1658 48 m_sampleRate(sampleRate),
Chris@1658 49 m_resolution(resolution),
Chris@1660 50 m_haveTextLabels(false),
Chris@1658 51 m_notifier(this,
Chris@1658 52 notifyOnAdd ?
Chris@1658 53 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1658 54 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1658 55 m_completion(100) {
Chris@1429 56 PlayParameterRepository::getInstance()->addPlayable(this);
Chris@391 57 }
Chris@391 58
Chris@1658 59 virtual ~SparseOneDimensionalModel() {
Chris@391 60 PlayParameterRepository::getInstance()->removePlayable(this);
Chris@391 61 }
Chris@391 62
Chris@1580 63 QString getTypeName() const override { return tr("Sparse 1-D"); }
Chris@1659 64 bool isSparse() const { return true; }
Chris@1659 65 bool isOK() const override { return true; }
Chris@420 66
Chris@1659 67 sv_frame_t getStartFrame() const override {
Chris@1659 68 return m_events.getStartFrame();
Chris@1659 69 }
Chris@1659 70 sv_frame_t getEndFrame() const override {
Chris@1659 71 if (m_events.isEmpty()) return 0;
Chris@1659 72 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1659 73 if (e % m_resolution == 0) return e;
Chris@1659 74 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 75 }
Chris@1659 76
Chris@1658 77 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1658 78 int getResolution() const { return m_resolution; }
Chris@1658 79
Chris@1658 80 bool canPlay() const override { return true; }
Chris@1658 81 QString getDefaultPlayClipId() const override { return "tap"; }
Chris@1658 82
Chris@1660 83 bool hasTextLabels() const { return m_haveTextLabels; }
Chris@1671 84
Chris@1671 85 int getCompletion() const override { return m_completion; }
Chris@1658 86
Chris@1658 87 void setCompletion(int completion, bool update = true) {
Chris@1658 88
Chris@1658 89 { QMutexLocker locker(&m_mutex);
Chris@1658 90 if (m_completion == completion) return;
Chris@1658 91 m_completion = completion;
Chris@1658 92 }
Chris@1658 93
Chris@1658 94 if (update) {
Chris@1658 95 m_notifier.makeDeferredNotifications();
Chris@1658 96 }
Chris@1658 97
Chris@1658 98 emit completionChanged();
Chris@1658 99
Chris@1658 100 if (completion == 100) {
Chris@1658 101 // henceforth:
Chris@1658 102 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1658 103 emit modelChanged();
Chris@1658 104 }
Chris@1658 105 }
Chris@1658 106
Chris@1658 107 /**
Chris@1658 108 * Query methods.
Chris@1658 109 */
Chris@1658 110
Chris@1658 111 int getEventCount() const {
Chris@1658 112 return m_events.count();
Chris@1658 113 }
Chris@1658 114 bool isEmpty() const {
Chris@1658 115 return m_events.isEmpty();
Chris@1658 116 }
Chris@1658 117 bool containsEvent(const Event &e) const {
Chris@1658 118 return m_events.contains(e);
Chris@1658 119 }
Chris@1658 120 EventVector getAllEvents() const {
Chris@1658 121 return m_events.getAllEvents();
Chris@1658 122 }
Chris@1658 123 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1658 124 return m_events.getEventsSpanning(f, duration);
Chris@1658 125 }
Chris@1658 126 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1658 127 return m_events.getEventsCovering(f);
Chris@1658 128 }
Chris@1658 129 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1658 130 int overspill = 0) const {
Chris@1658 131 return m_events.getEventsWithin(f, duration, overspill);
Chris@1658 132 }
Chris@1658 133 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1658 134 return m_events.getEventsStartingWithin(f, duration);
Chris@1658 135 }
Chris@1658 136 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1658 137 return m_events.getEventsStartingAt(f);
Chris@1658 138 }
Chris@1658 139 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1658 140 std::function<bool(Event)> predicate,
Chris@1658 141 EventSeries::Direction direction,
Chris@1658 142 Event &found) const {
Chris@1658 143 return m_events.getNearestEventMatching
Chris@1658 144 (startSearchAt, predicate, direction, found);
Chris@1658 145 }
Chris@1658 146
Chris@1658 147 /**
Chris@1658 148 * Editing methods.
Chris@1658 149 */
Chris@1658 150 void add(Event e) override {
Chris@1658 151
Chris@1658 152 { QMutexLocker locker(&m_mutex);
Chris@1658 153 m_events.add(e.withoutValue().withoutDuration());
Chris@1660 154
Chris@1660 155 if (e.getLabel() != "") {
Chris@1660 156 m_haveTextLabels = true;
Chris@1660 157 }
Chris@1658 158 }
Chris@1658 159
Chris@1658 160 m_notifier.update(e.getFrame(), m_resolution);
Chris@1658 161 }
Chris@1658 162
Chris@1658 163 void remove(Event e) override {
Chris@1658 164 { QMutexLocker locker(&m_mutex);
Chris@1658 165 m_events.remove(e);
Chris@1658 166 }
Chris@1658 167 emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution);
Chris@1658 168 }
Chris@1658 169
Chris@420 170 /**
Chris@420 171 * TabularModel methods.
Chris@420 172 */
Chris@420 173
Chris@1658 174 int getRowCount() const override {
Chris@1658 175 return m_events.count();
Chris@1658 176 }
Chris@1658 177
Chris@1658 178 int getColumnCount() const override {
Chris@420 179 return 3;
Chris@420 180 }
Chris@420 181
Chris@1658 182 bool isColumnTimeValue(int column) const override {
Chris@1658 183 return (column < 2);
Chris@1658 184 }
Chris@1658 185
Chris@1658 186 sv_frame_t getFrameForRow(int row) const override {
Chris@1658 187 if (row < 0 || row >= m_events.count()) {
Chris@1658 188 return 0;
Chris@1658 189 }
Chris@1658 190 Event e = m_events.getEventByIndex(row);
Chris@1658 191 return e.getFrame();
Chris@1658 192 }
Chris@1658 193
Chris@1658 194 int getRowForFrame(sv_frame_t frame) const override {
Chris@1658 195 return m_events.getIndexForEvent(Event(frame));
Chris@1658 196 }
Chris@1658 197
Chris@1658 198 QString getHeading(int column) const override {
Chris@420 199 switch (column) {
Chris@420 200 case 0: return tr("Time");
Chris@420 201 case 1: return tr("Frame");
Chris@420 202 case 2: return tr("Label");
Chris@420 203 default: return tr("Unknown");
Chris@420 204 }
Chris@420 205 }
Chris@420 206
Chris@1658 207 SortType getSortType(int column) const override {
Chris@1658 208 if (column == 2) return SortAlphabetical;
Chris@1658 209 return SortNumeric;
Chris@1658 210 }
Chris@1658 211
Chris@1658 212 QVariant getData(int row, int column, int role) const override {
Chris@1658 213
Chris@1658 214 if (row < 0 || row >= m_events.count()) {
Chris@1658 215 return QVariant();
Chris@425 216 }
Chris@425 217
Chris@1658 218 Event e = m_events.getEventByIndex(row);
Chris@420 219
Chris@420 220 switch (column) {
Chris@1658 221 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1658 222 case 1: return int(e.getFrame());
Chris@1658 223 case 2: return e.getLabel();
Chris@420 224 default: return QVariant();
Chris@420 225 }
Chris@420 226 }
Chris@420 227
Chris@1658 228 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1658 229 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1658 230 if (role != Qt::EditRole) return nullptr;
Chris@1658 231
Chris@1658 232 Event e0 = m_events.getEventByIndex(row);
Chris@1658 233 Event e1;
Chris@1658 234
Chris@1658 235 switch (column) {
Chris@1658 236 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1658 237 getSampleRate()))); break;
Chris@1658 238 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1658 239 case 2: e1 = e0.withLabel(value.toString()); break;
Chris@425 240 }
Chris@425 241
Chris@1658 242 ChangeEventsCommand *command =
Chris@1658 243 new ChangeEventsCommand(this, tr("Edit Data"));
Chris@1658 244 command->remove(e0);
Chris@1658 245 command->add(e1);
Chris@424 246 return command->finish();
Chris@424 247 }
Chris@424 248
Chris@852 249 /**
Chris@852 250 * NoteExportable methods.
Chris@852 251 */
Chris@852 252
Chris@1580 253 NoteList getNotes() const override {
Chris@1643 254 return getNotesStartingWithin(getStartFrame(),
Chris@1643 255 getEndFrame() - getStartFrame());
Chris@852 256 }
Chris@852 257
Chris@1643 258 NoteList getNotesActiveAt(sv_frame_t frame) const override {
Chris@1643 259 return getNotesStartingWithin(frame, 1);
Chris@1643 260 }
Chris@1643 261
Chris@1643 262 NoteList getNotesStartingWithin(sv_frame_t startFrame,
Chris@1643 263 sv_frame_t duration) const override {
Chris@852 264
Chris@852 265 NoteList notes;
Chris@1658 266 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
Chris@1658 267 for (const auto &e: ee) {
Chris@1658 268 notes.push_back(e.toNoteData(getSampleRate(), true));
Chris@852 269 }
Chris@852 270 return notes;
Chris@852 271 }
Chris@1658 272
Chris@1658 273 /**
Chris@1658 274 * XmlExportable methods.
Chris@1658 275 */
Chris@1658 276 void toXml(QTextStream &out,
Chris@1658 277 QString indent = "",
Chris@1658 278 QString extraAttributes = "") const override {
Chris@1658 279
Chris@1658 280 Model::toXml
Chris@1658 281 (out,
Chris@1658 282 indent,
Chris@1658 283 QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" "
Chris@1658 284 "notifyOnAdd=\"%2\" dataset=\"%3\" %4")
Chris@1658 285 .arg(m_resolution)
Chris@1658 286 .arg("true") // always true after model reaches 100% -
Chris@1658 287 // subsequent events are always notified
Chris@1658 288 .arg(getObjectExportId(&m_events))
Chris@1658 289 .arg(extraAttributes));
Chris@1658 290
Chris@1658 291 m_events.toXml(out, indent, QString("dimensions=\"1\""));
Chris@1658 292 }
Chris@1658 293
Chris@1658 294 protected:
Chris@1658 295 sv_samplerate_t m_sampleRate;
Chris@1658 296 int m_resolution;
Chris@1658 297
Chris@1660 298 bool m_haveTextLabels;
Chris@1658 299 DeferredNotifier m_notifier;
Chris@1658 300 int m_completion;
Chris@1658 301
Chris@1658 302 EventSeries m_events;
Chris@1658 303
Chris@1658 304 mutable QMutex m_mutex;
Chris@147 305 };
Chris@147 306
Chris@147 307 #endif
Chris@147 308
Chris@147 309
Chris@147 310