Chris@441: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@441: Chris@441: /* Chris@441: Sonic Visualiser Chris@441: An audio file viewer and annotation editor. Chris@441: Centre for Digital Music, Queen Mary, University of London. Chris@441: Chris@441: This program is free software; you can redistribute it and/or Chris@441: modify it under the terms of the GNU General Public License as Chris@441: published by the Free Software Foundation; either version 2 of the Chris@441: License, or (at your option) any later version. See the file Chris@441: COPYING included with this distribution for more information. Chris@441: */ Chris@441: Chris@1581: #ifndef SV_REGION_MODEL_H Chris@1581: #define SV_REGION_MODEL_H Chris@441: Chris@1649: #include "EventCommands.h" Chris@1649: #include "TabularModel.h" Chris@1649: #include "Model.h" Chris@1651: #include "DeferredNotifier.h" Chris@1649: Chris@441: #include "base/RealTime.h" Chris@1649: #include "base/EventSeries.h" Chris@1649: #include "base/UnitDatabase.h" Chris@1649: Chris@1649: #include "system/System.h" Chris@1649: Chris@1649: #include Chris@441: Chris@441: /** Chris@1649: * RegionModel -- a model for intervals associated with a value, which Chris@1649: * we call regions for no very compelling reason. Chris@441: */ Chris@1649: class RegionModel : public Model, Chris@1649: public TabularModel, Chris@1649: public EventEditable Chris@441: { Chris@441: Q_OBJECT Chris@441: Chris@441: public: Chris@1649: RegionModel(sv_samplerate_t sampleRate, Chris@1649: int resolution, Chris@441: bool notifyOnAdd = true) : Chris@1649: m_sampleRate(sampleRate), Chris@1649: m_resolution(resolution), Chris@1649: m_valueMinimum(0.f), Chris@1649: m_valueMaximum(0.f), Chris@1649: m_haveExtents(false), Chris@1429: m_valueQuantization(0), Chris@1649: m_haveDistinctValues(false), Chris@1651: m_notifier(this, Chris@1651: notifyOnAdd ? Chris@1651: DeferredNotifier::NOTIFY_ALWAYS : Chris@1651: DeferredNotifier::NOTIFY_DEFERRED), Chris@1655: m_completion(100) { Chris@441: } Chris@441: Chris@1040: RegionModel(sv_samplerate_t sampleRate, int resolution, Chris@459: float valueMinimum, float valueMaximum, Chris@459: bool notifyOnAdd = true) : Chris@1649: m_sampleRate(sampleRate), Chris@1649: m_resolution(resolution), Chris@1649: m_valueMinimum(valueMinimum), Chris@1649: m_valueMaximum(valueMaximum), Chris@1649: m_haveExtents(false), Chris@1429: m_valueQuantization(0), Chris@1649: m_haveDistinctValues(false), Chris@1651: m_notifier(this, Chris@1651: notifyOnAdd ? Chris@1651: DeferredNotifier::NOTIFY_ALWAYS : Chris@1651: DeferredNotifier::NOTIFY_DEFERRED), Chris@1655: m_completion(100) { Chris@441: } Chris@441: Chris@1649: virtual ~RegionModel() { Chris@1649: } Chris@1649: Chris@1649: QString getTypeName() const override { return tr("Region"); } Chris@1649: Chris@1649: bool isOK() const override { return true; } Chris@1649: sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); } Chris@1649: sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); } Chris@1649: sv_samplerate_t getSampleRate() const override { return m_sampleRate; } Chris@1649: int getResolution() const { return m_resolution; } Chris@1649: Chris@1649: QString getScaleUnits() const { return m_units; } Chris@1649: void setScaleUnits(QString units) { Chris@1649: m_units = units; Chris@1649: UnitDatabase::getInstance()->registerUnit(units); Chris@441: } Chris@441: Chris@441: float getValueQuantization() const { return m_valueQuantization; } Chris@441: void setValueQuantization(float q) { m_valueQuantization = q; } Chris@441: Chris@442: bool haveDistinctValues() const { return m_haveDistinctValues; } Chris@442: Chris@1649: float getValueMinimum() const { return m_valueMinimum; } Chris@1649: float getValueMaximum() const { return m_valueMaximum; } Chris@1649: Chris@1649: int getCompletion() const { return m_completion; } Chris@441: Chris@1649: void setCompletion(int completion, bool update = true) { Chris@441: Chris@1651: { QMutexLocker locker(&m_mutex); Chris@1651: if (m_completion == completion) return; Chris@1651: m_completion = completion; Chris@1649: } Chris@1649: Chris@1651: if (update) { Chris@1651: m_notifier.makeDeferredNotifications(); Chris@1649: } Chris@1651: Chris@1651: emit completionChanged(); Chris@1651: Chris@1651: if (completion == 100) { Chris@1651: // henceforth: Chris@1651: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); Chris@1649: emit modelChanged(); Chris@1649: } Chris@441: } Chris@441: Chris@441: /** Chris@1649: * Query methods. Chris@1649: */ Chris@1649: int getEventCount() const { Chris@1649: return m_events.count(); Chris@1649: } Chris@1649: bool isEmpty() const { Chris@1649: return m_events.isEmpty(); Chris@1649: } Chris@1649: bool containsEvent(const Event &e) const { Chris@1649: return m_events.contains(e); Chris@1649: } Chris@1649: EventVector getAllEvents() const { Chris@1649: return m_events.getAllEvents(); Chris@1649: } Chris@1649: EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { Chris@1649: return m_events.getEventsSpanning(f, duration); Chris@1649: } Chris@1649: EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { Chris@1649: return m_events.getEventsWithin(f, duration); Chris@1649: } Chris@1649: EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { Chris@1649: return m_events.getEventsStartingWithin(f, duration); Chris@1649: } Chris@1649: EventVector getEventsCovering(sv_frame_t f) const { Chris@1649: return m_events.getEventsCovering(f); Chris@1649: } Chris@1649: Chris@1649: /** Chris@1649: * Editing methods. Chris@1649: */ Chris@1649: void add(Event e) override { Chris@1649: Chris@1649: bool allChange = false; Chris@1649: Chris@1649: { Chris@1649: QMutexLocker locker(&m_mutex); Chris@1649: m_events.add(e); Chris@1649: Chris@1649: float v = e.getValue(); Chris@1649: if (!ISNAN(v) && !ISINF(v)) { Chris@1649: if (!m_haveExtents || v < m_valueMinimum) { Chris@1649: m_valueMinimum = v; allChange = true; Chris@1649: } Chris@1649: if (!m_haveExtents || v > m_valueMaximum) { Chris@1649: m_valueMaximum = v; allChange = true; Chris@1649: } Chris@1649: m_haveExtents = true; Chris@1649: } Chris@1649: Chris@1649: if (e.hasValue() && e.getValue() != 0.f) { Chris@1649: m_haveDistinctValues = true; Chris@1649: } Chris@1649: } Chris@1649: Chris@1651: m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); Chris@1651: Chris@1649: if (allChange) { Chris@1649: emit modelChanged(); Chris@1649: } Chris@1649: } Chris@1649: Chris@1649: void remove(Event e) override { Chris@1649: { Chris@1649: QMutexLocker locker(&m_mutex); Chris@1649: m_events.remove(e); Chris@1649: } Chris@1649: emit modelChangedWithin(e.getFrame(), Chris@1649: e.getFrame() + e.getDuration() + m_resolution); Chris@1649: } Chris@1649: Chris@1649: /** Chris@441: * TabularModel methods. Chris@441: */ Chris@1649: Chris@1649: int getRowCount() const override { Chris@1649: return m_events.count(); Chris@1649: } Chris@1649: Chris@1649: int getColumnCount() const override { Chris@618: return 5; Chris@441: } Chris@441: Chris@1649: bool isColumnTimeValue(int column) const override { Chris@1649: // NB duration is not a "time value" -- that's for columns Chris@1649: // whose sort ordering is exactly that of the frame time Chris@1649: return (column < 2); Chris@1649: } Chris@1649: Chris@1649: sv_frame_t getFrameForRow(int row) const override { Chris@1649: if (row < 0 || row >= m_events.count()) { Chris@1649: return 0; Chris@1649: } Chris@1649: Event e = m_events.getEventByIndex(row); Chris@1649: return e.getFrame(); Chris@1649: } Chris@1649: Chris@1649: int getRowForFrame(sv_frame_t frame) const override { Chris@1649: return m_events.getIndexForEvent(Event(frame)); Chris@1649: } Chris@1649: Chris@1649: QString getHeading(int column) const override { Chris@441: switch (column) { Chris@441: case 0: return tr("Time"); Chris@441: case 1: return tr("Frame"); Chris@441: case 2: return tr("Value"); Chris@441: case 3: return tr("Duration"); Chris@441: case 4: return tr("Label"); Chris@441: default: return tr("Unknown"); Chris@441: } Chris@441: } Chris@441: Chris@1651: SortType getSortType(int column) const override { Chris@1651: if (column == 4) return SortAlphabetical; Chris@1651: return SortNumeric; Chris@1651: } Chris@1651: Chris@1649: QVariant getData(int row, int column, int role) const override { Chris@1649: Chris@1649: if (row < 0 || row >= m_events.count()) { Chris@1649: return QVariant(); Chris@441: } Chris@441: Chris@1649: Event e = m_events.getEventByIndex(row); Chris@441: Chris@441: switch (column) { Chris@1649: case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); Chris@1649: case 1: return int(e.getFrame()); Chris@1649: case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); Chris@1649: case 3: return int(e.getDuration()); Chris@1649: case 4: return e.getLabel(); Chris@441: default: return QVariant(); Chris@441: } Chris@441: } Chris@441: Chris@1649: Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { Chris@1649: Chris@1649: if (row < 0 || row >= m_events.count()) return nullptr; Chris@1649: if (role != Qt::EditRole) return nullptr; Chris@1649: Chris@1649: Event e0 = m_events.getEventByIndex(row); Chris@1649: Event e1; Chris@1649: Chris@1649: switch (column) { Chris@1649: case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * Chris@1649: getSampleRate()))); break; Chris@1649: case 1: e1 = e0.withFrame(value.toInt()); break; Chris@1649: case 2: e1 = e0.withValue(float(value.toDouble())); break; Chris@1649: case 3: e1 = e0.withDuration(value.toInt()); break; Chris@1649: case 4: e1 = e0.withLabel(value.toString()); break; Chris@441: } Chris@441: Chris@1649: ChangeEventsCommand *command = Chris@1649: new ChangeEventsCommand(this, tr("Edit Data")); Chris@1649: command->remove(e0); Chris@1649: command->add(e1); Chris@441: return command->finish(); Chris@441: } Chris@441: Chris@1649: Chris@1649: /** Chris@1649: * XmlExportable methods. Chris@1649: */ Chris@1649: void toXml(QTextStream &out, Chris@1649: QString indent = "", Chris@1649: QString extraAttributes = "") const override { Chris@1649: Chris@1649: Model::toXml Chris@1649: (out, Chris@1649: indent, Chris@1649: QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" " Chris@1649: "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" " Chris@1649: "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" " Chris@1651: "units=\"%8\" %9") Chris@1649: .arg(m_resolution) Chris@1651: .arg("true") // always true after model reaches 100% - Chris@1651: // subsequent events are always notified Chris@1649: .arg(getObjectExportId(&m_events)) Chris@1649: .arg("region") Chris@1649: .arg(m_valueQuantization) Chris@1649: .arg(m_valueMinimum) Chris@1649: .arg(m_valueMaximum) Chris@1651: .arg(encodeEntities(m_units)) Chris@1649: .arg(extraAttributes)); Chris@1649: Chris@1649: m_events.toXml(out, indent, QString("dimensions=\"3\"")); Chris@442: } Chris@442: Chris@441: protected: Chris@1649: sv_samplerate_t m_sampleRate; Chris@1649: int m_resolution; Chris@1649: Chris@1649: float m_valueMinimum; Chris@1649: float m_valueMaximum; Chris@1649: bool m_haveExtents; Chris@441: float m_valueQuantization; Chris@442: bool m_haveDistinctValues; Chris@1649: QString m_units; Chris@1651: DeferredNotifier m_notifier; Chris@1651: int m_completion; Chris@1649: Chris@1649: EventSeries m_events; Chris@1649: Chris@1649: mutable QMutex m_mutex; Chris@441: }; Chris@441: Chris@441: #endif