annotate data/model/TextModel.h @ 1873:1d44fdc8196c csv-import-headers

Extend tests to include testing (at least some of) the actual data as well as the layout
author Chris Cannam
date Thu, 18 Jun 2020 13:42:48 +0100
parents 21c792334c2e
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_TEXT_MODEL_H
Chris@1581 16 #define SV_TEXT_MODEL_H
Chris@147 17
Chris@1661 18 #include "EventCommands.h"
Chris@1661 19 #include "TabularModel.h"
Chris@1661 20 #include "Model.h"
Chris@1661 21 #include "DeferredNotifier.h"
Chris@1661 22
Chris@1661 23 #include "base/EventSeries.h"
Chris@302 24 #include "base/XmlExportable.h"
Chris@147 25 #include "base/RealTime.h"
Chris@147 26
Chris@1661 27 #include "system/System.h"
Chris@1661 28
Chris@423 29 #include <QStringList>
Chris@423 30
Chris@147 31 /**
Chris@1661 32 * A model representing casual textual annotations. A piece of text
Chris@1661 33 * has a given time and y-value in the [0,1) range (indicative of
Chris@1661 34 * height on the window).
Chris@147 35 */
Chris@1661 36 class TextModel : public Model,
Chris@1661 37 public TabularModel,
Chris@1661 38 public EventEditable
Chris@147 39 {
Chris@423 40 Q_OBJECT
Chris@423 41
Chris@147 42 public:
Chris@1661 43 TextModel(sv_samplerate_t sampleRate,
Chris@1661 44 int resolution,
Chris@1661 45 bool notifyOnAdd = true) :
Chris@1661 46 m_sampleRate(sampleRate),
Chris@1661 47 m_resolution(resolution),
Chris@1661 48 m_notifier(this,
Chris@1752 49 getId(),
Chris@1661 50 notifyOnAdd ?
Chris@1661 51 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1661 52 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1661 53 m_completion(100) {
Chris@147 54 }
Chris@345 55
Chris@1580 56 QString getTypeName() const override { return tr("Text"); }
cannam@1695 57 bool isSparse() const override { return true; }
Chris@1661 58 bool isOK() const override { return true; }
Chris@1661 59
Chris@1661 60 sv_frame_t getStartFrame() const override {
Chris@1661 61 return m_events.getStartFrame();
Chris@1661 62 }
Chris@1725 63 sv_frame_t getTrueEndFrame() const override {
Chris@1661 64 if (m_events.isEmpty()) return 0;
Chris@1661 65 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1661 66 if (e % m_resolution == 0) return e;
Chris@1661 67 else return (e / m_resolution + 1) * m_resolution;
Chris@1661 68 }
Chris@1661 69
Chris@1661 70 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1661 71 int getResolution() const { return m_resolution; }
Chris@1661 72
Chris@1671 73 int getCompletion() const override { return m_completion; }
Chris@1661 74
Chris@1661 75 void setCompletion(int completion, bool update = true) {
Chris@1661 76
Chris@1661 77 { QMutexLocker locker(&m_mutex);
Chris@1661 78 if (m_completion == completion) return;
Chris@1661 79 m_completion = completion;
Chris@1661 80 }
Chris@1661 81
Chris@1661 82 if (update) {
Chris@1661 83 m_notifier.makeDeferredNotifications();
Chris@1661 84 }
Chris@1661 85
Chris@1752 86 emit completionChanged(getId());
Chris@1661 87
Chris@1661 88 if (completion == 100) {
Chris@1661 89 // henceforth:
Chris@1661 90 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 91 emit modelChanged(getId());
Chris@1661 92 }
Chris@1661 93 }
Chris@1661 94
Chris@1661 95 /**
Chris@1661 96 * Query methods.
Chris@1661 97 */
Chris@1661 98
Chris@1661 99 int getEventCount() const {
Chris@1661 100 return m_events.count();
Chris@1661 101 }
Chris@1661 102 bool isEmpty() const {
Chris@1661 103 return m_events.isEmpty();
Chris@1661 104 }
Chris@1661 105 bool containsEvent(const Event &e) const {
Chris@1661 106 return m_events.contains(e);
Chris@1661 107 }
Chris@1661 108 EventVector getAllEvents() const {
Chris@1661 109 return m_events.getAllEvents();
Chris@1661 110 }
Chris@1661 111 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1661 112 return m_events.getEventsSpanning(f, duration);
Chris@1661 113 }
Chris@1661 114 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1661 115 return m_events.getEventsCovering(f);
Chris@1661 116 }
Chris@1661 117 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1661 118 int overspill = 0) const {
Chris@1661 119 return m_events.getEventsWithin(f, duration, overspill);
Chris@1661 120 }
Chris@1661 121 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1661 122 return m_events.getEventsStartingWithin(f, duration);
Chris@1661 123 }
Chris@1661 124 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1661 125 return m_events.getEventsStartingAt(f);
Chris@1661 126 }
Chris@1661 127 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1661 128 std::function<bool(Event)> predicate,
Chris@1661 129 EventSeries::Direction direction,
Chris@1661 130 Event &found) const {
Chris@1661 131 return m_events.getNearestEventMatching
Chris@1661 132 (startSearchAt, predicate, direction, found);
Chris@1661 133 }
Chris@1661 134
Chris@1661 135 /**
Chris@1661 136 * Editing methods.
Chris@1661 137 */
Chris@1661 138 void add(Event e) override {
Chris@1661 139
Chris@1661 140 { QMutexLocker locker(&m_mutex);
Chris@1674 141 m_events.add(e.withoutDuration().withoutLevel());
Chris@1661 142 }
Chris@1661 143
Chris@1661 144 m_notifier.update(e.getFrame(), m_resolution);
Chris@1661 145 }
Chris@1661 146
Chris@1661 147 void remove(Event e) override {
Chris@1661 148 { QMutexLocker locker(&m_mutex);
Chris@1661 149 m_events.remove(e);
Chris@1661 150 }
Chris@1752 151 emit modelChangedWithin(getId(),
Chris@1752 152 e.getFrame(), e.getFrame() + m_resolution);
Chris@1661 153 }
Chris@424 154
Chris@424 155 /**
Chris@424 156 * TabularModel methods.
Chris@424 157 */
Chris@424 158
Chris@1661 159 int getRowCount() const override {
Chris@1661 160 return m_events.count();
Chris@1661 161 }
Chris@1661 162
Chris@1661 163 int getColumnCount() const override {
Chris@424 164 return 4;
Chris@424 165 }
Chris@424 166
Chris@1661 167 bool isColumnTimeValue(int column) const override {
Chris@1661 168 return (column < 2);
Chris@1661 169 }
Chris@1661 170
Chris@1661 171 sv_frame_t getFrameForRow(int row) const override {
Chris@1661 172 if (row < 0 || row >= m_events.count()) {
Chris@1661 173 return 0;
Chris@1661 174 }
Chris@1661 175 Event e = m_events.getEventByIndex(row);
Chris@1661 176 return e.getFrame();
Chris@1661 177 }
Chris@1661 178
Chris@1661 179 int getRowForFrame(sv_frame_t frame) const override {
Chris@1661 180 return m_events.getIndexForEvent(Event(frame));
Chris@1661 181 }
Chris@1661 182
Chris@1661 183 QString getHeading(int column) const override {
Chris@424 184 switch (column) {
Chris@424 185 case 0: return tr("Time");
Chris@424 186 case 1: return tr("Frame");
Chris@424 187 case 2: return tr("Height");
Chris@424 188 case 3: return tr("Label");
Chris@424 189 default: return tr("Unknown");
Chris@424 190 }
Chris@424 191 }
Chris@424 192
Chris@1661 193 SortType getSortType(int column) const override {
Chris@1661 194 if (column == 3) return SortAlphabetical;
Chris@1661 195 return SortNumeric;
Chris@1661 196 }
Chris@1661 197
Chris@1661 198 QVariant getData(int row, int column, int role) const override {
Chris@1661 199
Chris@1661 200 if (row < 0 || row >= m_events.count()) {
Chris@1661 201 return QVariant();
Chris@425 202 }
Chris@425 203
Chris@1661 204 Event e = m_events.getEventByIndex(row);
Chris@424 205
Chris@424 206 switch (column) {
Chris@1661 207 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1661 208 case 1: return int(e.getFrame());
Chris@1661 209 case 2: return e.getValue();
Chris@1661 210 case 3: return e.getLabel();
Chris@424 211 default: return QVariant();
Chris@424 212 }
Chris@424 213 }
Chris@424 214
Chris@1661 215 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1661 216
Chris@1661 217 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1661 218 if (role != Qt::EditRole) return nullptr;
Chris@1661 219
Chris@1661 220 Event e0 = m_events.getEventByIndex(row);
Chris@1661 221 Event e1;
Chris@1661 222
Chris@1661 223 switch (column) {
Chris@1661 224 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1661 225 getSampleRate()))); break;
Chris@1661 226 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1661 227 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1661 228 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 229 }
Chris@425 230
Chris@1742 231 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1661 232 command->remove(e0);
Chris@1661 233 command->add(e1);
Chris@424 234 return command->finish();
Chris@424 235 }
Chris@1804 236
Chris@1804 237 bool isEditable() const override { return true; }
Chris@1804 238
Chris@1804 239 Command *getInsertRowCommand(int row) override {
Chris@1804 240 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 241 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 242 tr("Add Label"));
Chris@1804 243 Event e = m_events.getEventByIndex(row);
Chris@1804 244 command->add(e);
Chris@1804 245 return command->finish();
Chris@1804 246 }
Chris@1804 247
Chris@1804 248 Command *getRemoveRowCommand(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("Delete Label"));
Chris@1804 252 Event e = m_events.getEventByIndex(row);
Chris@1804 253 command->remove(e);
Chris@1804 254 return command->finish();
Chris@1804 255 }
Chris@1661 256
Chris@1661 257 /**
Chris@1661 258 * XmlExportable methods.
Chris@1661 259 */
Chris@1661 260 void toXml(QTextStream &out,
Chris@1661 261 QString indent = "",
Chris@1661 262 QString extraAttributes = "") const override {
Chris@424 263
Chris@1661 264 Model::toXml
Chris@1661 265 (out,
Chris@1661 266 indent,
Chris@1661 267 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1661 268 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"text\" %4")
Chris@1661 269 .arg(m_resolution)
Chris@1661 270 .arg("true") // always true after model reaches 100% -
Chris@1661 271 // subsequent events are always notified
Chris@1677 272 .arg(m_events.getExportId())
Chris@1661 273 .arg(extraAttributes));
Chris@1674 274
Chris@1674 275 Event::ExportNameOptions options;
Chris@1789 276 options.valueAttributeName = "height";
Chris@1661 277
Chris@1674 278 m_events.toXml(out, indent, QString("dimensions=\"2\""), options);
Chris@424 279 }
Chris@1679 280
Chris@1833 281 QVector<QString>
Chris@1833 282 getStringExportHeaders(DataExportOptions options) const override {
Chris@1815 283 Event::ExportNameOptions nameOpts;
Chris@1815 284 nameOpts.valueAttributeName = "height";
Chris@1833 285 return m_events.getStringExportHeaders(options, nameOpts);
Chris@1815 286 }
Chris@1815 287
Chris@1833 288 QVector<QVector<QString>>
Chris@1833 289 toStringExportRows(DataExportOptions options,
Chris@1833 290 sv_frame_t startFrame,
Chris@1833 291 sv_frame_t duration) const override {
Chris@1833 292 return m_events.toStringExportRows(options,
Chris@1833 293 startFrame,
Chris@1833 294 duration,
Chris@1833 295 m_sampleRate,
Chris@1833 296 m_resolution,
Chris@1833 297 Event().withValue(0.f));
Chris@1679 298 }
Chris@1661 299
Chris@1661 300 protected:
Chris@1661 301 sv_samplerate_t m_sampleRate;
Chris@1661 302 int m_resolution;
Chris@424 303
Chris@1661 304 DeferredNotifier m_notifier;
Chris@1661 305 int m_completion;
Chris@1661 306
Chris@1661 307 EventSeries m_events;
Chris@1661 308
Chris@1661 309 mutable QMutex m_mutex;
Chris@424 310
Chris@147 311 };
Chris@147 312
Chris@147 313
Chris@147 314 #endif
Chris@147 315
Chris@147 316
Chris@147 317