annotate data/model/TextModel.h @ 1714:83cb6e9d769b

Attempt to cope with the fact that Windows Server (for CI builds) lacks certain codecs
author Chris Cannam
date Fri, 17 May 2019 11:05:10 +0100
parents cd6be949a16a
children 78fe29adfd16
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@1661 49 notifyOnAdd ?
Chris@1661 50 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1661 51 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1661 52 m_completion(100) {
Chris@147 53 }
Chris@345 54
Chris@1580 55 QString getTypeName() const override { return tr("Text"); }
cannam@1695 56 bool isSparse() const override { return true; }
Chris@1661 57 bool isOK() const override { return true; }
Chris@1661 58
Chris@1661 59 sv_frame_t getStartFrame() const override {
Chris@1661 60 return m_events.getStartFrame();
Chris@1661 61 }
Chris@1661 62 sv_frame_t getEndFrame() const override {
Chris@1661 63 if (m_events.isEmpty()) return 0;
Chris@1661 64 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1661 65 if (e % m_resolution == 0) return e;
Chris@1661 66 else return (e / m_resolution + 1) * m_resolution;
Chris@1661 67 }
Chris@1661 68
Chris@1661 69 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1661 70 int getResolution() const { return m_resolution; }
Chris@1661 71
Chris@1671 72 int getCompletion() const override { return m_completion; }
Chris@1661 73
Chris@1661 74 void setCompletion(int completion, bool update = true) {
Chris@1661 75
Chris@1661 76 { QMutexLocker locker(&m_mutex);
Chris@1661 77 if (m_completion == completion) return;
Chris@1661 78 m_completion = completion;
Chris@1661 79 }
Chris@1661 80
Chris@1661 81 if (update) {
Chris@1661 82 m_notifier.makeDeferredNotifications();
Chris@1661 83 }
Chris@1661 84
Chris@1661 85 emit completionChanged();
Chris@1661 86
Chris@1661 87 if (completion == 100) {
Chris@1661 88 // henceforth:
Chris@1661 89 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1661 90 emit modelChanged();
Chris@1661 91 }
Chris@1661 92 }
Chris@1661 93
Chris@1661 94 /**
Chris@1661 95 * Query methods.
Chris@1661 96 */
Chris@1661 97
Chris@1661 98 int getEventCount() const {
Chris@1661 99 return m_events.count();
Chris@1661 100 }
Chris@1661 101 bool isEmpty() const {
Chris@1661 102 return m_events.isEmpty();
Chris@1661 103 }
Chris@1661 104 bool containsEvent(const Event &e) const {
Chris@1661 105 return m_events.contains(e);
Chris@1661 106 }
Chris@1661 107 EventVector getAllEvents() const {
Chris@1661 108 return m_events.getAllEvents();
Chris@1661 109 }
Chris@1661 110 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1661 111 return m_events.getEventsSpanning(f, duration);
Chris@1661 112 }
Chris@1661 113 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1661 114 return m_events.getEventsCovering(f);
Chris@1661 115 }
Chris@1661 116 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1661 117 int overspill = 0) const {
Chris@1661 118 return m_events.getEventsWithin(f, duration, overspill);
Chris@1661 119 }
Chris@1661 120 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1661 121 return m_events.getEventsStartingWithin(f, duration);
Chris@1661 122 }
Chris@1661 123 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1661 124 return m_events.getEventsStartingAt(f);
Chris@1661 125 }
Chris@1661 126 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1661 127 std::function<bool(Event)> predicate,
Chris@1661 128 EventSeries::Direction direction,
Chris@1661 129 Event &found) const {
Chris@1661 130 return m_events.getNearestEventMatching
Chris@1661 131 (startSearchAt, predicate, direction, found);
Chris@1661 132 }
Chris@1661 133
Chris@1661 134 /**
Chris@1661 135 * Editing methods.
Chris@1661 136 */
Chris@1661 137 void add(Event e) override {
Chris@1661 138
Chris@1661 139 { QMutexLocker locker(&m_mutex);
Chris@1674 140 m_events.add(e.withoutDuration().withoutLevel());
Chris@1661 141 }
Chris@1661 142
Chris@1661 143 m_notifier.update(e.getFrame(), m_resolution);
Chris@1661 144 }
Chris@1661 145
Chris@1661 146 void remove(Event e) override {
Chris@1661 147 { QMutexLocker locker(&m_mutex);
Chris@1661 148 m_events.remove(e);
Chris@1661 149 }
Chris@1661 150 emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution);
Chris@1661 151 }
Chris@424 152
Chris@424 153 /**
Chris@424 154 * TabularModel methods.
Chris@424 155 */
Chris@424 156
Chris@1661 157 int getRowCount() const override {
Chris@1661 158 return m_events.count();
Chris@1661 159 }
Chris@1661 160
Chris@1661 161 int getColumnCount() const override {
Chris@424 162 return 4;
Chris@424 163 }
Chris@424 164
Chris@1661 165 bool isColumnTimeValue(int column) const override {
Chris@1661 166 return (column < 2);
Chris@1661 167 }
Chris@1661 168
Chris@1661 169 sv_frame_t getFrameForRow(int row) const override {
Chris@1661 170 if (row < 0 || row >= m_events.count()) {
Chris@1661 171 return 0;
Chris@1661 172 }
Chris@1661 173 Event e = m_events.getEventByIndex(row);
Chris@1661 174 return e.getFrame();
Chris@1661 175 }
Chris@1661 176
Chris@1661 177 int getRowForFrame(sv_frame_t frame) const override {
Chris@1661 178 return m_events.getIndexForEvent(Event(frame));
Chris@1661 179 }
Chris@1661 180
Chris@1661 181 QString getHeading(int column) const override {
Chris@424 182 switch (column) {
Chris@424 183 case 0: return tr("Time");
Chris@424 184 case 1: return tr("Frame");
Chris@424 185 case 2: return tr("Height");
Chris@424 186 case 3: return tr("Label");
Chris@424 187 default: return tr("Unknown");
Chris@424 188 }
Chris@424 189 }
Chris@424 190
Chris@1661 191 SortType getSortType(int column) const override {
Chris@1661 192 if (column == 3) return SortAlphabetical;
Chris@1661 193 return SortNumeric;
Chris@1661 194 }
Chris@1661 195
Chris@1661 196 QVariant getData(int row, int column, int role) const override {
Chris@1661 197
Chris@1661 198 if (row < 0 || row >= m_events.count()) {
Chris@1661 199 return QVariant();
Chris@425 200 }
Chris@425 201
Chris@1661 202 Event e = m_events.getEventByIndex(row);
Chris@424 203
Chris@424 204 switch (column) {
Chris@1661 205 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1661 206 case 1: return int(e.getFrame());
Chris@1661 207 case 2: return e.getValue();
Chris@1661 208 case 3: return e.getLabel();
Chris@424 209 default: return QVariant();
Chris@424 210 }
Chris@424 211 }
Chris@424 212
Chris@1661 213 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1661 214
Chris@1661 215 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1661 216 if (role != Qt::EditRole) return nullptr;
Chris@1661 217
Chris@1661 218 Event e0 = m_events.getEventByIndex(row);
Chris@1661 219 Event e1;
Chris@1661 220
Chris@1661 221 switch (column) {
Chris@1661 222 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1661 223 getSampleRate()))); break;
Chris@1661 224 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1661 225 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1661 226 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 227 }
Chris@425 228
Chris@1661 229 ChangeEventsCommand *command =
Chris@1661 230 new ChangeEventsCommand(this, tr("Edit Data"));
Chris@1661 231 command->remove(e0);
Chris@1661 232 command->add(e1);
Chris@424 233 return command->finish();
Chris@424 234 }
Chris@1661 235
Chris@1661 236 /**
Chris@1661 237 * XmlExportable methods.
Chris@1661 238 */
Chris@1661 239 void toXml(QTextStream &out,
Chris@1661 240 QString indent = "",
Chris@1661 241 QString extraAttributes = "") const override {
Chris@424 242
Chris@1661 243 Model::toXml
Chris@1661 244 (out,
Chris@1661 245 indent,
Chris@1661 246 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1661 247 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"text\" %4")
Chris@1661 248 .arg(m_resolution)
Chris@1661 249 .arg("true") // always true after model reaches 100% -
Chris@1661 250 // subsequent events are always notified
Chris@1677 251 .arg(m_events.getExportId())
Chris@1661 252 .arg(extraAttributes));
Chris@1674 253
Chris@1674 254 Event::ExportNameOptions options;
Chris@1674 255 options.valueAtttributeName = "height";
Chris@1661 256
Chris@1674 257 m_events.toXml(out, indent, QString("dimensions=\"2\""), options);
Chris@424 258 }
Chris@1679 259
Chris@1679 260 QString toDelimitedDataString(QString delimiter,
Chris@1679 261 DataExportOptions options,
Chris@1679 262 sv_frame_t startFrame,
Chris@1679 263 sv_frame_t duration) const override {
Chris@1679 264 return m_events.toDelimitedDataString(delimiter,
Chris@1679 265 options,
Chris@1679 266 startFrame,
Chris@1679 267 duration,
Chris@1679 268 m_sampleRate,
Chris@1679 269 m_resolution,
Chris@1679 270 Event().withValue(0.f));
Chris@1679 271 }
Chris@1661 272
Chris@1661 273 protected:
Chris@1661 274 sv_samplerate_t m_sampleRate;
Chris@1661 275 int m_resolution;
Chris@424 276
Chris@1661 277 DeferredNotifier m_notifier;
Chris@1661 278 int m_completion;
Chris@1661 279
Chris@1661 280 EventSeries m_events;
Chris@1661 281
Chris@1661 282 mutable QMutex m_mutex;
Chris@424 283
Chris@147 284 };
Chris@147 285
Chris@147 286
Chris@147 287 #endif
Chris@147 288
Chris@147 289
Chris@147 290