Chris@302: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@302: Chris@302: /* Chris@302: Sonic Visualiser Chris@302: An audio file viewer and annotation editor. Chris@302: Centre for Digital Music, Queen Mary, University of London. Chris@302: This file copyright 2006-2007 Chris Cannam and QMUL. Chris@302: Chris@302: This program is free software; you can redistribute it and/or Chris@302: modify it under the terms of the GNU General Public License as Chris@302: published by the Free Software Foundation; either version 2 of the Chris@302: License, or (at your option) any later version. See the file Chris@302: COPYING included with this distribution for more information. Chris@302: */ Chris@302: Chris@1581: #ifndef SV_IMAGE_MODEL_H Chris@1581: #define SV_IMAGE_MODEL_H Chris@302: Chris@1663: #include "EventCommands.h" Chris@1663: #include "TabularModel.h" Chris@1663: #include "Model.h" Chris@1663: #include "DeferredNotifier.h" Chris@1663: Chris@1663: #include "base/EventSeries.h" Chris@302: #include "base/XmlExportable.h" Chris@302: #include "base/RealTime.h" Chris@302: Chris@1663: #include "system/System.h" Chris@1663: Chris@423: #include Chris@423: Chris@302: /** Chris@1663: * A model representing image annotations, identified by filename or Chris@1663: * URI, at a given time, with an optional label. The filename can be Chris@1663: * empty, in which case we instead have a space to put an image in. Chris@302: */ Chris@302: Chris@1663: class ImageModel : public Model, Chris@1663: public TabularModel, Chris@1663: public EventEditable Chris@302: { Chris@423: Q_OBJECT Chris@423: Chris@302: public: Chris@1663: ImageModel(sv_samplerate_t sampleRate, Chris@1663: int resolution, Chris@1663: bool notifyOnAdd = true) : Chris@1663: m_sampleRate(sampleRate), Chris@1663: m_resolution(resolution), Chris@1663: m_notifier(this, Chris@1663: notifyOnAdd ? Chris@1663: DeferredNotifier::NOTIFY_ALWAYS : Chris@1663: DeferredNotifier::NOTIFY_DEFERRED), Chris@1663: m_completion(100) { Chris@1663: } Chris@302: Chris@1580: QString getTypeName() const override { return tr("Image"); } Chris@1663: bool isSparse() const { return true; } Chris@1663: bool isOK() const override { return true; } Chris@345: Chris@1663: sv_frame_t getStartFrame() const override { Chris@1663: return m_events.getStartFrame(); Chris@1663: } Chris@1663: sv_frame_t getEndFrame() const override { Chris@1663: if (m_events.isEmpty()) return 0; Chris@1663: sv_frame_t e = m_events.getEndFrame() + 1; Chris@1663: if (e % m_resolution == 0) return e; Chris@1663: else return (e / m_resolution + 1) * m_resolution; Chris@1663: } Chris@1663: Chris@1663: sv_samplerate_t getSampleRate() const override { return m_sampleRate; } Chris@1663: int getResolution() const { return m_resolution; } Chris@1663: Chris@1663: int getCompletion() const { return m_completion; } Chris@1663: Chris@1663: void setCompletion(int completion, bool update = true) { Chris@1663: Chris@1663: { QMutexLocker locker(&m_mutex); Chris@1663: if (m_completion == completion) return; Chris@1663: m_completion = completion; Chris@1663: } Chris@1663: Chris@1663: if (update) { Chris@1663: m_notifier.makeDeferredNotifications(); Chris@1663: } Chris@1663: Chris@1663: emit completionChanged(); Chris@1663: Chris@1663: if (completion == 100) { Chris@1663: // henceforth: Chris@1663: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); Chris@1663: emit modelChanged(); Chris@1663: } Chris@1663: } Chris@1663: Chris@1663: /** Chris@1663: * Query methods. Chris@1663: */ Chris@1663: Chris@1663: //!!! todo: go through all models, weeding out the methods here Chris@1663: //!!! that are not used Chris@1663: Chris@1663: int getEventCount() const { Chris@1663: return m_events.count(); Chris@1663: } Chris@1663: bool isEmpty() const { Chris@1663: return m_events.isEmpty(); Chris@1663: } Chris@1663: bool containsEvent(const Event &e) const { Chris@1663: return m_events.contains(e); Chris@1663: } Chris@1663: EventVector getAllEvents() const { Chris@1663: return m_events.getAllEvents(); Chris@1663: } Chris@1663: EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { Chris@1663: return m_events.getEventsSpanning(f, duration); Chris@1663: } Chris@1663: EventVector getEventsCovering(sv_frame_t f) const { Chris@1663: return m_events.getEventsCovering(f); Chris@1663: } Chris@1663: EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration, Chris@1663: int overspill = 0) const { Chris@1663: return m_events.getEventsWithin(f, duration, overspill); Chris@1663: } Chris@1663: EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { Chris@1663: return m_events.getEventsStartingWithin(f, duration); Chris@1663: } Chris@1663: EventVector getEventsStartingAt(sv_frame_t f) const { Chris@1663: return m_events.getEventsStartingAt(f); Chris@1663: } Chris@1663: bool getNearestEventMatching(sv_frame_t startSearchAt, Chris@1663: std::function predicate, Chris@1663: EventSeries::Direction direction, Chris@1663: Event &found) const { Chris@1663: return m_events.getNearestEventMatching Chris@1663: (startSearchAt, predicate, direction, found); Chris@302: } Chris@302: Chris@302: /** Chris@1663: * Editing methods. Chris@302: */ Chris@1663: void add(Event e) override { Chris@1663: Chris@1663: { QMutexLocker locker(&m_mutex); Chris@1674: m_events.add(e.withoutDuration().withoutValue().withoutLevel()); Chris@1429: } Chris@1663: Chris@1663: m_notifier.update(e.getFrame(), m_resolution); Chris@1663: } Chris@1663: Chris@1663: void remove(Event e) override { Chris@1663: { QMutexLocker locker(&m_mutex); Chris@1663: m_events.remove(e); Chris@1429: } Chris@1663: emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); Chris@1663: } Chris@424: Chris@424: /** Chris@424: * TabularModel methods. Chris@424: */ Chris@424: Chris@1663: int getRowCount() const override { Chris@1663: return m_events.count(); Chris@1663: } Chris@1663: Chris@1663: int getColumnCount() const override { Chris@424: return 4; Chris@424: } Chris@424: Chris@1663: bool isColumnTimeValue(int column) const override { Chris@1663: return (column < 2); Chris@1663: } Chris@1663: Chris@1663: sv_frame_t getFrameForRow(int row) const override { Chris@1663: if (row < 0 || row >= m_events.count()) { Chris@1663: return 0; Chris@1663: } Chris@1663: Event e = m_events.getEventByIndex(row); Chris@1663: return e.getFrame(); Chris@1663: } Chris@1663: Chris@1663: int getRowForFrame(sv_frame_t frame) const override { Chris@1663: return m_events.getIndexForEvent(Event(frame)); Chris@1663: } Chris@1663: Chris@1663: QString getHeading(int column) const override { Chris@424: switch (column) { Chris@424: case 0: return tr("Time"); Chris@424: case 1: return tr("Frame"); Chris@424: case 2: return tr("Image"); Chris@424: case 3: return tr("Label"); Chris@424: default: return tr("Unknown"); Chris@424: } Chris@424: } Chris@424: Chris@1663: SortType getSortType(int column) const override { Chris@1663: if (column >= 2) return SortAlphabetical; Chris@1663: return SortNumeric; Chris@1663: } Chris@1663: Chris@1663: QVariant getData(int row, int column, int role) const override { Chris@1663: Chris@1663: if (row < 0 || row >= m_events.count()) { Chris@1663: return QVariant(); Chris@425: } Chris@425: Chris@1663: Event e = m_events.getEventByIndex(row); Chris@424: Chris@424: switch (column) { Chris@1663: case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); Chris@1663: case 1: return int(e.getFrame()); Chris@1663: case 2: return e.getURI(); Chris@1663: case 3: return e.getLabel(); Chris@424: default: return QVariant(); Chris@424: } Chris@424: } Chris@424: Chris@1663: Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { Chris@1663: Chris@1663: if (row < 0 || row >= m_events.count()) return nullptr; Chris@1663: if (role != Qt::EditRole) return nullptr; Chris@1663: Chris@1663: Event e0 = m_events.getEventByIndex(row); Chris@1663: Event e1; Chris@1663: Chris@1663: switch (column) { Chris@1663: case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * Chris@1663: getSampleRate()))); break; Chris@1663: case 1: e1 = e0.withFrame(value.toInt()); break; Chris@1663: case 2: e1 = e0.withURI(value.toString()); break; Chris@1663: case 3: e1 = e0.withLabel(value.toString()); break; Chris@425: } Chris@425: Chris@1663: ChangeEventsCommand *command = Chris@1663: new ChangeEventsCommand(this, tr("Edit Data")); Chris@1663: command->remove(e0); Chris@1663: command->add(e1); Chris@424: return command->finish(); Chris@424: } Chris@1663: Chris@1663: /** Chris@1663: * XmlExportable methods. Chris@1663: */ Chris@1663: void toXml(QTextStream &out, Chris@1663: QString indent = "", Chris@1663: QString extraAttributes = "") const override { Chris@424: Chris@1663: Model::toXml Chris@1663: (out, Chris@1663: indent, Chris@1663: QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" " Chris@1663: "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"image\" %4") Chris@1663: .arg(m_resolution) Chris@1663: .arg("true") // always true after model reaches 100% - Chris@1663: // subsequent events are always notified Chris@1677: .arg(m_events.getExportId()) Chris@1663: .arg(extraAttributes)); Chris@1674: Chris@1674: Event::ExportNameOptions options; Chris@1674: options.uriAttributeName = "image"; Chris@1663: Chris@1674: m_events.toXml(out, indent, QString("dimensions=\"1\""), options); Chris@424: } Chris@1663: Chris@1663: protected: Chris@1663: sv_samplerate_t m_sampleRate; Chris@1663: int m_resolution; Chris@424: Chris@1663: DeferredNotifier m_notifier; Chris@1663: int m_completion; Chris@1663: Chris@1663: EventSeries m_events; Chris@1663: Chris@1663: mutable QMutex m_mutex; Chris@302: }; Chris@302: Chris@302: Chris@302: #endif Chris@302: