annotate data/model/SparseOneDimensionalModel.h @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents c546429d4c2f
children
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@1752 52 getId(),
Chris@1658 53 notifyOnAdd ?
Chris@1658 54 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1658 55 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1658 56 m_completion(100) {
Chris@1751 57 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 58 (getId().untyped, this);
Chris@391 59 }
Chris@391 60
Chris@1658 61 virtual ~SparseOneDimensionalModel() {
Chris@1751 62 PlayParameterRepository::getInstance()->removePlayable
Chris@1751 63 (getId().untyped);
Chris@391 64 }
Chris@391 65
Chris@1580 66 QString getTypeName() const override { return tr("Sparse 1-D"); }
Chris@1692 67 bool isSparse() const override { return true; }
Chris@1659 68 bool isOK() const override { return true; }
Chris@420 69
Chris@1659 70 sv_frame_t getStartFrame() const override {
Chris@1659 71 return m_events.getStartFrame();
Chris@1659 72 }
Chris@1725 73 sv_frame_t getTrueEndFrame() const override {
Chris@1659 74 if (m_events.isEmpty()) return 0;
Chris@1659 75 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1659 76 if (e % m_resolution == 0) return e;
Chris@1659 77 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 78 }
Chris@1659 79
Chris@1658 80 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1658 81 int getResolution() const { return m_resolution; }
Chris@1658 82
Chris@1658 83 bool canPlay() const override { return true; }
Chris@1658 84 QString getDefaultPlayClipId() const override { return "tap"; }
Chris@1658 85
Chris@1660 86 bool hasTextLabels() const { return m_haveTextLabels; }
Chris@1671 87
Chris@1671 88 int getCompletion() const override { return m_completion; }
Chris@1658 89
Chris@1658 90 void setCompletion(int completion, bool update = true) {
Chris@1658 91
Chris@1798 92 if (m_completion == completion) return;
Chris@1798 93 m_completion = completion;
Chris@1658 94
Chris@1658 95 if (update) {
Chris@1658 96 m_notifier.makeDeferredNotifications();
Chris@1658 97 }
Chris@1658 98
Chris@1752 99 emit completionChanged(getId());
Chris@1658 100
Chris@1658 101 if (completion == 100) {
Chris@1658 102 // henceforth:
Chris@1658 103 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 104 emit modelChanged(getId());
Chris@1658 105 }
Chris@1658 106 }
Chris@1658 107
Chris@1658 108 /**
Chris@1658 109 * Query methods.
Chris@1658 110 */
Chris@1658 111
Chris@1658 112 int getEventCount() const {
Chris@1658 113 return m_events.count();
Chris@1658 114 }
Chris@1658 115 bool isEmpty() const {
Chris@1658 116 return m_events.isEmpty();
Chris@1658 117 }
Chris@1658 118 bool containsEvent(const Event &e) const {
Chris@1658 119 return m_events.contains(e);
Chris@1658 120 }
Chris@1658 121 EventVector getAllEvents() const {
Chris@1658 122 return m_events.getAllEvents();
Chris@1658 123 }
Chris@1658 124 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1658 125 return m_events.getEventsSpanning(f, duration);
Chris@1658 126 }
Chris@1658 127 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1658 128 return m_events.getEventsCovering(f);
Chris@1658 129 }
Chris@1658 130 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1658 131 int overspill = 0) const {
Chris@1658 132 return m_events.getEventsWithin(f, duration, overspill);
Chris@1658 133 }
Chris@1658 134 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1658 135 return m_events.getEventsStartingWithin(f, duration);
Chris@1658 136 }
Chris@1658 137 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1658 138 return m_events.getEventsStartingAt(f);
Chris@1658 139 }
Chris@1658 140 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1658 141 std::function<bool(Event)> predicate,
Chris@1658 142 EventSeries::Direction direction,
Chris@1658 143 Event &found) const {
Chris@1658 144 return m_events.getNearestEventMatching
Chris@1658 145 (startSearchAt, predicate, direction, found);
Chris@1658 146 }
Chris@1658 147
Chris@1658 148 /**
Chris@1658 149 * Editing methods.
Chris@1658 150 */
Chris@1658 151 void add(Event e) override {
Chris@1658 152
Chris@1798 153 m_events.add(e.withoutValue().withoutDuration());
Chris@1660 154
Chris@1798 155 if (e.getLabel() != "") {
Chris@1798 156 m_haveTextLabels = true;
Chris@1658 157 }
Chris@1658 158
Chris@1658 159 m_notifier.update(e.getFrame(), m_resolution);
Chris@1658 160 }
Chris@1658 161
Chris@1658 162 void remove(Event e) override {
Chris@1798 163 m_events.remove(e);
Chris@1752 164 emit modelChangedWithin(getId(),
Chris@1752 165 e.getFrame(), e.getFrame() + m_resolution);
Chris@1658 166 }
Chris@1658 167
Chris@420 168 /**
Chris@420 169 * TabularModel methods.
Chris@420 170 */
Chris@420 171
Chris@1658 172 int getRowCount() const override {
Chris@1658 173 return m_events.count();
Chris@1658 174 }
Chris@1658 175
Chris@1658 176 int getColumnCount() const override {
Chris@420 177 return 3;
Chris@420 178 }
Chris@420 179
Chris@1658 180 bool isColumnTimeValue(int column) const override {
Chris@1658 181 return (column < 2);
Chris@1658 182 }
Chris@1658 183
Chris@1658 184 sv_frame_t getFrameForRow(int row) const override {
Chris@1658 185 if (row < 0 || row >= m_events.count()) {
Chris@1658 186 return 0;
Chris@1658 187 }
Chris@1658 188 Event e = m_events.getEventByIndex(row);
Chris@1658 189 return e.getFrame();
Chris@1658 190 }
Chris@1658 191
Chris@1658 192 int getRowForFrame(sv_frame_t frame) const override {
Chris@1658 193 return m_events.getIndexForEvent(Event(frame));
Chris@1658 194 }
Chris@1658 195
Chris@1658 196 QString getHeading(int column) const override {
Chris@420 197 switch (column) {
Chris@420 198 case 0: return tr("Time");
Chris@420 199 case 1: return tr("Frame");
Chris@420 200 case 2: return tr("Label");
Chris@420 201 default: return tr("Unknown");
Chris@420 202 }
Chris@420 203 }
Chris@420 204
Chris@1658 205 SortType getSortType(int column) const override {
Chris@1658 206 if (column == 2) return SortAlphabetical;
Chris@1658 207 return SortNumeric;
Chris@1658 208 }
Chris@1658 209
Chris@1658 210 QVariant getData(int row, int column, int role) const override {
Chris@1658 211
Chris@1658 212 if (row < 0 || row >= m_events.count()) {
Chris@1658 213 return QVariant();
Chris@425 214 }
Chris@425 215
Chris@1658 216 Event e = m_events.getEventByIndex(row);
Chris@420 217
Chris@420 218 switch (column) {
Chris@1658 219 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1658 220 case 1: return int(e.getFrame());
Chris@1658 221 case 2: return e.getLabel();
Chris@420 222 default: return QVariant();
Chris@420 223 }
Chris@420 224 }
Chris@420 225
Chris@1658 226 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1658 227 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1658 228 if (role != Qt::EditRole) return nullptr;
Chris@1658 229
Chris@1658 230 Event e0 = m_events.getEventByIndex(row);
Chris@1658 231 Event e1;
Chris@1658 232
Chris@1658 233 switch (column) {
Chris@1658 234 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1658 235 getSampleRate()))); break;
Chris@1658 236 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1658 237 case 2: e1 = e0.withLabel(value.toString()); break;
Chris@425 238 }
Chris@425 239
Chris@1742 240 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1658 241 command->remove(e0);
Chris@1658 242 command->add(e1);
Chris@424 243 return command->finish();
Chris@424 244 }
Chris@424 245
Chris@1804 246 bool isEditable() const override { return true; }
Chris@1804 247
Chris@1804 248 Command *getInsertRowCommand(int row) override {
Chris@1804 249 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 250 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 251 tr("Add Point"));
Chris@1804 252 Event e = m_events.getEventByIndex(row);
Chris@1804 253 command->add(e);
Chris@1804 254 return command->finish();
Chris@1804 255 }
Chris@1804 256
Chris@1804 257 Command *getRemoveRowCommand(int row) override {
Chris@1804 258 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 259 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 260 tr("Delete Point"));
Chris@1804 261 Event e = m_events.getEventByIndex(row);
Chris@1804 262 command->remove(e);
Chris@1804 263 return command->finish();
Chris@1804 264 }
Chris@1804 265
Chris@852 266 /**
Chris@852 267 * NoteExportable methods.
Chris@852 268 */
Chris@852 269
Chris@1580 270 NoteList getNotes() const override {
Chris@1643 271 return getNotesStartingWithin(getStartFrame(),
Chris@1643 272 getEndFrame() - getStartFrame());
Chris@852 273 }
Chris@852 274
Chris@1643 275 NoteList getNotesActiveAt(sv_frame_t frame) const override {
Chris@1643 276 return getNotesStartingWithin(frame, 1);
Chris@1643 277 }
Chris@1643 278
Chris@1643 279 NoteList getNotesStartingWithin(sv_frame_t startFrame,
Chris@1643 280 sv_frame_t duration) const override {
Chris@852 281
Chris@852 282 NoteList notes;
Chris@1658 283 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
Chris@1658 284 for (const auto &e: ee) {
Chris@1658 285 notes.push_back(e.toNoteData(getSampleRate(), true));
Chris@852 286 }
Chris@852 287 return notes;
Chris@852 288 }
Chris@1658 289
Chris@1658 290 /**
Chris@1658 291 * XmlExportable methods.
Chris@1658 292 */
Chris@1658 293 void toXml(QTextStream &out,
Chris@1658 294 QString indent = "",
Chris@1658 295 QString extraAttributes = "") const override {
Chris@1658 296
Chris@1658 297 Model::toXml
Chris@1658 298 (out,
Chris@1658 299 indent,
Chris@1658 300 QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" "
Chris@1658 301 "notifyOnAdd=\"%2\" dataset=\"%3\" %4")
Chris@1658 302 .arg(m_resolution)
Chris@1658 303 .arg("true") // always true after model reaches 100% -
Chris@1658 304 // subsequent events are always notified
Chris@1677 305 .arg(m_events.getExportId())
Chris@1658 306 .arg(extraAttributes));
Chris@1658 307
Chris@1658 308 m_events.toXml(out, indent, QString("dimensions=\"1\""));
Chris@1658 309 }
Chris@1679 310
Chris@1833 311 QVector<QString>
Chris@1833 312 getStringExportHeaders(DataExportOptions options) const override {
Chris@1833 313 return m_events.getStringExportHeaders(options, {});
Chris@1815 314 }
Chris@1815 315
Chris@1833 316 QVector<QVector<QString>>
Chris@1833 317 toStringExportRows(DataExportOptions options,
Chris@1833 318 sv_frame_t startFrame,
Chris@1833 319 sv_frame_t duration) const override {
Chris@1833 320 return m_events.toStringExportRows(options,
Chris@1833 321 startFrame,
Chris@1833 322 duration,
Chris@1833 323 m_sampleRate,
Chris@1833 324 m_resolution,
Chris@1833 325 {});
Chris@1679 326 }
Chris@1658 327
Chris@1658 328 protected:
Chris@1658 329 sv_samplerate_t m_sampleRate;
Chris@1658 330 int m_resolution;
Chris@1658 331
Chris@1798 332 std::atomic<bool> m_haveTextLabels;
Chris@1658 333 DeferredNotifier m_notifier;
Chris@1798 334 std::atomic<int> m_completion;
Chris@1658 335
Chris@1658 336 EventSeries m_events;
Chris@147 337 };
Chris@147 338
Chris@147 339 #endif
Chris@147 340
Chris@147 341