Search code examples
c++qtmodel-view-controllerqtcharts

My Qt QAbstractProxyModel crashes with ChartView but not with TableView?


I have a custom QAbstractTableModel and a proxy model which flips the axes of the first model. With the table this works, I just switch the model. When I switch to my proxy model for the chart it crashes at the point where I assign the rows to the QHXYModelMapper. What have I screwed up?

This is the table model:

#include "tablemodel.h"

TableModel::TableModel(QObject *parent) :
    QAbstractTableModel(parent)
{

}

int TableModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_data.count();
}

int TableModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    if(m_data.count() < 1)
    {
        return 0;
    }
    return m_data[0].count();
}

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal) {
        if (section % 2 == 0)
            return "x";
        else
            return "y";
    } else {
        return QString("%1").arg(section + 1);
    }
}

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!role == Qt::DisplayRole)
    {
        return QVariant();
    }
    return m_data[index.row()].at(index.column());
}

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {
        m_data[index.row()].replace(index.column(), value.toDouble());
        emit dataChanged(index, index);
        return true;
    }
    return false;
}

void TableModel::appendRow(QVector<double> row)
{
    emit layoutAboutToBeChanged();
    emit beginInsertRows(QModelIndex(), rowCount(), rowCount());

    m_data.append(row);

    emit endInsertRows();
    emit layoutChanged();
}

void TableModel::clear()
{
    for(int i = 0; i < m_data.count(); ++i)
    {
        m_data[i].clear();
    }
    m_data.clear();
}

This is the proxy implementation:

HorizontalProxyModel::HorizontalProxyModel(QObject *parent) : QAbstractProxyModel(parent)
{
}

QModelIndex HorizontalProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
    if (sourceModel()) {
        return sourceModel()->index(proxyIndex.column(), proxyIndex.row());
    } else {
        return QModelIndex();
    }
}

QModelIndex HorizontalProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
    return index(sourceIndex.column(), sourceIndex.row());
}

QModelIndex HorizontalProxyModel::index(int row, int column, const QModelIndex &) const
{
    return createIndex(row, column, (void*) 0);
}

QModelIndex HorizontalProxyModel::parent(const QModelIndex &) const
{
    return QModelIndex();
}

int HorizontalProxyModel::rowCount(const QModelIndex &) const
{
    return sourceModel() ? sourceModel()->columnCount() : 0;
}

int HorizontalProxyModel::columnCount(const QModelIndex &) const
{
    return sourceModel() ? sourceModel()->rowCount() : 0;
}

QVariant HorizontalProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (!sourceModel()) { return QVariant(); }
    Qt::Orientation new_orientation = orientation == Qt::Horizontal ?
                Qt::Vertical : Qt::Horizontal;
    return sourceModel() ? sourceModel()->headerData(section, new_orientation, role) : 0;
}

QVariant HorizontalProxyModel::data(const QModelIndex &index) const
{
    qDebug() << "h model data";
    return sourceModel() ? sourceModel()->data(sourceModel()->index(index.column(), index.row())) : 0;
}

And this is where I assign the model:

void MainWindow::plot(QAbstractItemModel* m)
{
QChart* chart = new QChart;

qDebug() << m->rowCount() << " " << m->columnCount();

for(int row = 0; row < m->rowCount(); ++row)
{

    QLineSeries *series = new QLineSeries;
    QHXYModelMapper* mapper = new QHXYModelMapper;
    QString name = "Row " + QString::number(row);

    series->setName(name);
    mapper->setModel(m);
    mapper->setSeries(series);
    mapper->setXRow(row);  //crashes here if proxy model
    mapper->setYRow(row);

    chart->addSeries(series);

}
chart->createDefaultAxes();
QChart* oldChart = chartView->chart();
chartView->setChart(chart);
oldChart->deleteLater();
}

EDIT

Some more info...

Looking at the debugger, it seems that the index being created in the ProxyModel and passed to the original model is -1/-1 (invalid). Does line 9 in this debug output mean that the base class QAbstractProxyModel::data() is being called, instead of the one from my derived proxy model? If so, why?

1   __pthread_kill                                                                                 

    0x7fff91eaff06 
2   pthread_kill                                                                                       0x7fff907204ec 
3   abort                                                                                              0x7fff876cd6df 
4   qt_message_fatal(QtMsgType, QMessageLogContext const&, QString const&)                             0x100ac3e79    
5   QMessageLogger::fatal(const char *, ...) const                                                     0x100ac5847    
6   qt_assert_x(const char *, const char *, const char *, int)                                         0x100ac0682    
7   QList<QVector<double>>::operator[](int) const                               qlist.h            541 0x10000fced    
8   TableModel::data(QModelIndex const&, int) const                             tablemodel.cpp     46  0x10000f8f1    
9   QAbstractProxyModel::data(QModelIndex const&, int) const                                           0x100c4c28b    
10  QtCharts::QXYModelMapperPrivate::valueFromModel(QModelIndex)                                       0x10171dfb3    
11  QtCharts::QXYModelMapperPrivate::initializeXYFromModel()                                           0x10171d92f    
12  QtCharts::QHXYModelMapper::setYRow(int)                                                            0x101720bf4    
13  MainWindow::plot(QAbstractItemModel *)

UDPATE: FIX

So, the fix I have right now is to call QHXYModelMapper::setColumnCount() manually, like so:

mapper->setModel(m);
mapper->setSeries(series);
mapper->setColumnCount(m->columnCount());
mapper->setXRow(row);
mapper->setYRow(row);

The docs seem to imply that the default value will use the total number of columns in the model, if I don't explicitly set this:

http://doc.qt.io/qt-5/qhxymodelmapper.html#columnCount-prop


Solution

  • Talked it over on the Qt forums a bit, this appears to be a bug, where the default value for the HXYModelMapper columnCount property does not actually retrieve the proper columnCount from the model. The workaround is to call setColumnCount yourself and set it to the columnCount of your model.

    mapper->setModel(m);
    mapper->setSeries(series);
    mapper->setColumnCount(m->columnCount());
    mapper->setXRow(row);
    mapper->setYRow(row);
    

    Bug report: https://bugreports.qt.io/browse/QTBUG-57342