Mercurial > hg > svcore
changeset 420:50a956688baa
* reorganise tabular data editor model support
author | Chris Cannam |
---|---|
date | Wed, 11 Jun 2008 16:13:25 +0000 |
parents | 64e7bbb255d3 |
children | 397fe91dc8e0 |
files | data/data.pro data/model/ModelDataTableModel.cpp data/model/ModelDataTableModel.h data/model/SparseModel.h data/model/SparseOneDimensionalModel.h data/model/SparseTimeValueModel.h data/model/TabularModel.h |
diffstat | 7 files changed, 352 insertions(+), 281 deletions(-) [+] |
line wrap: on
line diff
--- a/data/data.pro Wed Jun 11 12:54:18 2008 +0000 +++ b/data/data.pro Wed Jun 11 16:13:25 2008 +0000 @@ -62,6 +62,7 @@ model/SparseOneDimensionalModel.h \ model/SparseTimeValueModel.h \ model/SparseValueModel.h \ + model/TabularModel.h \ model/TextModel.h \ model/WaveFileModel.h \ model/WritableWaveFileModel.h \
--- a/data/model/ModelDataTableModel.cpp Wed Jun 11 12:54:18 2008 +0000 +++ b/data/model/ModelDataTableModel.cpp Wed Jun 11 16:13:25 2008 +0000 @@ -15,19 +15,20 @@ #include "ModelDataTableModel.h" -#include "SparseTimeValueModel.h" -#include "SparseOneDimensionalModel.h" -#include "SparseModel.h" +#include "TabularModel.h" +#include "Model.h" #include <algorithm> +#include <iostream> -ModelDataTableModel::ModelDataTableModel(Model *m) : +ModelDataTableModel::ModelDataTableModel(TabularModel *m) : m_model(m) { - connect(m, SIGNAL(modelChanged()), this, SLOT(modelChanged())); - connect(m, SIGNAL(modelChanged(size_t, size_t)), + Model *baseModel = dynamic_cast<Model *>(m); + + connect(baseModel, SIGNAL(modelChanged()), this, SLOT(modelChanged())); + connect(baseModel, SIGNAL(modelChanged(size_t, size_t)), this, SLOT(modelChanged(size_t, size_t))); - rebuildRowVector(); } ModelDataTableModel::~ModelDataTableModel() @@ -37,191 +38,22 @@ QVariant ModelDataTableModel::data(const QModelIndex &index, int role) const { - if (role != Qt::DisplayRole && role != Qt::EditRole) { - return QVariant(); - } - - bool withUnit = (role == Qt::DisplayRole); - if (!index.isValid()) return QVariant(); - - int row = index.row(), col = index.column(); - - if (row < 0 || row >= m_rows.size()) return QVariant(); - - if (dynamic_cast<const SparseOneDimensionalModel *>(m_model)) { - return dataSparse<SparseOneDimensionalModel::Point>(row, col, withUnit); - } else if (dynamic_cast<const SparseTimeValueModel *>(m_model)) { - return dataSparse<SparseTimeValueModel::Point>(row, col, withUnit); - } - - return QVariant(); -} - -template <typename PointType> -QVariant -ModelDataTableModel::dataSparse(int row, int col, bool withUnit) const -{ - size_t frame = m_rows[row]; - - // This is just garbage. This would be a reasonable enough way to - // handle this in a dynamically typed language but it's hopeless - // in C++. The design is simply wrong. We need virtual helper - // methods in the model itself. - - typedef SparseModel<PointType> ModelType; - typedef std::multiset<PointType, typename PointType::OrderComparator> - PointListType; - - const ModelType *sm = dynamic_cast<const ModelType *>(m_model); - const PointListType &points = sm->getPoints(frame); - - // it is possible to have more than one point at the same frame - - int indexAtFrame = 0; - int ri = row; - while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; } - - for (typename PointListType::const_iterator i = points.begin(); - i != points.end(); ++i) { - - const PointType *point = &(*i); - if (point->frame < frame) continue; - if (point->frame > frame) return QVariant(); - if (indexAtFrame > 0) { --indexAtFrame; continue; } - - switch (col) { - - case 0: - { - RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate()); - std::cerr << "Returning time " << rt << std::endl; - return QVariant(rt.toText().c_str()); - } - - case 1: - std::cerr << "Returning frame " << frame << std::endl; - return QVariant(int(frame)); - - case 2: - if (dynamic_cast<const SparseOneDimensionalModel *>(m_model)) { - const SparseOneDimensionalModel::Point *cp = - reinterpret_cast<const SparseOneDimensionalModel::Point *>(point); - std::cerr << "Returning label \"" << cp->label.toStdString() << "\"" << std::endl; - return QVariant(cp->label); - } else if (dynamic_cast<const SparseTimeValueModel *>(m_model)) { - const SparseTimeValueModel::Point *cp = - reinterpret_cast<const SparseTimeValueModel::Point *>(point); - std::cerr << "Returning value " << cp->value << std::endl; - if (withUnit) { - return QVariant(QString("%1 %2").arg(cp->value) - .arg(dynamic_cast<const SparseTimeValueModel *>(m_model)->getScaleUnits())); - } else { - return cp->value; - } - } else return QVariant(); - - case 3: - if (dynamic_cast<const SparseOneDimensionalModel *>(m_model)) { - return QVariant(); - } else if (dynamic_cast<const SparseTimeValueModel *>(m_model)) { - return reinterpret_cast<const SparseTimeValueModel::Point *>(point)->label; - } else return QVariant(); - } - } - - return QVariant(); + return m_model->getData(getUnsorted(index.row()), index.column(), role); } bool ModelDataTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (role != Qt::EditRole) { - std::cerr << "setData: ignoring role " << role << std::endl; + if (!index.isValid()) return false; + Command *command = m_model->setData(getUnsorted(index.row()), + index.column(), value, role); + if (command) { + emit executeCommand(command); + return true; + } else { return false; } - - //!!! see comment about disgustuality of this whole process, in - //dataSparse above - - if (!index.isValid()) return false; - - int row = index.row(), col = index.column(); - - if (row < 0 || row >= m_rows.size()) return false; - - if (dynamic_cast<const SparseOneDimensionalModel *>(m_model)) { - return setDataSparse<SparseOneDimensionalModel::Point>(row, col, value); - } else if (dynamic_cast<const SparseTimeValueModel *>(m_model)) { - return setDataSparse<SparseTimeValueModel::Point>(row, col, value); - } - - return false; -} - -template <typename PointType> -bool -ModelDataTableModel::setDataSparse(int row, int col, QVariant value) -{ - size_t frame = m_rows[row]; - - typedef SparseModel<PointType> ModelType; - typedef std::multiset<PointType, typename PointType::OrderComparator> - PointListType; - typedef typename ModelType::EditCommand EditCommandType; - - ModelType *sm = dynamic_cast<ModelType *>(m_model); - const PointListType &points = sm->getPoints(frame); - - // it is possible to have more than one point at the same frame - - int indexAtFrame = 0; - int ri = row; - while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; } - - for (typename PointListType::const_iterator i = points.begin(); - i != points.end(); ++i) { - - const PointType *point = &(*i); - if (point->frame < frame) continue; - if (point->frame > frame) return false; - if (indexAtFrame > 0) { --indexAtFrame; continue; } - - switch (col) { - - case 0: - { -/* - RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate()); - std::cerr << "Returning time " << rt << std::endl; - return QVariant(rt.toText().c_str()); -*/ - } - - case 1: - { - EditCommandType *command = - new EditCommandType(sm, tr("Edit point time")); - PointType newPoint(*point); - newPoint.frame = value.toInt(); //!!! check validity - command->deletePoint(*point); - command->addPoint(newPoint); - command = command->finish(); - if (command) emit executeCommand(command); - return true; - } -// std::cerr << "Returning frame " << frame << std::endl; -// return QVariant(frame); //!!! RealTime - - case 2: - break; - - case 3: - break; - } - } - - return false; } Qt::ItemFlags @@ -236,19 +68,7 @@ ModelDataTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - if (section == 0) return QVariant(tr("Time")); - if (section == 1) return QVariant(tr("Frame")); - else if (section == 2) { - if (dynamic_cast<const SparseOneDimensionalModel *>(m_model)) { - return QVariant(tr("Label")); - } else if (dynamic_cast<const SparseTimeValueModel *>(m_model)) { - return QVariant(tr("Value")); - } - } else if (section == 3) { - if (dynamic_cast<const SparseTimeValueModel *>(m_model)) { - return QVariant(tr("Label")); - } - } + return m_model->getHeading(section); } return QVariant(); } @@ -269,98 +89,94 @@ ModelDataTableModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; - return m_rows.size(); + return m_model->getRowCount(); } int ModelDataTableModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; - if (!canHandleModelType(m_model)) return 0; - - if (dynamic_cast<SparseOneDimensionalModel *>(m_model)) { - return 3; - } else if (dynamic_cast<SparseTimeValueModel *>(m_model)) { - return 4; - } - - return 2; + return m_model->getColumnCount(); } QModelIndex ModelDataTableModel::getModelIndexForFrame(size_t frame) const { - std::vector<size_t>::const_iterator i = - std::lower_bound(m_rows.begin(), m_rows.end(), frame); - size_t dist = std::distance(m_rows.begin(), i); - return createIndex(dist, 0, 0); + int row = m_model->getRowForFrame(frame); + return createIndex(getSorted(row), 0, 0); } size_t ModelDataTableModel::getFrameForModelIndex(const QModelIndex &index) const { - int row = index.row(); - if (m_rows.empty()) return 0; - if (row < 0) row == 0; - if (row > m_rows.size()-1) row = m_rows.size()-1; - return m_rows[row]; + return m_model->getFrameForRow(getUnsorted(index.row())); +} + +void +ModelDataTableModel::sort(int column, Qt::SortOrder sortOrder) +{ + std::cerr << "ModelDataTableModel::sort(" << column << ", " << sortOrder + << ")" << std::endl; + m_sortColumn = column; + m_sortOrdering = sortOrder; + m_sort.clear(); + emit layoutChanged(); } void ModelDataTableModel::modelChanged() { - rebuildRowVector(); + m_sort.clear(); emit layoutChanged(); } void ModelDataTableModel::modelChanged(size_t f0, size_t f1) { - std::cerr << "ModelDataTableModel::modelChanged(" << f0 << "," << f1 << ")" << std::endl; - //!!! highly inefficient - rebuildRowVector(); + //!!! inefficient + m_sort.clear(); emit layoutChanged(); } -void -ModelDataTableModel::rebuildRowVector() +int +ModelDataTableModel::getSorted(int row) { - if (!canHandleModelType(m_model)) return; + if (m_model->isColumnTimeValue(m_sortColumn)) { + if (m_sortOrdering == Qt::AscendingOrder) { + return row; + } else { + return rowCount() - row - 1; + } + } - m_rows.clear(); - - if (dynamic_cast<SparseOneDimensionalModel *>(m_model)) { - rebuildRowVectorSparse<SparseOneDimensionalModel::Point>(); - } else if (dynamic_cast<SparseTimeValueModel *>(m_model)) { - rebuildRowVectorSparse<SparseTimeValueModel::Point>(); + if (m_sort.empty()) { + resort(); } + if (row < 0 || row >= m_sort.size()) return 0; + return m_sort[row]; } -template <typename PointType> -void -ModelDataTableModel::rebuildRowVectorSparse() +int +ModelDataTableModel::getUnsorted(int row) { - // gah - - typedef SparseModel<PointType> ModelType; - typedef std::multiset<PointType, typename PointType::OrderComparator> - PointListType; - - ModelType *sm = dynamic_cast<ModelType *>(m_model); - const PointListType &points = sm->getPoints(); - - for (typename PointListType::const_iterator i = points.begin(); - i != points.end(); ++i) { - m_rows.push_back(i->frame); + if (m_model->isColumnTimeValue(m_sortColumn)) { + if (m_sortOrdering == Qt::AscendingOrder) { + return row; + } else { + return rowCount() - row - 1; + } } +//!!! need the reverse of this + if (m_sort.empty()) { + resort(); + } + if (row < 0 || row >= m_sort.size()) return 0; + return m_sort[row]; } -bool -ModelDataTableModel::canHandleModelType(Model *m) +void +ModelDataTableModel::resort() { - if (dynamic_cast<SparseOneDimensionalModel *>(m)) return true; - if (dynamic_cast<SparseTimeValueModel *>(m)) return true; - return false; + //... } -
--- a/data/model/ModelDataTableModel.h Wed Jun 11 12:54:18 2008 +0000 +++ b/data/model/ModelDataTableModel.h Wed Jun 11 16:13:25 2008 +0000 @@ -18,8 +18,9 @@ #include <QAbstractItemModel> -#include "Model.h" +#include <vector> +class TabularModel; class Command; class ModelDataTableModel : public QAbstractItemModel @@ -27,7 +28,7 @@ Q_OBJECT public: - ModelDataTableModel(Model *m); + ModelDataTableModel(TabularModel *m); virtual ~ModelDataTableModel(); QVariant data(const QModelIndex &index, int role) const; @@ -50,7 +51,7 @@ QModelIndex getModelIndexForFrame(size_t frame) const; size_t getFrameForModelIndex(const QModelIndex &) const; - static bool canHandleModelType(Model *); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); signals: void frameSelected(size_t); @@ -61,19 +62,14 @@ void modelChanged(size_t, size_t); protected: - // We need to have some sort of map between row and time in sample - // frames. I guess this will do for now. - - std::vector<size_t> m_rows; // contains sample frame - - Model *m_model; - - void rebuildRowVector(); - template <typename PointType> void rebuildRowVectorSparse(); - template <typename PointType> QVariant dataSparse(int row, int col, - bool withUnit) const; - template <typename PointType> bool setDataSparse(int row, int col, - QVariant value); + TabularModel *m_model; + int m_sortColumn; + Qt::SortOrder m_sortOrdering; + typedef std::vector<int> RowList; + RowList m_sort; + int getSorted(int row); + int getUnsorted(int row); + void resort(); }; #endif
--- a/data/model/SparseModel.h Wed Jun 11 12:54:18 2008 +0000 +++ b/data/model/SparseModel.h Wed Jun 11 16:13:25 2008 +0000 @@ -17,22 +17,26 @@ #define _SPARSE_MODEL_H_ #include "Model.h" +#include "TabularModel.h" #include "base/Command.h" #include <iostream> #include <set> +#include <vector> +#include <algorithm> + #include <QMutex> #include <QTextStream> - /** * Model containing sparse data (points with some properties). The * properties depend on the point type. */ template <typename PointType> -class SparseModel : public Model +class SparseModel : public Model, + public TabularModel { public: SparseModel(size_t sampleRate, size_t resolution, @@ -258,8 +262,38 @@ PointType m_newPoint; }; + /** + * TabularModel methods. + */ + + virtual int getRowCount() const + { + return m_points.size(); + } + + virtual long getFrameForRow(int row) const + { + PointListIterator i = getPointListIteratorForRow(row); + if (i == m_points.end()) return 0; + return i->frame; + } + + virtual int getRowForFrame(long frame) const + { + if (m_rows.empty()) rebuildRowVector(); + std::vector<long>::iterator i = + std::lower_bound(m_rows.begin(), m_rows.end(), frame); + return std::distance(m_rows.begin(), i); + } + + //!!! just for now + virtual int getColumnCount() const { return 1; } + virtual QString getHeading(int column) const { return tr("Unknown"); } + virtual QVariant getData(int row, int column, int role) const { + return QVariant(); + } + virtual bool isColumnTimeValue(int column) const { return true; } - protected: size_t m_sampleRate; size_t m_resolution; @@ -272,6 +306,47 @@ size_t m_pointCount; mutable QMutex m_mutex; int m_completion; + + void getPointIterators(long frame, + PointListIterator &startItr, + PointListIterator &endItr) const; + + // This is only used if the model is called on to act in + // TabularModel mode + mutable std::vector<long> m_rows; // map from row number to frame + void rebuildRowVector() const + { + m_rows.clear(); + for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) { + m_rows.push_back(i->frame); + } + } + + PointListIterator getPointListIteratorForRow(int row) const + { + if (m_rows.empty()) rebuildRowVector(); + if (row < 0 || row + 1 > m_rows.size()) return m_points.end(); + + size_t frame = m_rows[row]; + int indexAtFrame = 0; + int ri = row; + while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; } + int initialIndexAtFrame = indexAtFrame; + + PointListIterator i0, i1; + getPointIterators(frame, i0, i1); + PointListIterator i = i0; + + for (i = i0; i != i1; ++i) { + if (indexAtFrame > 0) { --indexAtFrame; continue; } + return i; + } + + if (indexAtFrame > 0) { + std::cerr << "WARNING: SparseModel::getPointListIteratorForRow: No iterator available for row " << row << " (frame = " << frame << ", index at frame = " << initialIndexAtFrame << ", leftover index " << indexAtFrame << ")" << std::endl; + } + return i; + } }; @@ -370,17 +445,8 @@ typename SparseModel<PointType>::PointList SparseModel<PointType>::getPoints(long frame) const { - QMutexLocker locker(&m_mutex); - - if (m_resolution == 0) return PointList(); - - long start = (frame / m_resolution) * m_resolution; - long end = start + m_resolution; - - PointType startPoint(start), endPoint(end); - - PointListIterator startItr = m_points.lower_bound(startPoint); - PointListIterator endItr = m_points.upper_bound(endPoint); + PointListIterator startItr, endItr; + getPointIterators(frame, startItr, endItr); PointList rv; @@ -392,6 +458,29 @@ } template <typename PointType> +void +SparseModel<PointType>::getPointIterators(long frame, + PointListIterator &startItr, + PointListIterator &endItr) const +{ + QMutexLocker locker(&m_mutex); + + if (m_resolution == 0) { + startItr = m_points.end(); + endItr = m_points.end(); + return; + } + + long start = (frame / m_resolution) * m_resolution; + long end = start + m_resolution; + + PointType startPoint(start), endPoint(end); + + startItr = m_points.lower_bound(startPoint); + endItr = m_points.upper_bound(endPoint); +} + +template <typename PointType> typename SparseModel<PointType>::PointList SparseModel<PointType>::getPreviousPoints(long originFrame) const { @@ -443,6 +532,7 @@ QMutexLocker locker(&m_mutex); m_resolution = resolution; } + m_rows.clear(); emit modelChanged(); } @@ -455,6 +545,7 @@ m_points.clear(); m_pointCount = 0; } + m_rows.clear(); emit modelChanged(); } @@ -476,6 +567,7 @@ // alternative is to notify on setCompletion). if (m_notifyOnAdd) { + m_rows.clear(); //!!! inefficient emit modelChanged(point.frame, point.frame + m_resolution); } else { if (m_sinceLastNotifyMin == -1 || @@ -510,6 +602,7 @@ } // std::cout << "SparseOneDimensionalModel: emit modelChanged(" // << point.frame << ")" << std::endl; + m_rows.clear(); //!!! inefficient emit modelChanged(point.frame, point.frame + m_resolution); } @@ -529,6 +622,7 @@ } m_notifyOnAdd = true; // henceforth + m_rows.clear(); //!!! inefficient emit modelChanged(); } else if (!m_notifyOnAdd) { @@ -536,6 +630,7 @@ if (update && m_sinceLastNotifyMin >= 0 && m_sinceLastNotifyMax >= 0) { + m_rows.clear(); //!!! inefficient emit modelChanged(m_sinceLastNotifyMin, m_sinceLastNotifyMax); m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; } else {
--- a/data/model/SparseOneDimensionalModel.h Wed Jun 11 12:54:18 2008 +0000 +++ b/data/model/SparseOneDimensionalModel.h Wed Jun 11 16:13:25 2008 +0000 @@ -95,7 +95,8 @@ return "<plugin program=\"tap\"/>"; } - int getIndexOf(const Point &point) { + int getIndexOf(const Point &point) + { // slow int i = 0; Point::Comparator comparator; @@ -107,6 +108,47 @@ } QString getTypeName() const { return tr("Sparse 1-D"); } + + /** + * TabularModel methods. + */ + + virtual int getColumnCount() const + { + return 3; + } + + virtual QString getHeading(int column) const + { + switch (column) { + case 0: return tr("Time"); + case 1: return tr("Frame"); + case 2: return tr("Label"); + default: return tr("Unknown"); + } + } + + virtual QVariant getData(int row, int column, int role) const + { + if (role != Qt::EditRole && role != Qt::DisplayRole) return QVariant(); + PointListIterator i = getPointListIteratorForRow(row); + if (i == m_points.end()) return QVariant(); + + switch (column) { + case 0: { + RealTime rt = RealTime::frame2RealTime(i->frame, getSampleRate()); + return QVariant(rt.toText().c_str()); + } + case 1: return QVariant(int(i->frame)); + case 2: return QVariant(i->label); + default: return QVariant(); + } + } + + virtual bool isColumnTimeValue(int column) const + { + return (column < 2); + } }; #endif
--- a/data/model/SparseTimeValueModel.h Wed Jun 11 12:54:18 2008 +0000 +++ b/data/model/SparseTimeValueModel.h Wed Jun 11 16:13:25 2008 +0000 @@ -97,6 +97,74 @@ } QString getTypeName() const { return tr("Sparse Time-Value"); } + + /** + * TabularModel methods. + */ + + virtual int getColumnCount() const + { + return 4; + } + + virtual QString getHeading(int column) const + { + switch (column) { + case 0: return tr("Time"); + case 1: return tr("Frame"); + case 2: return tr("Value"); + case 3: return tr("Label"); + default: return tr("Unknown"); + } + } + + virtual QVariant getData(int row, int column, int role) const + { + if (role != Qt::EditRole && role != Qt::DisplayRole) return QVariant(); + PointListIterator i = getPointListIteratorForRow(row); + if (i == m_points.end()) return QVariant(); + + switch (column) { + case 0: { + RealTime rt = RealTime::frame2RealTime(i->frame, getSampleRate()); + return rt.toText().c_str(); + } + case 1: return int(i->frame); + case 2: + if (role == Qt::EditRole) return i->value; + else return QString("%1 %2").arg(i->value).arg(getScaleUnits()); + case 3: return i->label; + default: return QVariant(); + } + } + + virtual Command *setData(int row, int column, QVariant value, int role) + { + if (role != Qt::EditRole) return false; + PointListIterator i = getPointListIteratorForRow(row); + if (i == m_points.end()) return false; + EditCommand *command = new EditCommand(this, tr("Edit Data")); + + Point point(*i); + command->deletePoint(point); + + switch (column) { + case 0: break; + case 1: break; + case 2: point.value = value.toDouble(); + std::cerr << "setting value of point at " << point.frame << " to " << point.value << std::endl; + break; + case 3: point.label = value.toString(); break; + } + + command->addPoint(point); + return command->finish(); + } + + virtual bool isColumnTimeValue(int column) const + { + return (column < 2); + } };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/TabularModel.h Wed Jun 11 16:13:25 2008 +0000 @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2008 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TABULAR_MODEL_H_ +#define _TABULAR_MODEL_H_ + +#include <QVariant> +#include <QString> + +class Command; + +/** + * TabularModel is an abstract base class for models that support + * direct access to data in a tabular form. A model that implements + * TabularModel may be displayed and, perhaps, edited in a data + * spreadsheet window. + * + * This is very like a cut-down QAbstractItemModel. It assumes a + * relationship between row number and frame time. + */ + +class TabularModel +{ +public: + virtual int getRowCount() const = 0; + virtual int getColumnCount() const = 0; + + virtual QString getHeading(int column) const = 0; + virtual QVariant getData(int row, int column, int role) const = 0; + + virtual long getFrameForRow(int row) const = 0; + virtual int getRowForFrame(long frame) const = 0; + + virtual bool isColumnTimeValue(int col) const = 0; + + virtual bool isEditable() const { return false; } + virtual Command *setData(int row, int column, const QVariant &, int role) + { return 0; } +}; + +#endif