Chris@413: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@413: 
Chris@413: /*
Chris@413:     Sonic Visualiser
Chris@413:     An audio file viewer and annotation editor.
Chris@413:     Centre for Digital Music, Queen Mary, University of London.
Chris@413:     This file copyright 2008 QMUL.
Chris@413:     
Chris@413:     This program is free software; you can redistribute it and/or
Chris@413:     modify it under the terms of the GNU General Public License as
Chris@413:     published by the Free Software Foundation; either version 2 of the
Chris@413:     License, or (at your option) any later version.  See the file
Chris@413:     COPYING included with this distribution for more information.
Chris@413: */
Chris@413: 
Chris@413: #include "ModelDataTableModel.h"
Chris@413: 
Chris@420: #include "TabularModel.h"
Chris@420: #include "Model.h"
Chris@413: 
Chris@422: #include <map>
Chris@417: #include <algorithm>
Chris@420: #include <iostream>
Chris@417: 
Chris@420: ModelDataTableModel::ModelDataTableModel(TabularModel *m) :
Chris@421:     m_model(m),
Chris@421:     m_sortColumn(0),
Chris@428:     m_sortOrdering(Qt::AscendingOrder),
Chris@428:     m_currentRow(0)
Chris@413: {
Chris@420:     Model *baseModel = dynamic_cast<Model *>(m);
Chris@420: 
Chris@420:     connect(baseModel, SIGNAL(modelChanged()), this, SLOT(modelChanged()));
Chris@1046:     connect(baseModel, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
Chris@1046:             this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
Chris@454:     connect(baseModel, SIGNAL(aboutToBeDeleted()),
Chris@454:             this, SLOT(modelAboutToBeDeleted()));
Chris@413: }
Chris@413: 
Chris@413: ModelDataTableModel::~ModelDataTableModel()
Chris@413: {
Chris@413: }
Chris@413: 
Chris@413: QVariant
Chris@413: ModelDataTableModel::data(const QModelIndex &index, int role) const
Chris@413: {
Chris@454:     if (!m_model) return QVariant();
Chris@424:     if (role != Qt::EditRole && role != Qt::DisplayRole) return QVariant();
Chris@413:     if (!index.isValid()) return QVariant();
Chris@420:     return m_model->getData(getUnsorted(index.row()), index.column(), role);
Chris@413: }
Chris@413: 
Chris@413: bool
Chris@413: ModelDataTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
Chris@413: {
Chris@454:     if (!m_model) return false;
Chris@420:     if (!index.isValid()) return false;
Chris@421:     Command *command = m_model->getSetDataCommand(getUnsorted(index.row()),
Chris@421:                                                   index.column(),
Chris@421:                                                   value, role);
Chris@420:     if (command) {
Chris@427:         emit addCommand(command);
Chris@420:         return true;
Chris@420:     } else {
Chris@416:         return false;
Chris@416:     }
Chris@413: }
Chris@413: 
Chris@427: bool
Chris@427: ModelDataTableModel::insertRow(int row, const QModelIndex &parent)
Chris@427: {
Chris@454:     if (!m_model) return false;
Chris@427:     if (parent.isValid()) return false;
Chris@427: 
Chris@427:     emit beginInsertRows(parent, row, row);
Chris@427: 
Chris@427:     Command *command = m_model->getInsertRowCommand(getUnsorted(row));
Chris@427: 
Chris@427:     if (command) {
Chris@427:         emit addCommand(command);
Chris@427:     }
Chris@427: 
Chris@427:     emit endInsertRows();
Chris@427: 
Chris@427:     return (command ? true : false);
Chris@427: }
Chris@427: 
Chris@427: bool
Chris@427: ModelDataTableModel::removeRow(int row, const QModelIndex &parent)
Chris@427: {
Chris@454:     if (!m_model) return false;
Chris@427:     if (parent.isValid()) return false;
Chris@427: 
Chris@427:     emit beginRemoveRows(parent, row, row);
Chris@427: 
Chris@427:     Command *command = m_model->getRemoveRowCommand(getUnsorted(row));
Chris@427: 
Chris@427:     if (command) {
Chris@427:         emit addCommand(command);
Chris@427:     }
Chris@427: 
Chris@427:     emit endRemoveRows();
Chris@427: 
Chris@427:     return (command ? true : false);
Chris@427: }
Chris@427: 
Chris@413: Qt::ItemFlags
Chris@929: ModelDataTableModel::flags(const QModelIndex &) const
Chris@413: {
Chris@416:     Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsEditable |
Chris@416:         Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsSelectable;
Chris@416:     return flags;
Chris@413: }
Chris@413: 
Chris@413: QVariant
Chris@413: ModelDataTableModel::headerData(int section, Qt::Orientation orientation, int role) const
Chris@413: {
Chris@454:     if (!m_model) return QVariant();
Chris@454: 
Chris@425:     if (orientation == Qt::Vertical && role == Qt::DisplayRole) {
Chris@425:         return section + 1;
Chris@425:     }
Chris@413:     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
Chris@420:         return m_model->getHeading(section);
Chris@425:     } 
Chris@413:     return QVariant();
Chris@413: }
Chris@413: 
Chris@413: QModelIndex
Chris@929: ModelDataTableModel::index(int row, int column, const QModelIndex &) const
Chris@413: {
Chris@763:     return createIndex(row, column, (void *)0);
Chris@413: }
Chris@413: 
Chris@413: QModelIndex
Chris@929: ModelDataTableModel::parent(const QModelIndex &) const
Chris@413: {
Chris@413:     return QModelIndex();
Chris@413: }
Chris@413: 
Chris@413: int
Chris@413: ModelDataTableModel::rowCount(const QModelIndex &parent) const
Chris@413: {
Chris@454:     if (!m_model) return 0;
Chris@413:     if (parent.isValid()) return 0;
Chris@420:     return m_model->getRowCount();
Chris@413: }
Chris@413: 
Chris@413: int
Chris@413: ModelDataTableModel::columnCount(const QModelIndex &parent) const
Chris@413: {
Chris@454:     if (!m_model) return 0;
Chris@413:     if (parent.isValid()) return 0;
Chris@420:     return m_model->getColumnCount();
Chris@416: }
Chris@416: 
Chris@416: QModelIndex 
Chris@1038: ModelDataTableModel::getModelIndexForFrame(sv_frame_t frame) const
Chris@416: {
Chris@454:     if (!m_model) return createIndex(0, 0);
Chris@420:     int row = m_model->getRowForFrame(frame);
Chris@763:     return createIndex(getSorted(row), 0, (void *)0);
Chris@413: }
Chris@413: 
Chris@1038: sv_frame_t
Chris@419: ModelDataTableModel::getFrameForModelIndex(const QModelIndex &index) const
Chris@419: {
Chris@454:     if (!m_model) return 0;
Chris@420:     return m_model->getFrameForRow(getUnsorted(index.row()));
Chris@420: }
Chris@420: 
Chris@618: QModelIndex
Chris@618: ModelDataTableModel::findText(QString text) const
Chris@618: {
Chris@618:     if (text == "") return QModelIndex();
Chris@618:     int rows = rowCount();
Chris@618:     int cols = columnCount();
Chris@618:     int current = getCurrentRow();
Chris@618:     for (int row = 1; row <= rows; ++row) {
Chris@618:         int wrapped = (row + current) % rows;
Chris@618:         for (int col = 0; col < cols; ++col) {
Chris@618:             if (m_model->getSortType(col) != TabularModel::SortAlphabetical) {
Chris@618:                 continue;
Chris@618:             }
Chris@618:             QString cell = m_model->getData(getUnsorted(wrapped), col,
Chris@618:                                             Qt::DisplayRole).toString();
Chris@618:             if (cell.contains(text, Qt::CaseInsensitive)) {
Chris@618:                 return createIndex(wrapped, col);
Chris@618:             }
Chris@618:         }
Chris@618:     }
Chris@618:     return QModelIndex();
Chris@618: }
Chris@618: 
Chris@420: void
Chris@420: ModelDataTableModel::sort(int column, Qt::SortOrder sortOrder)
Chris@420: {
Chris@690: //    SVDEBUG << "ModelDataTableModel::sort(" << column << ", " << sortOrder
Chris@687: //              << ")" << endl;
Chris@428:     int prevCurrent = getCurrentRow();
Chris@422:     if (m_sortColumn != column) {
Chris@428:         clearSort();
Chris@422:     }
Chris@420:     m_sortColumn = column;
Chris@420:     m_sortOrdering = sortOrder;
Chris@428:     int current = getCurrentRow();
Chris@428:     if (current != prevCurrent) {
Chris@843: //         cerr << "Current row changed from " << prevCurrent << " to " << current << " for underlying row " << m_currentRow << endl;
Chris@763:         emit currentChanged(createIndex(current, 0, (void *)0));
Chris@428:     }
Chris@420:     emit layoutChanged();
Chris@419: }
Chris@419: 
Chris@413: void
Chris@413: ModelDataTableModel::modelChanged()
Chris@413: {
Chris@428:     clearSort();
Chris@413:     emit layoutChanged();
Chris@413: }
Chris@413: 
Chris@413: void 
Chris@1046: ModelDataTableModel::modelChangedWithin(sv_frame_t, sv_frame_t)
Chris@416: {
Chris@420:     //!!! inefficient
Chris@428:     clearSort();
Chris@416:     emit layoutChanged();
Chris@416: }
Chris@413: 
Chris@454: void
Chris@454: ModelDataTableModel::modelAboutToBeDeleted()
Chris@454: {
Chris@454:     m_model = 0;
Chris@454:     emit modelRemoved();
Chris@454: }
Chris@454: 
Chris@420: int
Chris@426: ModelDataTableModel::getSorted(int row) const
Chris@413: {
Chris@454:     if (!m_model) return row;
Chris@454: 
Chris@420:     if (m_model->isColumnTimeValue(m_sortColumn)) {
Chris@420:         if (m_sortOrdering == Qt::AscendingOrder) {
Chris@420:             return row;
Chris@420:         } else {
Chris@420:             return rowCount() - row - 1;
Chris@420:         }
Chris@420:     }
Chris@413: 
Chris@420:     if (m_sort.empty()) {
Chris@420:         resort();
Chris@413:     }
Chris@422:     int result = 0;
Chris@929:     if (row >= 0 && row < (int)m_sort.size()) {
Chris@422:         result = m_sort[row];
Chris@422:     }
Chris@422:     if (m_sortOrdering == Qt::DescendingOrder) {
Chris@422:         result = rowCount() - result - 1;
Chris@422:     }
Chris@422: 
Chris@422:     return result;
Chris@413: }
Chris@413: 
Chris@420: int
Chris@426: ModelDataTableModel::getUnsorted(int row) const
Chris@413: {
Chris@454:     if (!m_model) return row;
Chris@454: 
Chris@420:     if (m_model->isColumnTimeValue(m_sortColumn)) {
Chris@420:         if (m_sortOrdering == Qt::AscendingOrder) {
Chris@420:             return row;
Chris@420:         } else {
Chris@420:             return rowCount() - row - 1;
Chris@420:         }
Chris@413:     }
Chris@422: 
Chris@420:     if (m_sort.empty()) {
Chris@420:         resort();
Chris@420:     }
Chris@422: 
Chris@422:     int result = 0;
Chris@929:     if (row >= 0 && row < (int)m_sort.size()) {
Chris@422:         if (m_sortOrdering == Qt::AscendingOrder) {
Chris@422:             result = m_rsort[row];
Chris@422:         } else {
Chris@422:             result = m_rsort[rowCount() - row - 1];
Chris@422:         }
Chris@422:     }
Chris@422: 
Chris@422:     return result;
Chris@413: }
Chris@413: 
Chris@420: void
Chris@426: ModelDataTableModel::resort() const
Chris@413: {
Chris@454:     if (!m_model) return;
Chris@454: 
Chris@422:     bool numeric = (m_model->getSortType(m_sortColumn) ==
Chris@422:                     TabularModel::SortNumeric);
Chris@422: 
Chris@843: //    cerr << "resort: numeric == " << numeric << endl;
Chris@618: 
Chris@422:     m_sort.clear();
Chris@422:     m_rsort.clear();
Chris@422: 
Chris@422:     if (numeric) resortNumeric();
Chris@422:     else resortAlphabetical();
Chris@422: 
Chris@422:     std::map<int, int> tmp;
Chris@422: 
Chris@422:     // rsort maps from sorted row number to original row number
Chris@422: 
Chris@929:     for (int i = 0; i < (int)m_rsort.size(); ++i) {
Chris@422:         tmp[m_rsort[i]] = i;
Chris@422:     }
Chris@422: 
Chris@422:     // tmp now maps from original row number to sorted row number
Chris@422: 
Chris@422:     for (std::map<int, int>::const_iterator i = tmp.begin(); i != tmp.end(); ++i) {
Chris@422:         m_sort.push_back(i->second);
Chris@422:     }
Chris@422: 
Chris@422:     // and sort now maps from original row number to sorted row number
Chris@413: }
Chris@413: 
Chris@422: void
Chris@426: ModelDataTableModel::resortNumeric() const
Chris@422: {
Chris@454:     if (!m_model) return;
Chris@454: 
Chris@422:     typedef std::multimap<double, int> MapType;
Chris@422: 
Chris@422:     MapType rowMap;
Chris@422:     int rows = m_model->getRowCount();
Chris@422: 
Chris@422:     for (int i = 0; i < rows; ++i) {
Chris@424:         QVariant value = m_model->getData(i, m_sortColumn, TabularModel::SortRole);
Chris@422:         rowMap.insert(MapType::value_type(value.toDouble(), i));
Chris@422:     }
Chris@422: 
Chris@422:     for (MapType::iterator i = rowMap.begin(); i != rowMap.end(); ++i) {
Chris@843: //        cerr << "resortNumeric: " << i->second << ": " << i->first << endl;
Chris@422:         m_rsort.push_back(i->second);
Chris@422:     }
Chris@422: 
Chris@422:     // rsort now maps from sorted row number to original row number
Chris@422: }
Chris@422: 
Chris@422: void
Chris@426: ModelDataTableModel::resortAlphabetical() const
Chris@422: {
Chris@454:     if (!m_model) return;
Chris@454: 
Chris@422:     typedef std::multimap<QString, int> MapType;
Chris@422: 
Chris@422:     MapType rowMap;
Chris@422:     int rows = m_model->getRowCount();
Chris@422: 
Chris@422:     for (int i = 0; i < rows; ++i) {
Chris@422:         QVariant value =
Chris@422:             m_model->getData(i, m_sortColumn, TabularModel::SortRole);
Chris@422:         rowMap.insert(MapType::value_type(value.toString(), i));
Chris@422:     }
Chris@422: 
Chris@422:     for (MapType::iterator i = rowMap.begin(); i != rowMap.end(); ++i) {
Chris@843: //        cerr << "resortAlphabetical: " << i->second << ": " << i->first << endl;
Chris@422:         m_rsort.push_back(i->second);
Chris@422:     }
Chris@422: 
Chris@422:     // rsort now maps from sorted row number to original row number
Chris@422: }
Chris@422: 
Chris@428: int
Chris@618: ModelDataTableModel::getCurrentRow() const
Chris@428: {
Chris@428:     return getSorted(m_currentRow);
Chris@428: }
Chris@428: 
Chris@428: void
Chris@428: ModelDataTableModel::setCurrentRow(int row)
Chris@428: {
Chris@428:     m_currentRow = getUnsorted(row);
Chris@428: }
Chris@428: 
Chris@428: void
Chris@428: ModelDataTableModel::clearSort()
Chris@428: {
Chris@428: //    int prevCurrent = getCurrentRow();
Chris@428:     m_sort.clear();
Chris@428: //    int current = getCurrentRow(); //!!! no --  not until the sort criteria have changed
Chris@428: //    if (current != prevCurrent) {
Chris@843: //        cerr << "Current row changed from " << prevCurrent << " to " << current << " for underlying row " << m_currentRow << endl;
Chris@428: //        emit currentRowChanged(createIndex(current, 0, 0));
Chris@428: //    }
Chris@428: }
Chris@428: 
Chris@428: