| 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 | 
| Chris@147 | 8     This program is free software; you can redistribute it and/or | 
| Chris@147 | 9     modify it under the terms of the GNU General Public License as | 
| Chris@147 | 10     published by the Free Software Foundation; either version 2 of the | 
| Chris@147 | 11     License, or (at your option) any later version.  See the file | 
| Chris@147 | 12     COPYING included with this distribution for more information. | 
| Chris@147 | 13 */ | 
| Chris@147 | 14 | 
| Chris@1581 | 15 #ifndef SV_TEXT_MODEL_H | 
| Chris@1581 | 16 #define SV_TEXT_MODEL_H | 
| Chris@147 | 17 | 
| Chris@1661 | 18 #include "EventCommands.h" | 
| Chris@1661 | 19 #include "TabularModel.h" | 
| Chris@1661 | 20 #include "Model.h" | 
| Chris@1661 | 21 #include "DeferredNotifier.h" | 
| Chris@1661 | 22 | 
| Chris@1661 | 23 #include "base/EventSeries.h" | 
| Chris@302 | 24 #include "base/XmlExportable.h" | 
| Chris@147 | 25 #include "base/RealTime.h" | 
| Chris@147 | 26 | 
| Chris@1661 | 27 #include "system/System.h" | 
| Chris@1661 | 28 | 
| Chris@423 | 29 #include <QStringList> | 
| Chris@423 | 30 | 
| Chris@147 | 31 /** | 
| Chris@1661 | 32  * A model representing casual textual annotations. A piece of text | 
| Chris@1661 | 33  * has a given time and y-value in the [0,1) range (indicative of | 
| Chris@1661 | 34  * height on the window). | 
| Chris@147 | 35  */ | 
| Chris@1661 | 36 class TextModel : public Model, | 
| Chris@1661 | 37                   public TabularModel, | 
| Chris@1661 | 38                   public EventEditable | 
| Chris@147 | 39 { | 
| Chris@423 | 40     Q_OBJECT | 
| Chris@423 | 41 | 
| Chris@147 | 42 public: | 
| Chris@1661 | 43     TextModel(sv_samplerate_t sampleRate, | 
| Chris@1661 | 44               int resolution, | 
| Chris@1661 | 45               bool notifyOnAdd = true) : | 
| Chris@1661 | 46         m_sampleRate(sampleRate), | 
| Chris@1661 | 47         m_resolution(resolution), | 
| Chris@1661 | 48         m_notifier(this, | 
| Chris@1661 | 49                    notifyOnAdd ? | 
| Chris@1661 | 50                    DeferredNotifier::NOTIFY_ALWAYS : | 
| Chris@1661 | 51                    DeferredNotifier::NOTIFY_DEFERRED), | 
| Chris@1661 | 52         m_completion(100) { | 
| Chris@147 | 53     } | 
| Chris@345 | 54 | 
| Chris@1580 | 55     QString getTypeName() const override { return tr("Text"); } | 
| cannam@1695 | 56     bool isSparse() const override { return true; } | 
| Chris@1661 | 57     bool isOK() const override { return true; } | 
| Chris@1661 | 58 | 
| Chris@1661 | 59     sv_frame_t getStartFrame() const override { | 
| Chris@1661 | 60         return m_events.getStartFrame(); | 
| Chris@1661 | 61     } | 
| Chris@1661 | 62     sv_frame_t getEndFrame() const override { | 
| Chris@1661 | 63         if (m_events.isEmpty()) return 0; | 
| Chris@1661 | 64         sv_frame_t e = m_events.getEndFrame() + 1; | 
| Chris@1661 | 65         if (e % m_resolution == 0) return e; | 
| Chris@1661 | 66         else return (e / m_resolution + 1) * m_resolution; | 
| Chris@1661 | 67     } | 
| Chris@1661 | 68 | 
| Chris@1661 | 69     sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | 
| Chris@1661 | 70     int getResolution() const { return m_resolution; } | 
| Chris@1661 | 71 | 
| Chris@1671 | 72     int getCompletion() const override { return m_completion; } | 
| Chris@1661 | 73 | 
| Chris@1661 | 74     void setCompletion(int completion, bool update = true) { | 
| Chris@1661 | 75 | 
| Chris@1661 | 76         {   QMutexLocker locker(&m_mutex); | 
| Chris@1661 | 77             if (m_completion == completion) return; | 
| Chris@1661 | 78             m_completion = completion; | 
| Chris@1661 | 79         } | 
| Chris@1661 | 80 | 
| Chris@1661 | 81         if (update) { | 
| Chris@1661 | 82             m_notifier.makeDeferredNotifications(); | 
| Chris@1661 | 83         } | 
| Chris@1661 | 84 | 
| Chris@1661 | 85         emit completionChanged(); | 
| Chris@1661 | 86 | 
| Chris@1661 | 87         if (completion == 100) { | 
| Chris@1661 | 88             // henceforth: | 
| Chris@1661 | 89             m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); | 
| Chris@1661 | 90             emit modelChanged(); | 
| Chris@1661 | 91         } | 
| Chris@1661 | 92     } | 
| Chris@1661 | 93 | 
| Chris@1661 | 94     /** | 
| Chris@1661 | 95      * Query methods. | 
| Chris@1661 | 96      */ | 
| Chris@1661 | 97 | 
| Chris@1661 | 98     int getEventCount() const { | 
| Chris@1661 | 99         return m_events.count(); | 
| Chris@1661 | 100     } | 
| Chris@1661 | 101     bool isEmpty() const { | 
| Chris@1661 | 102         return m_events.isEmpty(); | 
| Chris@1661 | 103     } | 
| Chris@1661 | 104     bool containsEvent(const Event &e) const { | 
| Chris@1661 | 105         return m_events.contains(e); | 
| Chris@1661 | 106     } | 
| Chris@1661 | 107     EventVector getAllEvents() const { | 
| Chris@1661 | 108         return m_events.getAllEvents(); | 
| Chris@1661 | 109     } | 
| Chris@1661 | 110     EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1661 | 111         return m_events.getEventsSpanning(f, duration); | 
| Chris@1661 | 112     } | 
| Chris@1661 | 113     EventVector getEventsCovering(sv_frame_t f) const { | 
| Chris@1661 | 114         return m_events.getEventsCovering(f); | 
| Chris@1661 | 115     } | 
| Chris@1661 | 116     EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration, | 
| Chris@1661 | 117                                 int overspill = 0) const { | 
| Chris@1661 | 118         return m_events.getEventsWithin(f, duration, overspill); | 
| Chris@1661 | 119     } | 
| Chris@1661 | 120     EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1661 | 121         return m_events.getEventsStartingWithin(f, duration); | 
| Chris@1661 | 122     } | 
| Chris@1661 | 123     EventVector getEventsStartingAt(sv_frame_t f) const { | 
| Chris@1661 | 124         return m_events.getEventsStartingAt(f); | 
| Chris@1661 | 125     } | 
| Chris@1661 | 126     bool getNearestEventMatching(sv_frame_t startSearchAt, | 
| Chris@1661 | 127                                  std::function<bool(Event)> predicate, | 
| Chris@1661 | 128                                  EventSeries::Direction direction, | 
| Chris@1661 | 129                                  Event &found) const { | 
| Chris@1661 | 130         return m_events.getNearestEventMatching | 
| Chris@1661 | 131             (startSearchAt, predicate, direction, found); | 
| Chris@1661 | 132     } | 
| Chris@1661 | 133 | 
| Chris@1661 | 134     /** | 
| Chris@1661 | 135      * Editing methods. | 
| Chris@1661 | 136      */ | 
| Chris@1661 | 137     void add(Event e) override { | 
| Chris@1661 | 138 | 
| Chris@1661 | 139         {   QMutexLocker locker(&m_mutex); | 
| Chris@1674 | 140             m_events.add(e.withoutDuration().withoutLevel()); | 
| Chris@1661 | 141         } | 
| Chris@1661 | 142 | 
| Chris@1661 | 143         m_notifier.update(e.getFrame(), m_resolution); | 
| Chris@1661 | 144     } | 
| Chris@1661 | 145 | 
| Chris@1661 | 146     void remove(Event e) override { | 
| Chris@1661 | 147         {   QMutexLocker locker(&m_mutex); | 
| Chris@1661 | 148             m_events.remove(e); | 
| Chris@1661 | 149         } | 
| Chris@1661 | 150         emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); | 
| Chris@1661 | 151     } | 
| Chris@424 | 152 | 
| Chris@424 | 153     /** | 
| Chris@424 | 154      * TabularModel methods. | 
| Chris@424 | 155      */ | 
| Chris@424 | 156 | 
| Chris@1661 | 157     int getRowCount() const override { | 
| Chris@1661 | 158         return m_events.count(); | 
| Chris@1661 | 159     } | 
| Chris@1661 | 160 | 
| Chris@1661 | 161     int getColumnCount() const override { | 
| Chris@424 | 162         return 4; | 
| Chris@424 | 163     } | 
| Chris@424 | 164 | 
| Chris@1661 | 165     bool isColumnTimeValue(int column) const override { | 
| Chris@1661 | 166         return (column < 2); | 
| Chris@1661 | 167     } | 
| Chris@1661 | 168 | 
| Chris@1661 | 169     sv_frame_t getFrameForRow(int row) const override { | 
| Chris@1661 | 170         if (row < 0 || row >= m_events.count()) { | 
| Chris@1661 | 171             return 0; | 
| Chris@1661 | 172         } | 
| Chris@1661 | 173         Event e = m_events.getEventByIndex(row); | 
| Chris@1661 | 174         return e.getFrame(); | 
| Chris@1661 | 175     } | 
| Chris@1661 | 176 | 
| Chris@1661 | 177     int getRowForFrame(sv_frame_t frame) const override { | 
| Chris@1661 | 178         return m_events.getIndexForEvent(Event(frame)); | 
| Chris@1661 | 179     } | 
| Chris@1661 | 180 | 
| Chris@1661 | 181     QString getHeading(int column) const override { | 
| Chris@424 | 182         switch (column) { | 
| Chris@424 | 183         case 0: return tr("Time"); | 
| Chris@424 | 184         case 1: return tr("Frame"); | 
| Chris@424 | 185         case 2: return tr("Height"); | 
| Chris@424 | 186         case 3: return tr("Label"); | 
| Chris@424 | 187         default: return tr("Unknown"); | 
| Chris@424 | 188         } | 
| Chris@424 | 189     } | 
| Chris@424 | 190 | 
| Chris@1661 | 191     SortType getSortType(int column) const override { | 
| Chris@1661 | 192         if (column == 3) return SortAlphabetical; | 
| Chris@1661 | 193         return SortNumeric; | 
| Chris@1661 | 194     } | 
| Chris@1661 | 195 | 
| Chris@1661 | 196     QVariant getData(int row, int column, int role) const override { | 
| Chris@1661 | 197 | 
| Chris@1661 | 198         if (row < 0 || row >= m_events.count()) { | 
| Chris@1661 | 199             return QVariant(); | 
| Chris@425 | 200         } | 
| Chris@425 | 201 | 
| Chris@1661 | 202         Event e = m_events.getEventByIndex(row); | 
| Chris@424 | 203 | 
| Chris@424 | 204         switch (column) { | 
| Chris@1661 | 205         case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | 
| Chris@1661 | 206         case 1: return int(e.getFrame()); | 
| Chris@1661 | 207         case 2: return e.getValue(); | 
| Chris@1661 | 208         case 3: return e.getLabel(); | 
| Chris@424 | 209         default: return QVariant(); | 
| Chris@424 | 210         } | 
| Chris@424 | 211     } | 
| Chris@424 | 212 | 
| Chris@1661 | 213     Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { | 
| Chris@1661 | 214 | 
| Chris@1661 | 215         if (row < 0 || row >= m_events.count()) return nullptr; | 
| Chris@1661 | 216         if (role != Qt::EditRole) return nullptr; | 
| Chris@1661 | 217 | 
| Chris@1661 | 218         Event e0 = m_events.getEventByIndex(row); | 
| Chris@1661 | 219         Event e1; | 
| Chris@1661 | 220 | 
| Chris@1661 | 221         switch (column) { | 
| Chris@1661 | 222         case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * | 
| Chris@1661 | 223                                                    getSampleRate()))); break; | 
| Chris@1661 | 224         case 1: e1 = e0.withFrame(value.toInt()); break; | 
| Chris@1661 | 225         case 2: e1 = e0.withValue(float(value.toDouble())); break; | 
| Chris@1661 | 226         case 3: e1 = e0.withLabel(value.toString()); break; | 
| Chris@425 | 227         } | 
| Chris@425 | 228 | 
| Chris@1661 | 229         ChangeEventsCommand *command = | 
| Chris@1661 | 230             new ChangeEventsCommand(this, tr("Edit Data")); | 
| Chris@1661 | 231         command->remove(e0); | 
| Chris@1661 | 232         command->add(e1); | 
| Chris@424 | 233         return command->finish(); | 
| Chris@424 | 234     } | 
| Chris@1661 | 235 | 
| Chris@1661 | 236     /** | 
| Chris@1661 | 237      * XmlExportable methods. | 
| Chris@1661 | 238      */ | 
| Chris@1661 | 239     void toXml(QTextStream &out, | 
| Chris@1661 | 240                QString indent = "", | 
| Chris@1661 | 241                QString extraAttributes = "") const override { | 
| Chris@424 | 242 | 
| Chris@1661 | 243         Model::toXml | 
| Chris@1661 | 244             (out, | 
| Chris@1661 | 245              indent, | 
| Chris@1661 | 246              QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" " | 
| Chris@1661 | 247                      "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"text\" %4") | 
| Chris@1661 | 248              .arg(m_resolution) | 
| Chris@1661 | 249              .arg("true") // always true after model reaches 100% - | 
| Chris@1661 | 250                           // subsequent events are always notified | 
| Chris@1677 | 251              .arg(m_events.getExportId()) | 
| Chris@1661 | 252              .arg(extraAttributes)); | 
| Chris@1674 | 253 | 
| Chris@1674 | 254         Event::ExportNameOptions options; | 
| Chris@1674 | 255         options.valueAtttributeName = "height"; | 
| Chris@1661 | 256 | 
| Chris@1674 | 257         m_events.toXml(out, indent, QString("dimensions=\"2\""), options); | 
| Chris@424 | 258     } | 
| Chris@1679 | 259 | 
| Chris@1679 | 260     QString toDelimitedDataString(QString delimiter, | 
| Chris@1679 | 261                                   DataExportOptions options, | 
| Chris@1679 | 262                                   sv_frame_t startFrame, | 
| Chris@1679 | 263                                   sv_frame_t duration) const override { | 
| Chris@1679 | 264         return m_events.toDelimitedDataString(delimiter, | 
| Chris@1679 | 265                                               options, | 
| Chris@1679 | 266                                               startFrame, | 
| Chris@1679 | 267                                               duration, | 
| Chris@1679 | 268                                               m_sampleRate, | 
| Chris@1679 | 269                                               m_resolution, | 
| Chris@1679 | 270                                               Event().withValue(0.f)); | 
| Chris@1679 | 271     } | 
| Chris@1661 | 272 | 
| Chris@1661 | 273 protected: | 
| Chris@1661 | 274     sv_samplerate_t m_sampleRate; | 
| Chris@1661 | 275     int m_resolution; | 
| Chris@424 | 276 | 
| Chris@1661 | 277     DeferredNotifier m_notifier; | 
| Chris@1661 | 278     int m_completion; | 
| Chris@1661 | 279 | 
| Chris@1661 | 280     EventSeries m_events; | 
| Chris@1661 | 281 | 
| Chris@1661 | 282     mutable QMutex m_mutex; | 
| Chris@424 | 283 | 
| Chris@147 | 284 }; | 
| Chris@147 | 285 | 
| Chris@147 | 286 | 
| Chris@147 | 287 #endif | 
| Chris@147 | 288 | 
| Chris@147 | 289 | 
| Chris@147 | 290 |