annotate data/model/TextModel.h @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +0100
parents 52705a328b34
children baafe1bb7e51
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@1661 236
Chris@1661 237 /**
Chris@1661 238 * XmlExportable methods.
Chris@1661 239 */
Chris@1661 240 void toXml(QTextStream &out,
Chris@1661 241 QString indent = "",
Chris@1661 242 QString extraAttributes = "") const override {
Chris@424 243
Chris@1661 244 Model::toXml
Chris@1661 245 (out,
Chris@1661 246 indent,
Chris@1661 247 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1661 248 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"text\" %4")
Chris@1661 249 .arg(m_resolution)
Chris@1661 250 .arg("true") // always true after model reaches 100% -
Chris@1661 251 // subsequent events are always notified
Chris@1677 252 .arg(m_events.getExportId())
Chris@1661 253 .arg(extraAttributes));
Chris@1674 254
Chris@1674 255 Event::ExportNameOptions options;
Chris@1674 256 options.valueAtttributeName = "height";
Chris@1661 257
Chris@1674 258 m_events.toXml(out, indent, QString("dimensions=\"2\""), options);
Chris@424 259 }
Chris@1679 260
Chris@1679 261 QString toDelimitedDataString(QString delimiter,
Chris@1679 262 DataExportOptions options,
Chris@1679 263 sv_frame_t startFrame,
Chris@1679 264 sv_frame_t duration) const override {
Chris@1679 265 return m_events.toDelimitedDataString(delimiter,
Chris@1679 266 options,
Chris@1679 267 startFrame,
Chris@1679 268 duration,
Chris@1679 269 m_sampleRate,
Chris@1679 270 m_resolution,
Chris@1679 271 Event().withValue(0.f));
Chris@1679 272 }
Chris@1661 273
Chris@1661 274 protected:
Chris@1661 275 sv_samplerate_t m_sampleRate;
Chris@1661 276 int m_resolution;
Chris@424 277
Chris@1661 278 DeferredNotifier m_notifier;
Chris@1661 279 int m_completion;
Chris@1661 280
Chris@1661 281 EventSeries m_events;
Chris@1661 282
Chris@1661 283 mutable QMutex m_mutex;
Chris@424 284
Chris@147 285 };
Chris@147 286
Chris@147 287
Chris@147 288 #endif
Chris@147 289
Chris@147 290
Chris@147 291