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@420: #include "TabularModel.h" Chris@147: #include "base/Command.h" Chris@425: #include "base/RealTime.h" Chris@147: Chris@147: #include Chris@147: Chris@147: #include Chris@420: #include Chris@420: #include Chris@420: Chris@425: #include Chris@425: Chris@147: #include Chris@147: #include 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@420: class SparseModel : public Model, Chris@420: public TabularModel 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@420: /** Chris@420: * TabularModel methods. Chris@420: */ Chris@420: Chris@420: virtual int getRowCount() const Chris@420: { Chris@420: return m_points.size(); Chris@420: } Chris@420: Chris@420: virtual long getFrameForRow(int row) const Chris@420: { Chris@420: PointListIterator i = getPointListIteratorForRow(row); Chris@420: if (i == m_points.end()) return 0; Chris@420: return i->frame; Chris@420: } Chris@420: Chris@420: virtual int getRowForFrame(long frame) const Chris@420: { Chris@420: if (m_rows.empty()) rebuildRowVector(); Chris@420: std::vector::iterator i = Chris@420: std::lower_bound(m_rows.begin(), m_rows.end(), frame); Chris@432: int row = std::distance(m_rows.begin(), i); Chris@432: if (i != m_rows.begin() && (i == m_rows.end() || *i != frame)) { Chris@432: --row; Chris@432: } Chris@432: return row; Chris@420: } Chris@420: Chris@420: virtual int getColumnCount() const { return 1; } Chris@425: virtual QVariant getData(int row, int column, int role) const Chris@425: { Chris@425: PointListIterator i = getPointListIteratorForRow(row); Chris@425: if (i == m_points.end()) return QVariant(); Chris@425: Chris@425: switch (column) { Chris@425: case 0: { Chris@425: if (role == SortRole) return int(i->frame); Chris@425: RealTime rt = RealTime::frame2RealTime(i->frame, getSampleRate()); Chris@425: if (role == Qt::EditRole) return rt.toString().c_str(); Chris@425: else return rt.toText().c_str(); Chris@425: } Chris@425: case 1: return int(i->frame); Chris@425: } Chris@425: Chris@420: return QVariant(); Chris@420: } Chris@427: Chris@425: virtual Command *getSetDataCommand(int row, int column, Chris@425: const QVariant &value, int role) Chris@425: { Chris@425: if (role != Qt::EditRole) return false; Chris@425: PointListIterator i = getPointListIteratorForRow(row); Chris@425: if (i == m_points.end()) return false; Chris@425: EditCommand *command = new EditCommand(this, tr("Edit Data")); Chris@425: Chris@425: Point point(*i); Chris@425: command->deletePoint(point); Chris@425: Chris@425: switch (column) { Chris@425: case 0: point.frame = lrint(value.toDouble() * getSampleRate()); break; Chris@425: case 1: point.frame = value.toInt(); break; Chris@425: } Chris@425: Chris@425: command->addPoint(point); Chris@425: return command->finish(); Chris@425: } Chris@425: Chris@427: virtual Command *getInsertRowCommand(int row) Chris@427: { Chris@427: EditCommand *command = new EditCommand(this, tr("Insert Data Point")); Chris@427: Point point(0); Chris@427: PointListIterator i = getPointListIteratorForRow(row); Chris@427: if (i == m_points.end() && i != m_points.begin()) --i; Chris@427: if (i != m_points.end()) point = *i; Chris@427: command->addPoint(point); Chris@427: return command->finish(); Chris@427: } Chris@427: Chris@427: virtual Command *getRemoveRowCommand(int row) Chris@427: { Chris@427: EditCommand *command = new EditCommand(this, tr("Delete Data Point")); Chris@427: PointListIterator i = getPointListIteratorForRow(row); Chris@427: if (i == m_points.end()) return 0; Chris@427: command->deletePoint(*i); Chris@427: return command->finish(); Chris@427: } Chris@427: 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@420: Chris@420: void getPointIterators(long frame, Chris@420: PointListIterator &startItr, Chris@420: PointListIterator &endItr) const; Chris@420: Chris@420: // This is only used if the model is called on to act in Chris@420: // TabularModel mode Chris@420: mutable std::vector m_rows; // map from row number to frame Chris@420: void rebuildRowVector() const Chris@420: { Chris@420: m_rows.clear(); Chris@420: for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) { Chris@420: m_rows.push_back(i->frame); Chris@420: } Chris@420: } Chris@420: Chris@420: PointListIterator getPointListIteratorForRow(int row) const Chris@420: { Chris@420: if (m_rows.empty()) rebuildRowVector(); Chris@425: if (row < 0 || row + 1 > int(m_rows.size())) return m_points.end(); Chris@420: Chris@420: size_t frame = m_rows[row]; Chris@420: int indexAtFrame = 0; Chris@420: int ri = row; Chris@420: while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; } Chris@420: int initialIndexAtFrame = indexAtFrame; Chris@420: Chris@420: PointListIterator i0, i1; Chris@420: getPointIterators(frame, i0, i1); Chris@420: PointListIterator i = i0; Chris@420: Chris@420: for (i = i0; i != i1; ++i) { Chris@420: if (indexAtFrame > 0) { --indexAtFrame; continue; } Chris@420: return i; Chris@420: } Chris@420: Chris@420: if (indexAtFrame > 0) { Chris@420: std::cerr << "WARNING: SparseModel::getPointListIteratorForRow: No iterator available for row " << row << " (frame = " << frame << ", index at frame = " << initialIndexAtFrame << ", leftover index " << indexAtFrame << ")" << std::endl; Chris@420: } Chris@420: return i; Chris@420: } 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@425: return 0; //!!! is this ever used? Chris@425: /* 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@425: */ 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@420: PointListIterator startItr, endItr; Chris@420: getPointIterators(frame, startItr, 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@420: void Chris@420: SparseModel::getPointIterators(long frame, Chris@420: PointListIterator &startItr, Chris@420: PointListIterator &endItr) const Chris@420: { Chris@420: QMutexLocker locker(&m_mutex); Chris@420: Chris@420: if (m_resolution == 0) { Chris@420: startItr = m_points.end(); Chris@420: endItr = m_points.end(); Chris@420: return; Chris@420: } Chris@420: Chris@420: long start = (frame / m_resolution) * m_resolution; Chris@420: long end = start + m_resolution; Chris@420: Chris@420: PointType startPoint(start), endPoint(end); Chris@420: Chris@420: startItr = m_points.lower_bound(startPoint); Chris@420: endItr = m_points.upper_bound(endPoint); Chris@420: } Chris@420: Chris@420: 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@420: m_rows.clear(); 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@420: m_rows.clear(); 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@420: m_rows.clear(); //!!! inefficient 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@420: m_rows.clear(); //!!! inefficient 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@420: m_rows.clear(); //!!! inefficient 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@420: m_rows.clear(); //!!! inefficient 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: