Chris@147: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@147: Chris@147: /* Chris@147: Sonic Visualiser Chris@147: An audio file viewer and annotation editor. Chris@147: Centre for Digital Music, Queen Mary, University of London. Chris@147: This file copyright 2006 Chris Cannam. Chris@147: Chris@147: This program is free software; you can redistribute it and/or Chris@147: modify it under the terms of the GNU General Public License as Chris@147: published by the Free Software Foundation; either version 2 of the Chris@147: License, or (at your option) any later version. See the file Chris@147: COPYING included with this distribution for more information. Chris@147: */ Chris@147: Chris@147: #ifndef _SPARSE_MODEL_H_ Chris@147: #define _SPARSE_MODEL_H_ Chris@147: Chris@150: #include "Model.h" Chris@147: #include "base/Command.h" Chris@147: Chris@147: #include Chris@147: Chris@147: #include Chris@147: #include Chris@147: #include Chris@147: Chris@147: Chris@147: /** Chris@147: * Model containing sparse data (points with some properties). The Chris@147: * properties depend on the point type. Chris@147: */ Chris@147: Chris@147: template Chris@147: class SparseModel : public Model Chris@147: { Chris@147: public: Chris@147: SparseModel(size_t sampleRate, size_t resolution, Chris@147: bool notifyOnAdd = true); Chris@147: virtual ~SparseModel() { } Chris@147: Chris@147: virtual bool isOK() const { return true; } Chris@147: virtual size_t getStartFrame() const; Chris@147: virtual size_t getEndFrame() const; Chris@147: virtual size_t getSampleRate() const { return m_sampleRate; } Chris@147: Chris@147: virtual Model *clone() const; Chris@147: Chris@147: // Number of frames of the underlying sample rate that this model Chris@147: // is capable of resolving to. For example, if m_resolution == 10 Chris@147: // then every point in this model will be at a multiple of 10 Chris@147: // sample frames and should be considered to cover a window ending Chris@147: // 10 sample frames later. Chris@147: virtual size_t getResolution() const { Chris@147: return m_resolution ? m_resolution : 1; Chris@147: } Chris@147: virtual void setResolution(size_t resolution); Chris@147: Chris@147: typedef PointType Point; Chris@147: typedef std::multiset PointList; Chris@147: typedef typename PointList::iterator PointListIterator; Chris@147: Chris@147: /** Chris@147: * Return whether the model is empty or not. Chris@147: */ Chris@147: virtual bool isEmpty() const; Chris@147: Chris@147: /** Chris@147: * Get the total number of points in the model. Chris@147: */ Chris@147: virtual size_t getPointCount() const; Chris@147: Chris@147: /** Chris@147: * Get all of the points in this model between the given Chris@147: * boundaries (in frames), as well as up to two points before and Chris@147: * after the boundaries. If you need exact boundaries, check the Chris@147: * point coordinates in the returned list. Chris@147: */ Chris@147: virtual PointList getPoints(long start, long end) const; Chris@147: Chris@147: /** Chris@147: * Get all points that cover the given frame number, taking the Chris@147: * resolution of the model into account. Chris@147: */ Chris@147: virtual PointList getPoints(long frame) const; Chris@147: Chris@147: /** Chris@297: * Get all points. Chris@297: */ Chris@297: virtual const PointList &getPoints() const { return m_points; } Chris@297: Chris@297: /** Chris@147: * Return all points that share the nearest frame number prior to Chris@147: * the given one at which there are any points. Chris@147: */ Chris@147: virtual PointList getPreviousPoints(long frame) const; Chris@147: Chris@147: /** Chris@147: * Return all points that share the nearest frame number Chris@147: * subsequent to the given one at which there are any points. Chris@147: */ Chris@147: virtual PointList getNextPoints(long frame) const; Chris@147: Chris@147: /** Chris@147: * Remove all points. Chris@147: */ Chris@147: virtual void clear(); Chris@147: Chris@147: /** Chris@147: * Add a point. Chris@147: */ Chris@147: virtual void addPoint(const PointType &point); Chris@147: Chris@147: /** Chris@147: * Remove a point. Points are not necessarily unique, so this Chris@147: * function will remove the first point that compares equal to the Chris@147: * supplied one using Point::Comparator. Other identical points Chris@147: * may remain in the model. Chris@147: */ Chris@147: virtual void deletePoint(const PointType &point); Chris@147: Chris@297: virtual bool isReady(int *completion = 0) const { Chris@297: bool ready = isOK() && (m_completion == 100); Chris@297: if (completion) *completion = m_completion; Chris@297: return ready; Chris@297: } Chris@297: Chris@333: virtual void setCompletion(int completion, bool update = true); Chris@147: virtual int getCompletion() const { return m_completion; } Chris@147: Chris@147: virtual bool hasTextLabels() const { return m_hasTextLabels; } Chris@147: Chris@345: QString getTypeName() const { return tr("Sparse"); } Chris@345: Chris@407: virtual QString getXmlOutputType() const { return "sparse"; } Chris@407: Chris@147: virtual void toXml(QTextStream &out, Chris@147: QString indent = "", Chris@147: QString extraAttributes = "") const; Chris@147: Chris@147: virtual QString toDelimitedDataString(QString delimiter) const Chris@147: { Chris@147: QString s; Chris@147: for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) { Chris@147: s += i->toDelimitedDataString(delimiter, m_sampleRate) + "\n"; Chris@147: } Chris@147: return s; Chris@147: } Chris@147: Chris@147: /** Chris@147: * Command to add a point, with undo. Chris@147: */ Chris@147: class AddPointCommand : public Command Chris@147: { Chris@147: public: Chris@147: AddPointCommand(SparseModel *model, Chris@147: const PointType &point, Chris@147: QString name = "") : Chris@147: m_model(model), m_point(point), m_name(name) { } Chris@147: Chris@147: virtual QString getName() const { Chris@147: return (m_name == "" ? tr("Add Point") : m_name); Chris@147: } Chris@147: Chris@147: virtual void execute() { m_model->addPoint(m_point); } Chris@147: virtual void unexecute() { m_model->deletePoint(m_point); } Chris@147: Chris@147: const PointType &getPoint() const { return m_point; } Chris@147: Chris@147: private: Chris@147: SparseModel *m_model; Chris@147: PointType m_point; Chris@147: QString m_name; Chris@147: }; Chris@147: Chris@147: Chris@147: /** Chris@147: * Command to remove a point, with undo. Chris@147: */ Chris@147: class DeletePointCommand : public Command Chris@147: { Chris@147: public: Chris@147: DeletePointCommand(SparseModel *model, Chris@147: const PointType &point) : Chris@147: m_model(model), m_point(point) { } Chris@147: Chris@147: virtual QString getName() const { return tr("Delete Point"); } Chris@147: Chris@147: virtual void execute() { m_model->deletePoint(m_point); } Chris@147: virtual void unexecute() { m_model->addPoint(m_point); } Chris@147: Chris@147: const PointType &getPoint() const { return m_point; } Chris@147: Chris@147: private: Chris@147: SparseModel *m_model; Chris@147: PointType m_point; Chris@147: }; Chris@147: Chris@147: Chris@147: /** Chris@147: * Command to add or remove a series of points, with undo. Chris@147: * Consecutive add/remove pairs for the same point are collapsed. Chris@147: */ Chris@147: class EditCommand : public MacroCommand Chris@147: { Chris@147: public: Chris@147: EditCommand(SparseModel *model, QString commandName); Chris@147: Chris@147: virtual void addPoint(const PointType &point); Chris@147: virtual void deletePoint(const PointType &point); Chris@147: Chris@147: /** Chris@147: * Stack an arbitrary other command in the same sequence. Chris@147: */ Chris@147: virtual void addCommand(Command *command) { addCommand(command, true); } Chris@147: Chris@147: /** Chris@387: * If any points have been added or deleted, return this Chris@387: * command (so the caller can add it to the command history). Chris@416: * Otherwise delete the command and return NULL. Chris@147: */ Chris@416: virtual EditCommand *finish(); Chris@147: Chris@147: protected: Chris@147: virtual void addCommand(Command *command, bool executeFirst); Chris@147: Chris@147: SparseModel *m_model; Chris@147: }; Chris@147: Chris@147: Chris@147: /** Chris@147: * Command to relabel a point. Chris@147: */ Chris@147: class RelabelCommand : public Command Chris@147: { Chris@147: public: Chris@147: RelabelCommand(SparseModel *model, Chris@147: const PointType &point, Chris@147: QString newLabel) : Chris@147: m_model(model), m_oldPoint(point), m_newPoint(point) { Chris@147: m_newPoint.label = newLabel; Chris@147: } Chris@147: Chris@147: virtual QString getName() const { return tr("Re-Label Point"); } Chris@147: Chris@147: virtual void execute() { Chris@147: m_model->deletePoint(m_oldPoint); Chris@147: m_model->addPoint(m_newPoint); Chris@147: std::swap(m_oldPoint, m_newPoint); Chris@147: } Chris@147: Chris@147: virtual void unexecute() { execute(); } Chris@147: Chris@147: private: Chris@147: SparseModel *m_model; Chris@147: PointType m_oldPoint; Chris@147: PointType m_newPoint; Chris@147: }; Chris@147: Chris@147: Chris@147: Chris@147: protected: Chris@147: size_t m_sampleRate; Chris@147: size_t m_resolution; Chris@147: bool m_notifyOnAdd; Chris@147: long m_sinceLastNotifyMin; Chris@147: long m_sinceLastNotifyMax; Chris@147: bool m_hasTextLabels; Chris@147: Chris@147: PointList m_points; Chris@147: size_t m_pointCount; Chris@147: mutable QMutex m_mutex; Chris@147: int m_completion; Chris@147: }; Chris@147: Chris@147: Chris@147: template Chris@147: SparseModel::SparseModel(size_t sampleRate, Chris@147: size_t resolution, Chris@147: bool notifyOnAdd) : Chris@147: m_sampleRate(sampleRate), Chris@147: m_resolution(resolution), Chris@147: m_notifyOnAdd(notifyOnAdd), Chris@147: m_sinceLastNotifyMin(-1), Chris@147: m_sinceLastNotifyMax(-1), Chris@147: m_hasTextLabels(false), Chris@147: m_pointCount(0), Chris@147: m_completion(100) Chris@147: { Chris@147: } Chris@147: Chris@147: template Chris@147: size_t Chris@147: SparseModel::getStartFrame() const Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: size_t f = 0; Chris@147: if (!m_points.empty()) { Chris@147: f = m_points.begin()->frame; Chris@147: } Chris@147: return f; Chris@147: } Chris@147: Chris@147: template Chris@147: size_t Chris@147: SparseModel::getEndFrame() const Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: size_t f = 0; Chris@147: if (!m_points.empty()) { Chris@147: PointListIterator i(m_points.end()); Chris@147: f = (--i)->frame; Chris@147: } Chris@147: return f; Chris@147: } Chris@147: Chris@147: template Chris@147: Model * Chris@147: SparseModel::clone() const Chris@147: { Chris@147: SparseModel *model = Chris@147: new SparseModel(m_sampleRate, m_resolution, m_notifyOnAdd); Chris@147: model->m_points = m_points; Chris@147: model->m_pointCount = m_pointCount; Chris@147: return model; Chris@147: } Chris@147: Chris@147: template Chris@147: bool Chris@147: SparseModel::isEmpty() const Chris@147: { Chris@147: return m_pointCount == 0; Chris@147: } Chris@147: Chris@147: template Chris@147: size_t Chris@147: SparseModel::getPointCount() const Chris@147: { Chris@147: return m_pointCount; Chris@147: } Chris@147: Chris@147: template Chris@147: typename SparseModel::PointList Chris@147: SparseModel::getPoints(long start, long end) const Chris@147: { Chris@147: if (start > end) return PointList(); Chris@147: QMutexLocker locker(&m_mutex); Chris@147: Chris@147: PointType startPoint(start), endPoint(end); Chris@147: Chris@147: PointListIterator startItr = m_points.lower_bound(startPoint); Chris@147: PointListIterator endItr = m_points.upper_bound(endPoint); Chris@147: Chris@147: if (startItr != m_points.begin()) --startItr; Chris@147: if (startItr != m_points.begin()) --startItr; Chris@147: if (endItr != m_points.end()) ++endItr; Chris@147: if (endItr != m_points.end()) ++endItr; Chris@147: Chris@147: PointList rv; Chris@147: Chris@147: for (PointListIterator i = startItr; i != endItr; ++i) { Chris@147: rv.insert(*i); Chris@147: } Chris@147: Chris@147: return rv; Chris@147: } Chris@147: Chris@147: template Chris@147: typename SparseModel::PointList Chris@147: SparseModel::getPoints(long frame) const Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: Chris@147: if (m_resolution == 0) return PointList(); Chris@147: Chris@147: long start = (frame / m_resolution) * m_resolution; Chris@147: long end = start + m_resolution; Chris@147: Chris@147: PointType startPoint(start), endPoint(end); Chris@147: Chris@147: PointListIterator startItr = m_points.lower_bound(startPoint); Chris@147: PointListIterator endItr = m_points.upper_bound(endPoint); Chris@147: Chris@147: PointList rv; Chris@147: Chris@147: for (PointListIterator i = startItr; i != endItr; ++i) { Chris@147: rv.insert(*i); Chris@147: } Chris@147: Chris@147: return rv; Chris@147: } Chris@147: Chris@147: template Chris@147: typename SparseModel::PointList Chris@147: SparseModel::getPreviousPoints(long originFrame) const Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: Chris@147: PointType lookupPoint(originFrame); Chris@147: PointList rv; Chris@147: Chris@147: PointListIterator i = m_points.lower_bound(lookupPoint); Chris@147: if (i == m_points.begin()) return rv; Chris@147: Chris@147: --i; Chris@147: long frame = i->frame; Chris@147: while (i->frame == frame) { Chris@147: rv.insert(*i); Chris@147: if (i == m_points.begin()) break; Chris@147: --i; Chris@147: } Chris@147: Chris@147: return rv; Chris@147: } Chris@147: Chris@147: template Chris@147: typename SparseModel::PointList Chris@147: SparseModel::getNextPoints(long originFrame) const Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: Chris@147: PointType lookupPoint(originFrame); Chris@147: PointList rv; Chris@147: Chris@147: PointListIterator i = m_points.upper_bound(lookupPoint); Chris@147: if (i == m_points.end()) return rv; Chris@147: Chris@147: long frame = i->frame; Chris@147: while (i != m_points.end() && i->frame == frame) { Chris@147: rv.insert(*i); Chris@147: ++i; Chris@147: } Chris@147: Chris@147: return rv; Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::setResolution(size_t resolution) Chris@147: { Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: m_resolution = resolution; Chris@147: } Chris@147: emit modelChanged(); Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::clear() Chris@147: { Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: m_points.clear(); Chris@147: m_pointCount = 0; Chris@147: } Chris@147: emit modelChanged(); Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::addPoint(const PointType &point) Chris@147: { Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: m_points.insert(point); Chris@147: m_pointCount++; Chris@338: if (point.getLabel() != "") m_hasTextLabels = true; Chris@147: } Chris@147: Chris@147: // Even though this model is nominally sparse, there may still be Chris@147: // too many signals going on here (especially as they'll probably Chris@147: // be queued from one thread to another), which is why we need the Chris@147: // notifyOnAdd as an option rather than a necessity (the Chris@147: // alternative is to notify on setCompletion). Chris@147: Chris@147: if (m_notifyOnAdd) { Chris@147: emit modelChanged(point.frame, point.frame + m_resolution); Chris@147: } else { Chris@147: if (m_sinceLastNotifyMin == -1 || Chris@147: point.frame < m_sinceLastNotifyMin) { Chris@147: m_sinceLastNotifyMin = point.frame; Chris@147: } Chris@147: if (m_sinceLastNotifyMax == -1 || Chris@147: point.frame > m_sinceLastNotifyMax) { Chris@147: m_sinceLastNotifyMax = point.frame; Chris@147: } Chris@147: } Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::deletePoint(const PointType &point) Chris@147: { Chris@147: { Chris@147: QMutexLocker locker(&m_mutex); Chris@147: Chris@147: PointListIterator i = m_points.lower_bound(point); Chris@147: typename PointType::Comparator comparator; Chris@147: while (i != m_points.end()) { Chris@147: if (i->frame > point.frame) break; Chris@147: if (!comparator(*i, point) && !comparator(point, *i)) { Chris@147: m_points.erase(i); Chris@147: m_pointCount--; Chris@147: break; Chris@147: } Chris@147: ++i; Chris@147: } Chris@147: } Chris@147: // std::cout << "SparseOneDimensionalModel: emit modelChanged(" Chris@147: // << point.frame << ")" << std::endl; Chris@147: emit modelChanged(point.frame, point.frame + m_resolution); Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@333: SparseModel::setCompletion(int completion, bool update) Chris@147: { Chris@301: // std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; Chris@191: Chris@147: if (m_completion != completion) { Chris@147: m_completion = completion; Chris@147: Chris@147: if (completion == 100) { Chris@147: Chris@297: if (!m_notifyOnAdd) { Chris@297: emit completionChanged(); Chris@297: } Chris@297: Chris@147: m_notifyOnAdd = true; // henceforth Chris@147: emit modelChanged(); Chris@147: Chris@147: } else if (!m_notifyOnAdd) { Chris@147: Chris@333: if (update && Chris@333: m_sinceLastNotifyMin >= 0 && Chris@147: m_sinceLastNotifyMax >= 0) { Chris@147: emit modelChanged(m_sinceLastNotifyMin, m_sinceLastNotifyMax); Chris@147: m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; Chris@147: } else { Chris@147: emit completionChanged(); Chris@147: } Chris@147: } else { Chris@147: emit completionChanged(); Chris@147: } Chris@147: } Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::toXml(QTextStream &out, Chris@147: QString indent, Chris@147: QString extraAttributes) const Chris@147: { Chris@318: std::cerr << "SparseModel::toXml: extraAttributes = \"" Chris@318: << extraAttributes.toStdString() << std::endl; Chris@318: Chris@407: QString type = getXmlOutputType(); Chris@407: Chris@147: Model::toXml Chris@147: (out, Chris@147: indent, Chris@407: QString("type=\"%1\" dimensions=\"%2\" resolution=\"%3\" notifyOnAdd=\"%4\" dataset=\"%5\" %6") Chris@407: .arg(type) Chris@147: .arg(PointType(0).getDimensions()) Chris@147: .arg(m_resolution) Chris@147: .arg(m_notifyOnAdd ? "true" : "false") Chris@147: .arg(getObjectExportId(&m_points)) Chris@147: .arg(extraAttributes)); Chris@147: Chris@147: out << indent; Chris@147: out << QString("\n") Chris@147: .arg(getObjectExportId(&m_points)) Chris@147: .arg(PointType(0).getDimensions()); Chris@147: Chris@147: for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) { Chris@314: i->toXml(out, indent + " "); Chris@147: } Chris@147: Chris@147: out << indent; Chris@147: out << "\n"; Chris@147: } Chris@147: Chris@147: template Chris@147: SparseModel::EditCommand::EditCommand(SparseModel *model, Chris@147: QString commandName) : Chris@147: MacroCommand(commandName), Chris@147: m_model(model) Chris@147: { Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::EditCommand::addPoint(const PointType &point) Chris@147: { Chris@147: addCommand(new AddPointCommand(m_model, point), true); Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::EditCommand::deletePoint(const PointType &point) Chris@147: { Chris@147: addCommand(new DeletePointCommand(m_model, point), true); Chris@147: } Chris@147: Chris@147: template Chris@416: typename SparseModel::EditCommand * Chris@147: SparseModel::EditCommand::finish() Chris@147: { Chris@147: if (!m_commands.empty()) { Chris@387: return this; Chris@147: } else { Chris@147: delete this; Chris@389: return 0; Chris@147: } Chris@147: } Chris@147: Chris@147: template Chris@147: void Chris@147: SparseModel::EditCommand::addCommand(Command *command, Chris@147: bool executeFirst) Chris@147: { Chris@147: if (executeFirst) command->execute(); Chris@147: Chris@147: if (!m_commands.empty()) { Chris@147: DeletePointCommand *dpc = dynamic_cast(command); Chris@147: if (dpc) { Chris@147: AddPointCommand *apc = dynamic_cast Chris@147: (m_commands[m_commands.size() - 1]); Chris@147: typename PointType::Comparator comparator; Chris@147: if (apc) { Chris@147: if (!comparator(apc->getPoint(), dpc->getPoint()) && Chris@147: !comparator(dpc->getPoint(), apc->getPoint())) { Chris@147: deleteCommand(apc); Chris@147: return; Chris@147: } Chris@147: } Chris@147: } Chris@147: } Chris@147: Chris@147: MacroCommand::addCommand(command); Chris@147: } Chris@147: Chris@147: Chris@147: #endif Chris@147: Chris@147: Chris@147: