| Chris@147 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@147 | 2 | 
| Chris@147 | 3 /* | 
| Chris@147 | 4     Sonic Visualiser | 
| Chris@147 | 5     An audio file viewer and annotation editor. | 
| Chris@147 | 6     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@147 | 7     This file copyright 2006 Chris Cannam. | 
| Chris@147 | 8 | 
| Chris@147 | 9     This program is free software; you can redistribute it and/or | 
| Chris@147 | 10     modify it under the terms of the GNU General Public License as | 
| Chris@147 | 11     published by the Free Software Foundation; either version 2 of the | 
| Chris@147 | 12     License, or (at your option) any later version.  See the file | 
| Chris@147 | 13     COPYING included with this distribution for more information. | 
| Chris@147 | 14 */ | 
| Chris@147 | 15 | 
| cannam@1452 | 16 #ifndef SV_SPARSE_MODEL_H | 
| cannam@1452 | 17 #define SV_SPARSE_MODEL_H | 
| Chris@147 | 18 | 
| Chris@150 | 19 #include "Model.h" | 
| Chris@420 | 20 #include "TabularModel.h" | 
| Chris@147 | 21 #include "base/Command.h" | 
| Chris@425 | 22 #include "base/RealTime.h" | 
| Chris@1218 | 23 #include "system/System.h" | 
| Chris@147 | 24 | 
| Chris@147 | 25 #include <iostream> | 
| Chris@147 | 26 | 
| Chris@147 | 27 #include <set> | 
| Chris@420 | 28 #include <vector> | 
| Chris@420 | 29 #include <algorithm> | 
| Chris@608 | 30 #include <iterator> | 
| Chris@420 | 31 | 
| Chris@425 | 32 #include <cmath> | 
| Chris@425 | 33 | 
| Chris@147 | 34 #include <QMutex> | 
| Chris@147 | 35 #include <QTextStream> | 
| Chris@147 | 36 | 
| Chris@147 | 37 /** | 
| Chris@147 | 38  * Model containing sparse data (points with some properties).  The | 
| Chris@147 | 39  * properties depend on the point type. | 
| Chris@147 | 40  */ | 
| Chris@147 | 41 | 
| Chris@147 | 42 template <typename PointType> | 
| Chris@420 | 43 class SparseModel : public Model, | 
| Chris@420 | 44                     public TabularModel | 
| Chris@147 | 45 { | 
| Chris@1458 | 46     // If we omit the Q_OBJECT macro, lupdate complains. | 
| Chris@1458 | 47 | 
| Chris@1458 | 48     // If we include it, moc fails (can't handle template classes). | 
| Chris@1458 | 49 | 
| Chris@1458 | 50     // If we omit it, lupdate still seems to emit translatable | 
| Chris@1458 | 51     // messages for the tr() strings in here. So I guess we omit it. | 
| Chris@1458 | 52 | 
| Chris@147 | 53 public: | 
| Chris@1040 | 54     SparseModel(sv_samplerate_t sampleRate, int resolution, | 
| Chris@1429 | 55                 bool notifyOnAdd = true); | 
| Chris@147 | 56     virtual ~SparseModel() { } | 
| Chris@147 | 57 | 
| Chris@147 | 58     virtual bool isOK() const { return true; } | 
| Chris@1038 | 59     virtual sv_frame_t getStartFrame() const; | 
| Chris@1038 | 60     virtual sv_frame_t getEndFrame() const; | 
| Chris@1040 | 61     virtual sv_samplerate_t getSampleRate() const { return m_sampleRate; } | 
| Chris@147 | 62 | 
| Chris@147 | 63     // Number of frames of the underlying sample rate that this model | 
| Chris@147 | 64     // is capable of resolving to.  For example, if m_resolution == 10 | 
| Chris@147 | 65     // then every point in this model will be at a multiple of 10 | 
| Chris@147 | 66     // sample frames and should be considered to cover a window ending | 
| Chris@147 | 67     // 10 sample frames later. | 
| Chris@929 | 68     virtual int getResolution() const { | 
| Chris@147 | 69         return m_resolution ? m_resolution : 1; | 
| Chris@147 | 70     } | 
| Chris@929 | 71     virtual void setResolution(int resolution); | 
| Chris@1066 | 72 | 
| Chris@147 | 73     typedef PointType Point; | 
| Chris@147 | 74     typedef std::multiset<PointType, | 
| Chris@1429 | 75                           typename PointType::OrderComparator> PointList; | 
| Chris@147 | 76     typedef typename PointList::iterator PointListIterator; | 
| Chris@606 | 77     typedef typename PointList::const_iterator PointListConstIterator; | 
| Chris@147 | 78 | 
| Chris@147 | 79     /** | 
| Chris@147 | 80      * Return whether the model is empty or not. | 
| Chris@147 | 81      */ | 
| Chris@147 | 82     virtual bool isEmpty() const; | 
| Chris@147 | 83 | 
| Chris@147 | 84     /** | 
| Chris@147 | 85      * Get the total number of points in the model. | 
| Chris@147 | 86      */ | 
| Chris@929 | 87     virtual int getPointCount() const; | 
| Chris@147 | 88 | 
| Chris@147 | 89     /** | 
| Chris@459 | 90      * Get all points. | 
| Chris@459 | 91      */ | 
| Chris@459 | 92     virtual const PointList &getPoints() const; | 
| Chris@459 | 93 | 
| Chris@459 | 94     /** | 
| Chris@147 | 95      * Get all of the points in this model between the given | 
| Chris@147 | 96      * boundaries (in frames), as well as up to two points before and | 
| Chris@147 | 97      * after the boundaries.  If you need exact boundaries, check the | 
| Chris@147 | 98      * point coordinates in the returned list. | 
| Chris@147 | 99      */ | 
| Chris@1038 | 100     virtual PointList getPoints(sv_frame_t start, sv_frame_t end) const; | 
| Chris@147 | 101 | 
| Chris@147 | 102     /** | 
| Chris@147 | 103      * Get all points that cover the given frame number, taking the | 
| Chris@147 | 104      * resolution of the model into account. | 
| Chris@147 | 105      */ | 
| Chris@1038 | 106     virtual PointList getPoints(sv_frame_t frame) const; | 
| Chris@147 | 107 | 
| Chris@147 | 108     /** | 
| Chris@147 | 109      * Return all points that share the nearest frame number prior to | 
| Chris@147 | 110      * the given one at which there are any points. | 
| Chris@147 | 111      */ | 
| Chris@1038 | 112     virtual PointList getPreviousPoints(sv_frame_t frame) const; | 
| Chris@147 | 113 | 
| Chris@147 | 114     /** | 
| Chris@147 | 115      * Return all points that share the nearest frame number | 
| Chris@147 | 116      * subsequent to the given one at which there are any points. | 
| Chris@147 | 117      */ | 
| Chris@1038 | 118     virtual PointList getNextPoints(sv_frame_t frame) const; | 
| Chris@147 | 119 | 
| Chris@147 | 120     /** | 
| Chris@147 | 121      * Remove all points. | 
| Chris@147 | 122      */ | 
| Chris@147 | 123     virtual void clear(); | 
| Chris@147 | 124 | 
| Chris@147 | 125     /** | 
| Chris@147 | 126      * Add a point. | 
| Chris@147 | 127      */ | 
| Chris@147 | 128     virtual void addPoint(const PointType &point); | 
| Chris@147 | 129 | 
| Chris@147 | 130     /** | 
| Chris@147 | 131      * Remove a point.  Points are not necessarily unique, so this | 
| Chris@147 | 132      * function will remove the first point that compares equal to the | 
| Chris@147 | 133      * supplied one using Point::Comparator.  Other identical points | 
| Chris@147 | 134      * may remain in the model. | 
| Chris@147 | 135      */ | 
| Chris@147 | 136     virtual void deletePoint(const PointType &point); | 
| Chris@147 | 137 | 
| Chris@1113 | 138     /** | 
| Chris@1113 | 139      * Return true if the given point is found in this model, false | 
| Chris@1113 | 140      * otherwise. | 
| Chris@1113 | 141      */ | 
| Chris@1113 | 142     virtual bool containsPoint(const PointType &point); | 
| Chris@1113 | 143 | 
| Chris@297 | 144     virtual bool isReady(int *completion = 0) const { | 
| Chris@297 | 145         bool ready = isOK() && (m_completion == 100); | 
| Chris@297 | 146         if (completion) *completion = m_completion; | 
| Chris@297 | 147         return ready; | 
| Chris@297 | 148     } | 
| Chris@297 | 149 | 
| Chris@333 | 150     virtual void setCompletion(int completion, bool update = true); | 
| Chris@147 | 151     virtual int getCompletion() const { return m_completion; } | 
| Chris@147 | 152 | 
| Chris@147 | 153     virtual bool hasTextLabels() const { return m_hasTextLabels; } | 
| Chris@147 | 154 | 
| cannam@1452 | 155     virtual bool isSparse() const { return true; } | 
| cannam@1452 | 156 | 
| Chris@345 | 157     QString getTypeName() const { return tr("Sparse"); } | 
| Chris@345 | 158 | 
| Chris@407 | 159     virtual QString getXmlOutputType() const { return "sparse"; } | 
| Chris@407 | 160 | 
| Chris@147 | 161     virtual void toXml(QTextStream &out, | 
| Chris@147 | 162                        QString indent = "", | 
| Chris@147 | 163                        QString extraAttributes = "") const; | 
| Chris@147 | 164 | 
| Chris@1060 | 165     virtual QString toDelimitedDataString(QString delimiter) const { | 
| Chris@1072 | 166         return toDelimitedDataStringWithOptions | 
| Chris@1072 | 167             (delimiter, DataExportDefaults); | 
| Chris@1060 | 168     } | 
| Chris@1060 | 169 | 
| Chris@1060 | 170     virtual QString toDelimitedDataStringWithOptions(QString delimiter, | 
| Chris@1064 | 171                                                      DataExportOptions opts) const { | 
| Chris@1064 | 172         return toDelimitedDataStringSubsetWithOptions | 
| Chris@1064 | 173             (delimiter, opts, | 
| Chris@1451 | 174              std::min(getStartFrame(), sv_frame_t(0)), getEndFrame()); | 
| Chris@147 | 175     } | 
| Chris@147 | 176 | 
| Chris@1060 | 177     virtual QString toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const { | 
| Chris@1072 | 178         return toDelimitedDataStringSubsetWithOptions | 
| Chris@1072 | 179             (delimiter, DataExportDefaults, f0, f1); | 
| Chris@1060 | 180     } | 
| Chris@1060 | 181 | 
| Chris@1064 | 182     virtual QString toDelimitedDataStringSubsetWithOptions(QString delimiter, DataExportOptions opts, sv_frame_t f0, sv_frame_t f1) const { | 
| Chris@1064 | 183         if (opts & DataExportFillGaps) { | 
| Chris@1064 | 184             return toDelimitedDataStringSubsetFilled(delimiter, opts, f0, f1); | 
| Chris@1064 | 185         } else { | 
| Chris@1064 | 186             QString s; | 
| Chris@1064 | 187             for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) { | 
| Chris@1127 | 188                 if (i->frame >= f0 && i->frame < f1) { | 
| Chris@1064 | 189                     s += i->toDelimitedDataString(delimiter, opts, m_sampleRate) + "\n"; | 
| Chris@1064 | 190                 } | 
| Chris@838 | 191             } | 
| Chris@1064 | 192             return s; | 
| Chris@838 | 193         } | 
| Chris@838 | 194     } | 
| Chris@838 | 195 | 
| Chris@147 | 196     /** | 
| Chris@147 | 197      * Command to add a point, with undo. | 
| Chris@147 | 198      */ | 
| Chris@147 | 199     class AddPointCommand : public Command | 
| Chris@147 | 200     { | 
| Chris@147 | 201     public: | 
| Chris@1429 | 202         AddPointCommand(SparseModel<PointType> *model, | 
| Chris@1429 | 203                         const PointType &point, | 
| Chris@147 | 204                         QString name = "") : | 
| Chris@1429 | 205             m_model(model), m_point(point), m_name(name) { } | 
| Chris@147 | 206 | 
| Chris@1429 | 207         virtual QString getName() const { | 
| Chris@147 | 208             return (m_name == "" ? tr("Add Point") : m_name); | 
| Chris@147 | 209         } | 
| Chris@147 | 210 | 
| Chris@1429 | 211         virtual void execute() { m_model->addPoint(m_point); } | 
| Chris@1429 | 212         virtual void unexecute() { m_model->deletePoint(m_point); } | 
| Chris@147 | 213 | 
| Chris@1429 | 214         const PointType &getPoint() const { return m_point; } | 
| Chris@147 | 215 | 
| Chris@147 | 216     private: | 
| Chris@1429 | 217         SparseModel<PointType> *m_model; | 
| Chris@1429 | 218         PointType m_point; | 
| Chris@147 | 219         QString m_name; | 
| Chris@147 | 220     }; | 
| Chris@147 | 221 | 
| Chris@147 | 222 | 
| Chris@147 | 223     /** | 
| Chris@147 | 224      * Command to remove a point, with undo. | 
| Chris@147 | 225      */ | 
| Chris@147 | 226     class DeletePointCommand : public Command | 
| Chris@147 | 227     { | 
| Chris@147 | 228     public: | 
| Chris@1429 | 229         DeletePointCommand(SparseModel<PointType> *model, | 
| Chris@1429 | 230                            const PointType &point) : | 
| Chris@1429 | 231             m_model(model), m_point(point) { } | 
| Chris@147 | 232 | 
| Chris@1429 | 233         virtual QString getName() const { return tr("Delete Point"); } | 
| Chris@147 | 234 | 
| Chris@1429 | 235         virtual void execute() { m_model->deletePoint(m_point); } | 
| Chris@1429 | 236         virtual void unexecute() { m_model->addPoint(m_point); } | 
| Chris@147 | 237 | 
| Chris@1429 | 238         const PointType &getPoint() const { return m_point; } | 
| Chris@147 | 239 | 
| Chris@147 | 240     private: | 
| Chris@1429 | 241         SparseModel<PointType> *m_model; | 
| Chris@1429 | 242         PointType m_point; | 
| Chris@147 | 243     }; | 
| Chris@147 | 244 | 
| Chris@147 | 245 | 
| Chris@147 | 246     /** | 
| Chris@147 | 247      * Command to add or remove a series of points, with undo. | 
| Chris@147 | 248      * Consecutive add/remove pairs for the same point are collapsed. | 
| Chris@147 | 249      */ | 
| Chris@147 | 250     class EditCommand : public MacroCommand | 
| Chris@147 | 251     { | 
| Chris@147 | 252     public: | 
| Chris@1429 | 253         EditCommand(SparseModel<PointType> *model, QString commandName); | 
| Chris@147 | 254 | 
| Chris@1429 | 255         virtual void addPoint(const PointType &point); | 
| Chris@1429 | 256         virtual void deletePoint(const PointType &point); | 
| Chris@147 | 257 | 
| Chris@1429 | 258         /** | 
| Chris@1429 | 259          * Stack an arbitrary other command in the same sequence. | 
| Chris@1429 | 260          */ | 
| Chris@1429 | 261         virtual void addCommand(Command *command) { addCommand(command, true); } | 
| Chris@147 | 262 | 
| Chris@1429 | 263         /** | 
| Chris@1429 | 264          * If any points have been added or deleted, return this | 
| Chris@1429 | 265          * command (so the caller can add it to the command history). | 
| Chris@1429 | 266          * Otherwise delete the command and return NULL. | 
| Chris@1429 | 267          */ | 
| Chris@1429 | 268         virtual EditCommand *finish(); | 
| Chris@147 | 269 | 
| Chris@147 | 270     protected: | 
| Chris@1429 | 271         virtual void addCommand(Command *command, bool executeFirst); | 
| Chris@147 | 272 | 
| Chris@1429 | 273         SparseModel<PointType> *m_model; | 
| Chris@147 | 274     }; | 
| Chris@147 | 275 | 
| Chris@147 | 276 | 
| Chris@147 | 277     /** | 
| Chris@147 | 278      * Command to relabel a point. | 
| Chris@147 | 279      */ | 
| Chris@147 | 280     class RelabelCommand : public Command | 
| Chris@147 | 281     { | 
| Chris@147 | 282     public: | 
| Chris@1429 | 283         RelabelCommand(SparseModel<PointType> *model, | 
| Chris@1429 | 284                        const PointType &point, | 
| Chris@1429 | 285                        QString newLabel) : | 
| Chris@1429 | 286             m_model(model), m_oldPoint(point), m_newPoint(point) { | 
| Chris@1429 | 287             m_newPoint.label = newLabel; | 
| Chris@1429 | 288         } | 
| Chris@147 | 289 | 
| Chris@1429 | 290         virtual QString getName() const { return tr("Re-Label Point"); } | 
| Chris@147 | 291 | 
| Chris@1429 | 292         virtual void execute() { | 
| Chris@1429 | 293             m_model->deletePoint(m_oldPoint); | 
| Chris@1429 | 294             m_model->addPoint(m_newPoint); | 
| Chris@1429 | 295             std::swap(m_oldPoint, m_newPoint); | 
| Chris@1429 | 296         } | 
| Chris@147 | 297 | 
| Chris@1429 | 298         virtual void unexecute() { execute(); } | 
| Chris@147 | 299 | 
| Chris@147 | 300     private: | 
| Chris@1429 | 301         SparseModel<PointType> *m_model; | 
| Chris@1429 | 302         PointType m_oldPoint; | 
| Chris@1429 | 303         PointType m_newPoint; | 
| Chris@147 | 304     }; | 
| Chris@147 | 305 | 
| Chris@420 | 306     /** | 
| Chris@420 | 307      * TabularModel methods. | 
| Chris@420 | 308      */ | 
| Chris@420 | 309 | 
| Chris@420 | 310     virtual int getRowCount() const | 
| Chris@420 | 311     { | 
| Chris@1038 | 312         return int(m_points.size()); | 
| Chris@420 | 313     } | 
| Chris@420 | 314 | 
| Chris@1038 | 315     virtual sv_frame_t getFrameForRow(int row) const | 
| Chris@420 | 316     { | 
| Chris@606 | 317         PointListConstIterator i = getPointListIteratorForRow(row); | 
| Chris@420 | 318         if (i == m_points.end()) return 0; | 
| Chris@420 | 319         return i->frame; | 
| Chris@420 | 320     } | 
| Chris@420 | 321 | 
| Chris@1038 | 322     virtual int getRowForFrame(sv_frame_t frame) const | 
| Chris@420 | 323     { | 
| Chris@420 | 324         if (m_rows.empty()) rebuildRowVector(); | 
| Chris@1038 | 325         std::vector<sv_frame_t>::iterator i = | 
| Chris@420 | 326             std::lower_bound(m_rows.begin(), m_rows.end(), frame); | 
| Chris@1038 | 327         ssize_t row = std::distance(m_rows.begin(), i); | 
| Chris@432 | 328         if (i != m_rows.begin() && (i == m_rows.end() || *i != frame)) { | 
| Chris@432 | 329             --row; | 
| Chris@432 | 330         } | 
| Chris@1038 | 331         return int(row); | 
| Chris@420 | 332     } | 
| Chris@420 | 333 | 
| Chris@420 | 334     virtual int getColumnCount() const { return 1; } | 
| Chris@425 | 335     virtual QVariant getData(int row, int column, int role) const | 
| Chris@425 | 336     { | 
| Chris@606 | 337         PointListConstIterator i = getPointListIteratorForRow(row); | 
| Chris@1254 | 338         if (i == m_points.end()) { | 
| Chris@1254 | 339 //            cerr << "no iterator for row " << row << " (have " << getRowCount() << " rows)" << endl; | 
| Chris@1254 | 340             return QVariant(); | 
| Chris@1254 | 341         } | 
| Chris@425 | 342 | 
| Chris@1254 | 343 //        cerr << "returning data for row " << row << " col " << column << endl; | 
| Chris@1254 | 344 | 
| Chris@425 | 345         switch (column) { | 
| Chris@425 | 346         case 0: { | 
| Chris@425 | 347             if (role == SortRole) return int(i->frame); | 
| Chris@425 | 348             RealTime rt = RealTime::frame2RealTime(i->frame, getSampleRate()); | 
| Chris@425 | 349             if (role == Qt::EditRole) return rt.toString().c_str(); | 
| Chris@425 | 350             else return rt.toText().c_str(); | 
| Chris@425 | 351         } | 
| Chris@425 | 352         case 1: return int(i->frame); | 
| Chris@425 | 353         } | 
| Chris@425 | 354 | 
| Chris@420 | 355         return QVariant(); | 
| Chris@420 | 356     } | 
| Chris@427 | 357 | 
| Chris@425 | 358     virtual Command *getSetDataCommand(int row, int column, | 
| Chris@425 | 359                                        const QVariant &value, int role) | 
| Chris@425 | 360     { | 
| Chris@740 | 361         if (role != Qt::EditRole) return 0; | 
| Chris@425 | 362         PointListIterator i = getPointListIteratorForRow(row); | 
| Chris@740 | 363         if (i == m_points.end()) return 0; | 
| Chris@425 | 364         EditCommand *command = new EditCommand(this, tr("Edit Data")); | 
| Chris@425 | 365 | 
| Chris@425 | 366         Point point(*i); | 
| Chris@425 | 367         command->deletePoint(point); | 
| Chris@425 | 368 | 
| Chris@425 | 369         switch (column) { | 
| Chris@425 | 370         case 0: point.frame = lrint(value.toDouble() * getSampleRate()); break; | 
| Chris@425 | 371         case 1: point.frame = value.toInt(); break; | 
| Chris@425 | 372         } | 
| Chris@425 | 373 | 
| Chris@425 | 374         command->addPoint(point); | 
| Chris@425 | 375         return command->finish(); | 
| Chris@425 | 376     } | 
| Chris@425 | 377 | 
| Chris@427 | 378     virtual Command *getInsertRowCommand(int row) | 
| Chris@427 | 379     { | 
| Chris@427 | 380         EditCommand *command = new EditCommand(this, tr("Insert Data Point")); | 
| Chris@427 | 381         Point point(0); | 
| Chris@427 | 382         PointListIterator i = getPointListIteratorForRow(row); | 
| Chris@427 | 383         if (i == m_points.end() && i != m_points.begin()) --i; | 
| Chris@427 | 384         if (i != m_points.end()) point = *i; | 
| Chris@427 | 385         command->addPoint(point); | 
| Chris@427 | 386         return command->finish(); | 
| Chris@427 | 387     } | 
| Chris@427 | 388 | 
| Chris@427 | 389     virtual Command *getRemoveRowCommand(int row) | 
| Chris@427 | 390     { | 
| Chris@427 | 391         PointListIterator i = getPointListIteratorForRow(row); | 
| Chris@427 | 392         if (i == m_points.end()) return 0; | 
| Chris@978 | 393         EditCommand *command = new EditCommand(this, tr("Delete Data Point")); | 
| Chris@427 | 394         command->deletePoint(*i); | 
| Chris@427 | 395         return command->finish(); | 
| Chris@427 | 396     } | 
| Chris@427 | 397 | 
| Chris@147 | 398 protected: | 
| Chris@1040 | 399     sv_samplerate_t m_sampleRate; | 
| Chris@929 | 400     int m_resolution; | 
| Chris@147 | 401     bool m_notifyOnAdd; | 
| Chris@1038 | 402     sv_frame_t m_sinceLastNotifyMin; | 
| Chris@1038 | 403     sv_frame_t m_sinceLastNotifyMax; | 
| Chris@147 | 404     bool m_hasTextLabels; | 
| Chris@147 | 405 | 
| Chris@147 | 406     PointList m_points; | 
| Chris@929 | 407     int m_pointCount; | 
| Chris@147 | 408     mutable QMutex m_mutex; | 
| Chris@147 | 409     int m_completion; | 
| Chris@420 | 410 | 
| Chris@1038 | 411     void getPointIterators(sv_frame_t frame, | 
| Chris@420 | 412                            PointListIterator &startItr, | 
| Chris@608 | 413                            PointListIterator &endItr); | 
| Chris@1038 | 414     void getPointIterators(sv_frame_t frame, | 
| Chris@608 | 415                            PointListConstIterator &startItr, | 
| Chris@608 | 416                            PointListConstIterator &endItr) const; | 
| Chris@420 | 417 | 
| Chris@420 | 418     // This is only used if the model is called on to act in | 
| Chris@420 | 419     // TabularModel mode | 
| Chris@1038 | 420     mutable std::vector<sv_frame_t> m_rows; // map from row number to frame | 
| Chris@1254 | 421 | 
| Chris@420 | 422     void rebuildRowVector() const | 
| Chris@420 | 423     { | 
| Chris@420 | 424         m_rows.clear(); | 
| Chris@608 | 425         for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) { | 
| Chris@777 | 426 //            std::cerr << "rebuildRowVector: row " << m_rows.size() << " -> " << i->frame << std::endl; | 
| Chris@420 | 427             m_rows.push_back(i->frame); | 
| Chris@420 | 428         } | 
| Chris@420 | 429     } | 
| Chris@420 | 430 | 
| Chris@608 | 431     PointListIterator getPointListIteratorForRow(int row) | 
| Chris@420 | 432     { | 
| Chris@420 | 433         if (m_rows.empty()) rebuildRowVector(); | 
| Chris@425 | 434         if (row < 0 || row + 1 > int(m_rows.size())) return m_points.end(); | 
| Chris@420 | 435 | 
| Chris@1038 | 436         sv_frame_t frame = m_rows[row]; | 
| Chris@420 | 437         int indexAtFrame = 0; | 
| Chris@420 | 438         int ri = row; | 
| Chris@420 | 439         while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; } | 
| Chris@420 | 440         int initialIndexAtFrame = indexAtFrame; | 
| Chris@420 | 441 | 
| Chris@420 | 442         PointListIterator i0, i1; | 
| Chris@420 | 443         getPointIterators(frame, i0, i1); | 
| Chris@420 | 444         PointListIterator i = i0; | 
| Chris@420 | 445 | 
| Chris@420 | 446         for (i = i0; i != i1; ++i) { | 
| Chris@778 | 447             if (i->frame < (int)frame) { continue; } | 
| Chris@420 | 448             if (indexAtFrame > 0) { --indexAtFrame; continue; } | 
| Chris@420 | 449             return i; | 
| Chris@420 | 450         } | 
| Chris@420 | 451 | 
| Chris@420 | 452         if (indexAtFrame > 0) { | 
| Chris@420 | 453             std::cerr << "WARNING: SparseModel::getPointListIteratorForRow: No iterator available for row " << row << " (frame = " << frame << ", index at frame = " << initialIndexAtFrame << ", leftover index " << indexAtFrame << ")" << std::endl; | 
| Chris@420 | 454         } | 
| Chris@420 | 455         return i; | 
| Chris@420 | 456     } | 
| Chris@608 | 457 | 
| Chris@608 | 458     PointListConstIterator getPointListIteratorForRow(int row) const | 
| Chris@608 | 459     { | 
| Chris@608 | 460         if (m_rows.empty()) rebuildRowVector(); | 
| Chris@608 | 461         if (row < 0 || row + 1 > int(m_rows.size())) return m_points.end(); | 
| Chris@608 | 462 | 
| Chris@1038 | 463         sv_frame_t frame = m_rows[row]; | 
| Chris@608 | 464         int indexAtFrame = 0; | 
| Chris@608 | 465         int ri = row; | 
| Chris@608 | 466         while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; } | 
| Chris@608 | 467         int initialIndexAtFrame = indexAtFrame; | 
| Chris@608 | 468 | 
| Chris@1254 | 469 //        std::cerr << "getPointListIteratorForRow " << row << ": initialIndexAtFrame = " << initialIndexAtFrame << " for frame " << frame << std::endl; | 
| Chris@777 | 470 | 
| Chris@608 | 471         PointListConstIterator i0, i1; | 
| Chris@608 | 472         getPointIterators(frame, i0, i1); | 
| Chris@608 | 473         PointListConstIterator i = i0; | 
| Chris@608 | 474 | 
| Chris@608 | 475         for (i = i0; i != i1; ++i) { | 
| Chris@785 | 476 //            std::cerr << "i->frame is " << i->frame << ", wanting " << frame << std::endl; | 
| Chris@785 | 477 | 
| Chris@778 | 478             if (i->frame < (int)frame) { continue; } | 
| Chris@608 | 479             if (indexAtFrame > 0) { --indexAtFrame; continue; } | 
| Chris@608 | 480             return i; | 
| Chris@608 | 481         } | 
| Chris@1254 | 482 /* | 
| Chris@1254 | 483         if (i == m_points.end()) { | 
| Chris@1254 | 484             std::cerr << "returning i at end" << std::endl; | 
| Chris@1254 | 485         } else { | 
| Chris@1254 | 486             std::cerr << "returning i with i->frame = " << i->frame << std::endl; | 
| Chris@1254 | 487         } | 
| Chris@1254 | 488 */ | 
| Chris@608 | 489         if (indexAtFrame > 0) { | 
| Chris@608 | 490             std::cerr << "WARNING: SparseModel::getPointListIteratorForRow: No iterator available for row " << row << " (frame = " << frame << ", index at frame = " << initialIndexAtFrame << ", leftover index " << indexAtFrame << ")" << std::endl; | 
| Chris@608 | 491         } | 
| Chris@608 | 492         return i; | 
| Chris@608 | 493     } | 
| Chris@1064 | 494 | 
| Chris@1064 | 495     QString toDelimitedDataStringSubsetFilled(QString delimiter, | 
| Chris@1064 | 496                                               DataExportOptions opts, | 
| Chris@1064 | 497                                               sv_frame_t f0, | 
| Chris@1064 | 498                                               sv_frame_t f1) const { | 
| Chris@1064 | 499 | 
| Chris@1064 | 500         QString s; | 
| Chris@1064 | 501         opts &= ~DataExportFillGaps; | 
| Chris@1064 | 502 | 
| Chris@1064 | 503         // find frame time of first point in range (if any) | 
| Chris@1064 | 504         sv_frame_t first = f0; | 
| Chris@1064 | 505         for (auto &p: m_points) { | 
| Chris@1064 | 506             if (p.frame >= f0) { | 
| Chris@1064 | 507                 first = p.frame; | 
| Chris@1064 | 508                 break; | 
| Chris@1064 | 509             } | 
| Chris@1064 | 510         } | 
| Chris@1064 | 511 | 
| Chris@1064 | 512         // project back to first frame time in range according to | 
| Chris@1064 | 513         // resolution.  e.g. if f0 = 2, first = 9, resolution = 4 then | 
| Chris@1064 | 514         // we start at 5 (because 1 is too early and we need to arrive | 
| Chris@1064 | 515         // at 9 to match the first actual point). This method is | 
| Chris@1064 | 516         // stupid but easy to understand: | 
| Chris@1064 | 517         sv_frame_t f = first; | 
| Chris@1064 | 518         while (f >= f0 + m_resolution) f -= m_resolution; | 
| Chris@1064 | 519 | 
| Chris@1064 | 520         // now progress, either writing the next point (if within | 
| Chris@1064 | 521         // distance) or a default point | 
| Chris@1064 | 522         PointListConstIterator itr = m_points.begin(); | 
| Chris@1064 | 523 | 
| Chris@1064 | 524         while (f < f1) { | 
| Chris@1064 | 525             if (itr != m_points.end() && itr->frame <= f) { | 
| Chris@1064 | 526                 s += itr->toDelimitedDataString(delimiter, opts, m_sampleRate); | 
| Chris@1064 | 527                 ++itr; | 
| Chris@1064 | 528             } else { | 
| Chris@1064 | 529                 s += Point(f).toDelimitedDataString(delimiter, opts, m_sampleRate); | 
| Chris@1064 | 530             } | 
| Chris@1064 | 531             s += "\n"; | 
| Chris@1064 | 532             f += m_resolution; | 
| Chris@1064 | 533         } | 
| Chris@1064 | 534 | 
| Chris@1064 | 535         return s; | 
| Chris@1064 | 536     } | 
| Chris@147 | 537 }; | 
| Chris@147 | 538 | 
| Chris@147 | 539 | 
| Chris@147 | 540 template <typename PointType> | 
| Chris@1040 | 541 SparseModel<PointType>::SparseModel(sv_samplerate_t sampleRate, | 
| Chris@929 | 542                                     int resolution, | 
| Chris@147 | 543                                     bool notifyOnAdd) : | 
| Chris@147 | 544     m_sampleRate(sampleRate), | 
| Chris@147 | 545     m_resolution(resolution), | 
| Chris@147 | 546     m_notifyOnAdd(notifyOnAdd), | 
| Chris@147 | 547     m_sinceLastNotifyMin(-1), | 
| Chris@147 | 548     m_sinceLastNotifyMax(-1), | 
| Chris@147 | 549     m_hasTextLabels(false), | 
| Chris@147 | 550     m_pointCount(0), | 
| Chris@147 | 551     m_completion(100) | 
| Chris@147 | 552 { | 
| Chris@147 | 553 } | 
| Chris@147 | 554 | 
| Chris@147 | 555 template <typename PointType> | 
| Chris@1038 | 556 sv_frame_t | 
| Chris@147 | 557 SparseModel<PointType>::getStartFrame() const | 
| Chris@147 | 558 { | 
| Chris@147 | 559     QMutexLocker locker(&m_mutex); | 
| Chris@1038 | 560     sv_frame_t f = 0; | 
| Chris@147 | 561     if (!m_points.empty()) { | 
| Chris@1429 | 562         f = m_points.begin()->frame; | 
| Chris@147 | 563     } | 
| Chris@147 | 564     return f; | 
| Chris@147 | 565 } | 
| Chris@147 | 566 | 
| Chris@147 | 567 template <typename PointType> | 
| Chris@1038 | 568 sv_frame_t | 
| Chris@147 | 569 SparseModel<PointType>::getEndFrame() const | 
| Chris@147 | 570 { | 
| Chris@147 | 571     QMutexLocker locker(&m_mutex); | 
| Chris@1038 | 572     sv_frame_t f = 0; | 
| Chris@147 | 573     if (!m_points.empty()) { | 
| Chris@1429 | 574         PointListConstIterator i(m_points.end()); | 
| Chris@1451 | 575         f = (--i)->frame + 1; | 
| Chris@147 | 576     } | 
| Chris@1451 | 577     return f; | 
| Chris@147 | 578 } | 
| Chris@147 | 579 | 
| Chris@147 | 580 template <typename PointType> | 
| Chris@147 | 581 bool | 
| Chris@147 | 582 SparseModel<PointType>::isEmpty() const | 
| Chris@147 | 583 { | 
| Chris@147 | 584     return m_pointCount == 0; | 
| Chris@147 | 585 } | 
| Chris@147 | 586 | 
| Chris@147 | 587 template <typename PointType> | 
| Chris@929 | 588 int | 
| Chris@147 | 589 SparseModel<PointType>::getPointCount() const | 
| Chris@147 | 590 { | 
| Chris@147 | 591     return m_pointCount; | 
| Chris@147 | 592 } | 
| Chris@147 | 593 | 
| Chris@147 | 594 template <typename PointType> | 
| Chris@459 | 595 const typename SparseModel<PointType>::PointList & | 
| Chris@459 | 596 SparseModel<PointType>::getPoints() const | 
| Chris@459 | 597 { | 
| Chris@459 | 598     return m_points; | 
| Chris@459 | 599 } | 
| Chris@459 | 600 | 
| Chris@459 | 601 template <typename PointType> | 
| Chris@147 | 602 typename SparseModel<PointType>::PointList | 
| Chris@1038 | 603 SparseModel<PointType>::getPoints(sv_frame_t start, sv_frame_t end) const | 
| Chris@147 | 604 { | 
| Chris@147 | 605     if (start > end) return PointList(); | 
| Chris@147 | 606     QMutexLocker locker(&m_mutex); | 
| Chris@147 | 607 | 
| Chris@147 | 608     PointType startPoint(start), endPoint(end); | 
| Chris@147 | 609 | 
| Chris@608 | 610     PointListConstIterator startItr = m_points.lower_bound(startPoint); | 
| Chris@608 | 611     PointListConstIterator   endItr = m_points.upper_bound(endPoint); | 
| Chris@147 | 612 | 
| Chris@147 | 613     if (startItr != m_points.begin()) --startItr; | 
| Chris@147 | 614     if (startItr != m_points.begin()) --startItr; | 
| Chris@147 | 615     if (endItr != m_points.end()) ++endItr; | 
| Chris@147 | 616     if (endItr != m_points.end()) ++endItr; | 
| Chris@147 | 617 | 
| Chris@147 | 618     PointList rv; | 
| Chris@147 | 619 | 
| Chris@608 | 620     for (PointListConstIterator i = startItr; i != endItr; ++i) { | 
| Chris@1429 | 621         rv.insert(*i); | 
| Chris@147 | 622     } | 
| Chris@147 | 623 | 
| Chris@147 | 624     return rv; | 
| Chris@147 | 625 } | 
| Chris@147 | 626 | 
| Chris@147 | 627 template <typename PointType> | 
| Chris@147 | 628 typename SparseModel<PointType>::PointList | 
| Chris@1038 | 629 SparseModel<PointType>::getPoints(sv_frame_t frame) const | 
| Chris@147 | 630 { | 
| Chris@608 | 631     PointListConstIterator startItr, endItr; | 
| Chris@420 | 632     getPointIterators(frame, startItr, endItr); | 
| Chris@147 | 633 | 
| Chris@147 | 634     PointList rv; | 
| Chris@147 | 635 | 
| Chris@608 | 636     for (PointListConstIterator i = startItr; i != endItr; ++i) { | 
| Chris@1429 | 637         rv.insert(*i); | 
| Chris@147 | 638     } | 
| Chris@147 | 639 | 
| Chris@147 | 640     return rv; | 
| Chris@147 | 641 } | 
| Chris@147 | 642 | 
| Chris@147 | 643 template <typename PointType> | 
| Chris@420 | 644 void | 
| Chris@1038 | 645 SparseModel<PointType>::getPointIterators(sv_frame_t frame, | 
| Chris@420 | 646                                           PointListIterator &startItr, | 
| Chris@608 | 647                                           PointListIterator &endItr) | 
| Chris@608 | 648 { | 
| Chris@608 | 649     QMutexLocker locker(&m_mutex); | 
| Chris@608 | 650 | 
| Chris@608 | 651     if (m_resolution == 0) { | 
| Chris@608 | 652         startItr = m_points.end(); | 
| Chris@608 | 653         endItr = m_points.end(); | 
| Chris@608 | 654         return; | 
| Chris@608 | 655     } | 
| Chris@608 | 656 | 
| Chris@1038 | 657     sv_frame_t start = (frame / m_resolution) * m_resolution; | 
| Chris@1038 | 658     sv_frame_t end = start + m_resolution; | 
| Chris@608 | 659 | 
| Chris@608 | 660     PointType startPoint(start), endPoint(end); | 
| Chris@777 | 661 | 
| Chris@608 | 662     startItr = m_points.lower_bound(startPoint); | 
| Chris@608 | 663       endItr = m_points.upper_bound(endPoint); | 
| Chris@608 | 664 } | 
| Chris@608 | 665 | 
| Chris@608 | 666 template <typename PointType> | 
| Chris@608 | 667 void | 
| Chris@1038 | 668 SparseModel<PointType>::getPointIterators(sv_frame_t frame, | 
| Chris@608 | 669                                           PointListConstIterator &startItr, | 
| Chris@608 | 670                                           PointListConstIterator &endItr) const | 
| Chris@420 | 671 { | 
| Chris@420 | 672     QMutexLocker locker(&m_mutex); | 
| Chris@420 | 673 | 
| Chris@420 | 674     if (m_resolution == 0) { | 
| Chris@785 | 675 //        std::cerr << "getPointIterators: resolution == 0, returning end()" << std::endl; | 
| Chris@420 | 676         startItr = m_points.end(); | 
| Chris@420 | 677         endItr = m_points.end(); | 
| Chris@420 | 678         return; | 
| Chris@420 | 679     } | 
| Chris@420 | 680 | 
| Chris@1038 | 681     sv_frame_t start = (frame / m_resolution) * m_resolution; | 
| Chris@1038 | 682     sv_frame_t end = start + m_resolution; | 
| Chris@420 | 683 | 
| Chris@420 | 684     PointType startPoint(start), endPoint(end); | 
| Chris@420 | 685 | 
| Chris@777 | 686 //    std::cerr << "getPointIterators: start frame " << start << ", end frame " << end << ", m_resolution " << m_resolution << std::endl; | 
| Chris@785 | 687 | 
| Chris@420 | 688     startItr = m_points.lower_bound(startPoint); | 
| Chris@420 | 689       endItr = m_points.upper_bound(endPoint); | 
| Chris@420 | 690 } | 
| Chris@420 | 691 | 
| Chris@420 | 692 template <typename PointType> | 
| Chris@147 | 693 typename SparseModel<PointType>::PointList | 
| Chris@1038 | 694 SparseModel<PointType>::getPreviousPoints(sv_frame_t originFrame) const | 
| Chris@147 | 695 { | 
| Chris@147 | 696     QMutexLocker locker(&m_mutex); | 
| Chris@147 | 697 | 
| Chris@147 | 698     PointType lookupPoint(originFrame); | 
| Chris@147 | 699     PointList rv; | 
| Chris@147 | 700 | 
| Chris@608 | 701     PointListConstIterator i = m_points.lower_bound(lookupPoint); | 
| Chris@147 | 702     if (i == m_points.begin()) return rv; | 
| Chris@147 | 703 | 
| Chris@147 | 704     --i; | 
| Chris@1038 | 705     sv_frame_t frame = i->frame; | 
| Chris@147 | 706     while (i->frame == frame) { | 
| Chris@1429 | 707         rv.insert(*i); | 
| Chris@1429 | 708         if (i == m_points.begin()) break; | 
| Chris@1429 | 709         --i; | 
| Chris@147 | 710     } | 
| Chris@147 | 711 | 
| Chris@147 | 712     return rv; | 
| Chris@147 | 713 } | 
| Chris@147 | 714 | 
| Chris@147 | 715 template <typename PointType> | 
| Chris@147 | 716 typename SparseModel<PointType>::PointList | 
| Chris@1038 | 717 SparseModel<PointType>::getNextPoints(sv_frame_t originFrame) const | 
| Chris@147 | 718 { | 
| Chris@147 | 719     QMutexLocker locker(&m_mutex); | 
| Chris@147 | 720 | 
| Chris@147 | 721     PointType lookupPoint(originFrame); | 
| Chris@147 | 722     PointList rv; | 
| Chris@147 | 723 | 
| Chris@608 | 724     PointListConstIterator i = m_points.upper_bound(lookupPoint); | 
| Chris@147 | 725     if (i == m_points.end()) return rv; | 
| Chris@147 | 726 | 
| Chris@1038 | 727     sv_frame_t frame = i->frame; | 
| Chris@147 | 728     while (i != m_points.end() && i->frame == frame) { | 
| Chris@1429 | 729         rv.insert(*i); | 
| Chris@1429 | 730         ++i; | 
| Chris@147 | 731     } | 
| Chris@147 | 732 | 
| Chris@147 | 733     return rv; | 
| Chris@147 | 734 } | 
| Chris@147 | 735 | 
| Chris@147 | 736 template <typename PointType> | 
| Chris@147 | 737 void | 
| Chris@929 | 738 SparseModel<PointType>::setResolution(int resolution) | 
| Chris@147 | 739 { | 
| Chris@147 | 740     { | 
| Chris@1429 | 741         QMutexLocker locker(&m_mutex); | 
| Chris@1429 | 742         m_resolution = resolution; | 
| Chris@1212 | 743         m_rows.clear(); | 
| Chris@147 | 744     } | 
| Chris@147 | 745     emit modelChanged(); | 
| Chris@147 | 746 } | 
| Chris@147 | 747 | 
| Chris@147 | 748 template <typename PointType> | 
| Chris@147 | 749 void | 
| Chris@147 | 750 SparseModel<PointType>::clear() | 
| Chris@147 | 751 { | 
| Chris@147 | 752     { | 
| Chris@1429 | 753         QMutexLocker locker(&m_mutex); | 
| Chris@1429 | 754         m_points.clear(); | 
| Chris@147 | 755         m_pointCount = 0; | 
| Chris@1212 | 756         m_rows.clear(); | 
| Chris@147 | 757     } | 
| Chris@147 | 758     emit modelChanged(); | 
| Chris@147 | 759 } | 
| Chris@147 | 760 | 
| Chris@147 | 761 template <typename PointType> | 
| Chris@147 | 762 void | 
| Chris@147 | 763 SparseModel<PointType>::addPoint(const PointType &point) | 
| Chris@147 | 764 { | 
| Chris@1456 | 765     { | 
| Chris@1456 | 766         QMutexLocker locker(&m_mutex); | 
| Chris@1212 | 767 | 
| Chris@1456 | 768         m_points.insert(point); | 
| Chris@1456 | 769         m_pointCount++; | 
| Chris@1456 | 770         if (point.getLabel() != "") m_hasTextLabels = true; | 
| Chris@147 | 771 | 
| Chris@1456 | 772         // Even though this model is nominally sparse, there may still | 
| Chris@1456 | 773         // be too many signals going on here (especially as they'll | 
| Chris@1456 | 774         // probably be queued from one thread to another), which is | 
| Chris@1456 | 775         // why we need the notifyOnAdd as an option rather than a | 
| Chris@1456 | 776         // necessity (the alternative is to notify on setCompletion). | 
| Chris@1456 | 777 | 
| Chris@1456 | 778         if (m_notifyOnAdd) { | 
| Chris@1456 | 779             m_rows.clear(); //!!! inefficient | 
| Chris@1456 | 780         } else { | 
| Chris@1456 | 781             if (m_sinceLastNotifyMin == -1 || | 
| Chris@1456 | 782                 point.frame < m_sinceLastNotifyMin) { | 
| Chris@1456 | 783                 m_sinceLastNotifyMin = point.frame; | 
| Chris@1456 | 784             } | 
| Chris@1456 | 785             if (m_sinceLastNotifyMax == -1 || | 
| Chris@1456 | 786                 point.frame > m_sinceLastNotifyMax) { | 
| Chris@1456 | 787                 m_sinceLastNotifyMax = point.frame; | 
| Chris@1456 | 788             } | 
| Chris@1456 | 789         } | 
| Chris@1456 | 790     } | 
| Chris@147 | 791 | 
| Chris@147 | 792     if (m_notifyOnAdd) { | 
| Chris@1429 | 793         emit modelChangedWithin(point.frame, point.frame + m_resolution); | 
| Chris@147 | 794     } | 
| Chris@147 | 795 } | 
| Chris@147 | 796 | 
| Chris@147 | 797 template <typename PointType> | 
| Chris@1113 | 798 bool | 
| Chris@1113 | 799 SparseModel<PointType>::containsPoint(const PointType &point) | 
| Chris@1113 | 800 { | 
| Chris@1212 | 801     QMutexLocker locker(&m_mutex); | 
| Chris@1113 | 802 | 
| Chris@1212 | 803     PointListIterator i = m_points.lower_bound(point); | 
| Chris@1212 | 804     typename PointType::Comparator comparator; | 
| Chris@1212 | 805     while (i != m_points.end()) { | 
| Chris@1212 | 806         if (i->frame > point.frame) break; | 
| Chris@1212 | 807         if (!comparator(*i, point) && !comparator(point, *i)) { | 
| Chris@1212 | 808             return true; | 
| Chris@1212 | 809         } | 
| Chris@1212 | 810         ++i; | 
| Chris@1113 | 811     } | 
| Chris@1113 | 812 | 
| Chris@1113 | 813     return false; | 
| Chris@1113 | 814 } | 
| Chris@1113 | 815 | 
| Chris@1113 | 816 template <typename PointType> | 
| Chris@147 | 817 void | 
| Chris@147 | 818 SparseModel<PointType>::deletePoint(const PointType &point) | 
| Chris@147 | 819 { | 
| Chris@1456 | 820     { | 
| Chris@1456 | 821         QMutexLocker locker(&m_mutex); | 
| Chris@147 | 822 | 
| Chris@1456 | 823         PointListIterator i = m_points.lower_bound(point); | 
| Chris@1456 | 824         typename PointType::Comparator comparator; | 
| Chris@1456 | 825         while (i != m_points.end()) { | 
| Chris@1456 | 826             if (i->frame > point.frame) break; | 
| Chris@1456 | 827             if (!comparator(*i, point) && !comparator(point, *i)) { | 
| Chris@1456 | 828                 m_points.erase(i); | 
| Chris@1456 | 829                 m_pointCount--; | 
| Chris@1456 | 830                 break; | 
| Chris@1429 | 831             } | 
| Chris@1456 | 832             ++i; | 
| Chris@1456 | 833         } | 
| Chris@1212 | 834 | 
| Chris@147 | 835 //    std::cout << "SparseOneDimensionalModel: emit modelChanged(" | 
| Chris@1429 | 836 //              << point.frame << ")" << std::endl; | 
| Chris@1456 | 837         m_rows.clear(); //!!! inefficient | 
| Chris@1456 | 838     } | 
| Chris@1456 | 839 | 
| Chris@931 | 840     emit modelChangedWithin(point.frame, point.frame + m_resolution); | 
| Chris@147 | 841 } | 
| Chris@147 | 842 | 
| Chris@147 | 843 template <typename PointType> | 
| Chris@147 | 844 void | 
| Chris@333 | 845 SparseModel<PointType>::setCompletion(int completion, bool update) | 
| Chris@147 | 846 { | 
| Chris@301 | 847 //    std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; | 
| Chris@1456 | 848     bool emitCompletionChanged = true; | 
| Chris@1456 | 849     bool emitGeneralModelChanged = false; | 
| Chris@1456 | 850     bool emitRegionChanged = false; | 
| Chris@191 | 851 | 
| Chris@1456 | 852     { | 
| Chris@1456 | 853         QMutexLocker locker(&m_mutex); | 
| Chris@1212 | 854 | 
| Chris@1456 | 855         if (m_completion != completion) { | 
| Chris@1456 | 856             m_completion = completion; | 
| Chris@147 | 857 | 
| Chris@1456 | 858             if (completion == 100) { | 
| Chris@147 | 859 | 
| Chris@1456 | 860                 if (m_notifyOnAdd) { | 
| Chris@1456 | 861                     emitCompletionChanged = false; | 
| Chris@1456 | 862                 } | 
| Chris@1456 | 863 | 
| Chris@1456 | 864                 m_notifyOnAdd = true; // henceforth | 
| Chris@1456 | 865                 m_rows.clear(); //!!! inefficient | 
| Chris@1456 | 866                 emitGeneralModelChanged = true; | 
| Chris@1456 | 867 | 
| Chris@1456 | 868             } else if (!m_notifyOnAdd) { | 
| Chris@1456 | 869 | 
| Chris@1456 | 870                 if (update && | 
| Chris@1456 | 871                     m_sinceLastNotifyMin >= 0 && | 
| Chris@1456 | 872                     m_sinceLastNotifyMax >= 0) { | 
| Chris@1456 | 873                     m_rows.clear(); //!!! inefficient | 
| Chris@1456 | 874                     emitRegionChanged = true; | 
| Chris@1456 | 875                 } | 
| Chris@297 | 876             } | 
| Chris@1456 | 877         } | 
| Chris@1456 | 878     } | 
| Chris@297 | 879 | 
| Chris@1456 | 880     if (emitCompletionChanged) { | 
| Chris@1456 | 881         emit completionChanged(); | 
| Chris@1456 | 882     } | 
| Chris@1456 | 883     if (emitGeneralModelChanged) { | 
| Chris@1456 | 884         emit modelChanged(); | 
| Chris@1456 | 885     } | 
| Chris@1456 | 886     if (emitRegionChanged) { | 
| Chris@1456 | 887         emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax); | 
| Chris@1456 | 888         m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; | 
| Chris@147 | 889     } | 
| Chris@147 | 890 } | 
| Chris@147 | 891 | 
| Chris@147 | 892 template <typename PointType> | 
| Chris@147 | 893 void | 
| Chris@147 | 894 SparseModel<PointType>::toXml(QTextStream &out, | 
| Chris@147 | 895                               QString indent, | 
| Chris@147 | 896                               QString extraAttributes) const | 
| Chris@147 | 897 { | 
| Chris@777 | 898 //    std::cerr << "SparseModel::toXml: extraAttributes = \"" | 
| Chris@777 | 899 //              << extraAttributes.toStdString() << std::endl; | 
| Chris@318 | 900 | 
| Chris@407 | 901     QString type = getXmlOutputType(); | 
| Chris@407 | 902 | 
| Chris@147 | 903     Model::toXml | 
| Chris@1429 | 904         (out, | 
| Chris@147 | 905          indent, | 
| Chris@1429 | 906          QString("type=\"%1\" dimensions=\"%2\" resolution=\"%3\" notifyOnAdd=\"%4\" dataset=\"%5\" %6") | 
| Chris@407 | 907          .arg(type) | 
| Chris@1429 | 908          .arg(PointType(0).getDimensions()) | 
| Chris@1429 | 909          .arg(m_resolution) | 
| Chris@1429 | 910          .arg(m_notifyOnAdd ? "true" : "false") | 
| Chris@1429 | 911          .arg(getObjectExportId(&m_points)) | 
| Chris@1429 | 912          .arg(extraAttributes)); | 
| Chris@147 | 913 | 
| Chris@147 | 914     out << indent; | 
| Chris@147 | 915     out << QString("<dataset id=\"%1\" dimensions=\"%2\">\n") | 
| Chris@1429 | 916         .arg(getObjectExportId(&m_points)) | 
| Chris@1429 | 917         .arg(PointType(0).getDimensions()); | 
| Chris@147 | 918 | 
| Chris@608 | 919     for (PointListConstIterator i = m_points.begin(); i != m_points.end(); ++i) { | 
| Chris@314 | 920         i->toXml(out, indent + "  "); | 
| Chris@147 | 921     } | 
| Chris@147 | 922 | 
| Chris@147 | 923     out << indent; | 
| Chris@147 | 924     out << "</dataset>\n"; | 
| Chris@147 | 925 } | 
| Chris@147 | 926 | 
| Chris@147 | 927 template <typename PointType> | 
| Chris@147 | 928 SparseModel<PointType>::EditCommand::EditCommand(SparseModel *model, | 
| Chris@147 | 929                                                  QString commandName) : | 
| Chris@147 | 930     MacroCommand(commandName), | 
| Chris@147 | 931     m_model(model) | 
| Chris@147 | 932 { | 
| Chris@147 | 933 } | 
| Chris@147 | 934 | 
| Chris@147 | 935 template <typename PointType> | 
| Chris@147 | 936 void | 
| Chris@147 | 937 SparseModel<PointType>::EditCommand::addPoint(const PointType &point) | 
| Chris@147 | 938 { | 
| Chris@147 | 939     addCommand(new AddPointCommand(m_model, point), true); | 
| Chris@147 | 940 } | 
| Chris@147 | 941 | 
| Chris@147 | 942 template <typename PointType> | 
| Chris@147 | 943 void | 
| Chris@147 | 944 SparseModel<PointType>::EditCommand::deletePoint(const PointType &point) | 
| Chris@147 | 945 { | 
| Chris@147 | 946     addCommand(new DeletePointCommand(m_model, point), true); | 
| Chris@147 | 947 } | 
| Chris@147 | 948 | 
| Chris@147 | 949 template <typename PointType> | 
| Chris@416 | 950 typename SparseModel<PointType>::EditCommand * | 
| Chris@147 | 951 SparseModel<PointType>::EditCommand::finish() | 
| Chris@147 | 952 { | 
| Chris@147 | 953     if (!m_commands.empty()) { | 
| Chris@387 | 954         return this; | 
| Chris@147 | 955     } else { | 
| Chris@147 | 956         delete this; | 
| Chris@389 | 957         return 0; | 
| Chris@147 | 958     } | 
| Chris@147 | 959 } | 
| Chris@147 | 960 | 
| Chris@147 | 961 template <typename PointType> | 
| Chris@147 | 962 void | 
| Chris@147 | 963 SparseModel<PointType>::EditCommand::addCommand(Command *command, | 
| Chris@1429 | 964                                                 bool executeFirst) | 
| Chris@147 | 965 { | 
| Chris@147 | 966     if (executeFirst) command->execute(); | 
| Chris@147 | 967 | 
| Chris@147 | 968     if (!m_commands.empty()) { | 
| Chris@1429 | 969         DeletePointCommand *dpc = dynamic_cast<DeletePointCommand *>(command); | 
| Chris@1429 | 970         if (dpc) { | 
| Chris@1429 | 971             AddPointCommand *apc = dynamic_cast<AddPointCommand *> | 
| Chris@1429 | 972                 (m_commands[m_commands.size() - 1]); | 
| Chris@1429 | 973             typename PointType::Comparator comparator; | 
| Chris@1429 | 974             if (apc) { | 
| Chris@1429 | 975                 if (!comparator(apc->getPoint(), dpc->getPoint()) && | 
| Chris@1429 | 976                     !comparator(dpc->getPoint(), apc->getPoint())) { | 
| Chris@1429 | 977                     deleteCommand(apc); | 
| Chris@1429 | 978                     return; | 
| Chris@1429 | 979                 } | 
| Chris@1429 | 980             } | 
| Chris@1429 | 981         } | 
| Chris@147 | 982     } | 
| Chris@147 | 983 | 
| Chris@147 | 984     MacroCommand::addCommand(command); | 
| Chris@147 | 985 } | 
| Chris@147 | 986 | 
| Chris@147 | 987 | 
| Chris@147 | 988 #endif | 
| Chris@147 | 989 | 
| Chris@147 | 990 | 
| Chris@147 | 991 |