annotate data/model/ImageModel.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 13bd41bd8a17
rev   line source
Chris@302 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@302 2
Chris@302 3 /*
Chris@302 4 Sonic Visualiser
Chris@302 5 An audio file viewer and annotation editor.
Chris@302 6 Centre for Digital Music, Queen Mary, University of London.
Chris@302 7 This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@302 8
Chris@302 9 This program is free software; you can redistribute it and/or
Chris@302 10 modify it under the terms of the GNU General Public License as
Chris@302 11 published by the Free Software Foundation; either version 2 of the
Chris@302 12 License, or (at your option) any later version. See the file
Chris@302 13 COPYING included with this distribution for more information.
Chris@302 14 */
Chris@302 15
Chris@1581 16 #ifndef SV_IMAGE_MODEL_H
Chris@1581 17 #define SV_IMAGE_MODEL_H
Chris@302 18
Chris@1663 19 #include "EventCommands.h"
Chris@1663 20 #include "TabularModel.h"
Chris@1663 21 #include "Model.h"
Chris@1663 22 #include "DeferredNotifier.h"
Chris@1663 23
Chris@1663 24 #include "base/EventSeries.h"
Chris@302 25 #include "base/XmlExportable.h"
Chris@302 26 #include "base/RealTime.h"
Chris@302 27
Chris@1663 28 #include "system/System.h"
Chris@1663 29
Chris@423 30 #include <QStringList>
Chris@423 31
Chris@302 32 /**
Chris@1663 33 * A model representing image annotations, identified by filename or
Chris@1663 34 * URI, at a given time, with an optional label. The filename can be
Chris@1663 35 * empty, in which case we instead have a space to put an image in.
Chris@302 36 */
Chris@302 37
Chris@1663 38 class ImageModel : public Model,
Chris@1663 39 public TabularModel,
Chris@1663 40 public EventEditable
Chris@302 41 {
Chris@423 42 Q_OBJECT
Chris@423 43
Chris@302 44 public:
Chris@1663 45 ImageModel(sv_samplerate_t sampleRate,
Chris@1663 46 int resolution,
Chris@1663 47 bool notifyOnAdd = true) :
Chris@1663 48 m_sampleRate(sampleRate),
Chris@1663 49 m_resolution(resolution),
Chris@1663 50 m_notifier(this,
Chris@1752 51 getId(),
Chris@1663 52 notifyOnAdd ?
Chris@1663 53 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1663 54 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1663 55 m_completion(100) {
Chris@1663 56 }
Chris@302 57
Chris@1580 58 QString getTypeName() const override { return tr("Image"); }
Chris@1692 59 bool isSparse() const override { return true; }
Chris@1663 60 bool isOK() const override { return true; }
Chris@345 61
Chris@1663 62 sv_frame_t getStartFrame() const override {
Chris@1663 63 return m_events.getStartFrame();
Chris@1663 64 }
Chris@1725 65 sv_frame_t getTrueEndFrame() const override {
Chris@1663 66 if (m_events.isEmpty()) return 0;
Chris@1663 67 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1663 68 if (e % m_resolution == 0) return e;
Chris@1663 69 else return (e / m_resolution + 1) * m_resolution;
Chris@1663 70 }
Chris@1663 71
Chris@1663 72 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1663 73 int getResolution() const { return m_resolution; }
Chris@1663 74
Chris@1692 75 int getCompletion() const override { return m_completion; }
Chris@1663 76
Chris@1663 77 void setCompletion(int completion, bool update = true) {
Chris@1663 78
Chris@1663 79 { QMutexLocker locker(&m_mutex);
Chris@1663 80 if (m_completion == completion) return;
Chris@1663 81 m_completion = completion;
Chris@1663 82 }
Chris@1663 83
Chris@1663 84 if (update) {
Chris@1663 85 m_notifier.makeDeferredNotifications();
Chris@1663 86 }
Chris@1663 87
Chris@1752 88 emit completionChanged(getId());
Chris@1663 89
Chris@1663 90 if (completion == 100) {
Chris@1663 91 // henceforth:
Chris@1663 92 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 93 emit modelChanged(getId());
Chris@1663 94 }
Chris@1663 95 }
Chris@1663 96
Chris@1663 97 /**
Chris@1663 98 * Query methods.
Chris@1663 99 */
Chris@1663 100
Chris@1663 101 //!!! todo: go through all models, weeding out the methods here
Chris@1663 102 //!!! that are not used
Chris@1663 103
Chris@1663 104 int getEventCount() const {
Chris@1663 105 return m_events.count();
Chris@1663 106 }
Chris@1663 107 bool isEmpty() const {
Chris@1663 108 return m_events.isEmpty();
Chris@1663 109 }
Chris@1663 110 bool containsEvent(const Event &e) const {
Chris@1663 111 return m_events.contains(e);
Chris@1663 112 }
Chris@1663 113 EventVector getAllEvents() const {
Chris@1663 114 return m_events.getAllEvents();
Chris@1663 115 }
Chris@1663 116 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1663 117 return m_events.getEventsSpanning(f, duration);
Chris@1663 118 }
Chris@1663 119 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1663 120 return m_events.getEventsCovering(f);
Chris@1663 121 }
Chris@1663 122 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1663 123 int overspill = 0) const {
Chris@1663 124 return m_events.getEventsWithin(f, duration, overspill);
Chris@1663 125 }
Chris@1663 126 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1663 127 return m_events.getEventsStartingWithin(f, duration);
Chris@1663 128 }
Chris@1663 129 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1663 130 return m_events.getEventsStartingAt(f);
Chris@1663 131 }
Chris@1663 132 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1663 133 std::function<bool(Event)> predicate,
Chris@1663 134 EventSeries::Direction direction,
Chris@1663 135 Event &found) const {
Chris@1663 136 return m_events.getNearestEventMatching
Chris@1663 137 (startSearchAt, predicate, direction, found);
Chris@302 138 }
Chris@302 139
Chris@302 140 /**
Chris@1663 141 * Editing methods.
Chris@302 142 */
Chris@1663 143 void add(Event e) override {
Chris@1663 144
Chris@1663 145 { QMutexLocker locker(&m_mutex);
Chris@1674 146 m_events.add(e.withoutDuration().withoutValue().withoutLevel());
Chris@1429 147 }
Chris@1663 148
Chris@1663 149 m_notifier.update(e.getFrame(), m_resolution);
Chris@1663 150 }
Chris@1663 151
Chris@1663 152 void remove(Event e) override {
Chris@1663 153 { QMutexLocker locker(&m_mutex);
Chris@1663 154 m_events.remove(e);
Chris@1429 155 }
Chris@1752 156 emit modelChangedWithin(getId(),
Chris@1752 157 e.getFrame(), e.getFrame() + m_resolution);
Chris@1663 158 }
Chris@424 159
Chris@424 160 /**
Chris@424 161 * TabularModel methods.
Chris@424 162 */
Chris@424 163
Chris@1663 164 int getRowCount() const override {
Chris@1663 165 return m_events.count();
Chris@1663 166 }
Chris@1663 167
Chris@1663 168 int getColumnCount() const override {
Chris@424 169 return 4;
Chris@424 170 }
Chris@424 171
Chris@1663 172 bool isColumnTimeValue(int column) const override {
Chris@1663 173 return (column < 2);
Chris@1663 174 }
Chris@1663 175
Chris@1663 176 sv_frame_t getFrameForRow(int row) const override {
Chris@1663 177 if (row < 0 || row >= m_events.count()) {
Chris@1663 178 return 0;
Chris@1663 179 }
Chris@1663 180 Event e = m_events.getEventByIndex(row);
Chris@1663 181 return e.getFrame();
Chris@1663 182 }
Chris@1663 183
Chris@1663 184 int getRowForFrame(sv_frame_t frame) const override {
Chris@1663 185 return m_events.getIndexForEvent(Event(frame));
Chris@1663 186 }
Chris@1663 187
Chris@1663 188 QString getHeading(int column) const override {
Chris@424 189 switch (column) {
Chris@424 190 case 0: return tr("Time");
Chris@424 191 case 1: return tr("Frame");
Chris@424 192 case 2: return tr("Image");
Chris@424 193 case 3: return tr("Label");
Chris@424 194 default: return tr("Unknown");
Chris@424 195 }
Chris@424 196 }
Chris@424 197
Chris@1663 198 SortType getSortType(int column) const override {
Chris@1663 199 if (column >= 2) return SortAlphabetical;
Chris@1663 200 return SortNumeric;
Chris@1663 201 }
Chris@1663 202
Chris@1663 203 QVariant getData(int row, int column, int role) const override {
Chris@1663 204
Chris@1663 205 if (row < 0 || row >= m_events.count()) {
Chris@1663 206 return QVariant();
Chris@425 207 }
Chris@425 208
Chris@1663 209 Event e = m_events.getEventByIndex(row);
Chris@424 210
Chris@424 211 switch (column) {
Chris@1663 212 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1663 213 case 1: return int(e.getFrame());
Chris@1663 214 case 2: return e.getURI();
Chris@1663 215 case 3: return e.getLabel();
Chris@424 216 default: return QVariant();
Chris@424 217 }
Chris@424 218 }
Chris@424 219
Chris@1663 220 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1663 221
Chris@1663 222 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1663 223 if (role != Qt::EditRole) return nullptr;
Chris@1663 224
Chris@1663 225 Event e0 = m_events.getEventByIndex(row);
Chris@1663 226 Event e1;
Chris@1663 227
Chris@1663 228 switch (column) {
Chris@1663 229 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1663 230 getSampleRate()))); break;
Chris@1663 231 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1663 232 case 2: e1 = e0.withURI(value.toString()); break;
Chris@1663 233 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 234 }
Chris@425 235
Chris@1742 236 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1663 237 command->remove(e0);
Chris@1663 238 command->add(e1);
Chris@424 239 return command->finish();
Chris@424 240 }
Chris@1663 241
Chris@1663 242 /**
Chris@1663 243 * XmlExportable methods.
Chris@1663 244 */
Chris@1663 245 void toXml(QTextStream &out,
Chris@1663 246 QString indent = "",
Chris@1663 247 QString extraAttributes = "") const override {
Chris@424 248
Chris@1663 249 Model::toXml
Chris@1663 250 (out,
Chris@1663 251 indent,
Chris@1663 252 QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" "
Chris@1663 253 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"image\" %4")
Chris@1663 254 .arg(m_resolution)
Chris@1663 255 .arg("true") // always true after model reaches 100% -
Chris@1663 256 // subsequent events are always notified
Chris@1677 257 .arg(m_events.getExportId())
Chris@1663 258 .arg(extraAttributes));
Chris@1674 259
Chris@1674 260 Event::ExportNameOptions options;
Chris@1674 261 options.uriAttributeName = "image";
Chris@1663 262
Chris@1674 263 m_events.toXml(out, indent, QString("dimensions=\"1\""), options);
Chris@424 264 }
Chris@1679 265
Chris@1679 266 QString toDelimitedDataString(QString delimiter,
Chris@1679 267 DataExportOptions options,
Chris@1679 268 sv_frame_t startFrame,
Chris@1679 269 sv_frame_t duration) const override {
Chris@1679 270 return m_events.toDelimitedDataString(delimiter,
Chris@1679 271 options,
Chris@1679 272 startFrame,
Chris@1679 273 duration,
Chris@1679 274 m_sampleRate,
Chris@1679 275 m_resolution,
Chris@1679 276 Event().withValue(0.f));
Chris@1679 277 }
Chris@1679 278
Chris@1663 279 protected:
Chris@1663 280 sv_samplerate_t m_sampleRate;
Chris@1663 281 int m_resolution;
Chris@424 282
Chris@1663 283 DeferredNotifier m_notifier;
Chris@1663 284 int m_completion;
Chris@1663 285
Chris@1663 286 EventSeries m_events;
Chris@1663 287
Chris@1663 288 mutable QMutex m_mutex;
Chris@302 289 };
Chris@302 290
Chris@302 291
Chris@302 292 #endif
Chris@302 293