annotate data/model/TextModel.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_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