andre@25: /* Copyright (C) 2015 by ETH Zürich andre@25: * Software engineering by Intevation GmbH andre@24: * andre@24: * This file is Free Software under the GNU GPL (v>=2) andre@24: * and comes with ABSOLUTELY NO WARRANTY! andre@24: * See LICENSE.txt for details. andre@24: */ andre@2: #include "metadataview.h" andre@2: #include "qxtcsvmodel.h" andre@4: #include "filterwidget.h" andre@28: #include "constants.h" andre@2: andre@2: #include andre@2: #include andre@2: #include andre@2: #include andre@2: #include andre@2: #include andre@2: #include andre@22: #include andre@2: #include andre@28: #include andre@35: #include andre@35: #include andre@2: andre@56: /**@brief Small wrapper around csv model to enable numerical sorting. */ andre@56: class numericSortCSVModel : public QxtCsvModel andre@56: { andre@56: public: andre@56: /**@brief returns the data as string, integer or double variant. */ andre@56: QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const { andre@56: QVariant base = QxtCsvModel::data(index, role); andre@56: bool ok = false; andre@56: int intVal = base.toInt(&ok); andre@56: if (ok) { andre@56: return intVal; andre@56: } andre@56: double dblVal = base.toDouble(&ok); andre@56: if (ok) { andre@56: return dblVal; andre@56: } andre@56: return base; andre@56: } andre@56: }; andre@56: andre@2: MetaDataView::MetaDataView(QWidget *parent, Qt::WindowFlags f) : andre@44: QWidget(parent, f), andre@44: mDateColIdx(-1) { andre@2: /* Create models */ andre@2: mSortModel = new QSortFilterProxyModel; andre@56: mCSVModel = new numericSortCSVModel; andre@2: setupGUI(); andre@2: andre@2: connect(mView->selectionModel(), &QItemSelectionModel::selectionChanged, andre@2: this, &MetaDataView::viewSelectionChanged); andre@4: connect(mSortModel, &QSortFilterProxyModel::dataChanged, andre@4: this, &MetaDataView::dataChanged); andre@2: return; andre@2: } andre@2: andre@2: void MetaDataView::setupGUI() { andre@2: QVBoxLayout *baseLayout = new QVBoxLayout; andre@4: andre@4: FilterWidget *filterWidget = new FilterWidget(mSortModel); andre@28: connect(filterWidget, &FilterWidget::filterHasChanged, andre@28: this, &MetaDataView::applyDefaultSort); andre@53: connect(filterWidget, &FilterWidget::filterHasChanged, andre@53: this, &MetaDataView::selectFirstRow); andre@4: baseLayout->addWidget(filterWidget); andre@4: andre@2: mView = new QTableView; andre@2: mView->setModel(mSortModel); andre@2: andre@2: mView->horizontalHeader()->setStretchLastSection(true); andre@2: // mView->setColumnWidth(0, 60); andre@2: mView->setSelectionBehavior(QAbstractItemView::SelectRows); andre@2: mView->setSelectionMode(QAbstractItemView::SingleSelection); andre@2: mView->setSortingEnabled(true); andre@2: mView->setEditTriggers(QAbstractItemView::NoEditTriggers); andre@2: andre@2: baseLayout->addWidget(mView); andre@37: mView->verticalHeader()->setVisible(false); andre@2: andre@2: setLayout(baseLayout); andre@2: } andre@2: andre@2: QString MetaDataView::parseMetaData(const QString& filePath) { andre@2: mCSVModel->setSource(filePath, true, ';', QTextCodec::codecForName("UTF8")); andre@2: if (!mCSVModel->rowCount()) { andre@2: return tr("Failed to parse file: '%1'").arg(filePath); andre@2: } andre@2: andre@2: mSortModel->setSourceModel(mCSVModel); andre@2: qDebug() << "Parsed: " << mCSVModel->rowCount() << " rows."; andre@28: applyDefaultSort(); andre@35: resizeColsToHeaders(); andre@44: andre@44: QSettings settings; andre@44: const QString displayDate = settings.value(DATE_COLUMN_KEY, DATE_COLUMN_DEFAULT).toString(); andre@44: settings.setValue(DATE_COLUMN_KEY, displayDate); andre@44: for (int i=0; i < mSortModel->columnCount(); i++) { andre@44: QString entry = mSortModel->headerData(i, Qt::Horizontal).toString(); andre@44: if (entry.toLower() == displayDate.toLower()) { andre@44: mDateColIdx = i; andre@44: break; andre@44: } andre@44: } andre@44: if (mDateColIdx == -1) { andre@44: qDebug() << "Failed to find displayDate column: " << displayDate; andre@44: mDateColIdx = DATE_COLUMN_FALLBACK_IDX; andre@44: } andre@2: return QString(); andre@2: } andre@2: andre@4: void MetaDataView::dataChanged() andre@4: { andre@4: QItemSelectionModel *selection = mView->selectionModel(); andre@4: QModelIndexList selected = selection->selectedIndexes(); andre@4: andre@4: qDebug() << "Data Changed."; andre@4: if (selected.isEmpty()) { andre@4: /* Nothing selected still we need to emit this signal to update andre@4: * the viewer otherwise selection changed handles it. */ andre@4: emit selectionChanged(QString(), 0, mSortModel->rowCount() - 1, andre@44: QString(), 0); andre@4: } andre@4: } andre@4: andre@2: void MetaDataView::viewSelectionChanged(const QItemSelection& selected, andre@2: const QItemSelection& deselected) { andre@4: if (selected.indexes().isEmpty()) { andre@4: /* Nothing selected */ andre@4: return; andre@4: } andre@2: /* One row selected */ andre@2: Q_ASSERT(selected.indexes().count() == mCSVModel->columnCount()); andre@44: const QModelIndex idx = selected.indexes()[FILENAME_COLUMN_IDX]; andre@44: const QString dateString = selected.indexes()[mDateColIdx].data().toString(); andre@3: bool ok; andre@3: qint64 secondsSinceEpoch = dateString.toLongLong(&ok); andre@44: QString timestamp = dateString; andre@44: if (ok) { andre@44: QDateTime datetime = QDateTime::fromMSecsSinceEpoch(secondsSinceEpoch * 1000); andre@44: if (datetime.isValid()) { andre@44: timestamp = datetime.toString(LONG_DATE_FORMAT); andre@44: } andre@3: } andre@3: emit selectionChanged(idx.data().toString(), idx.row(), mSortModel->rowCount() - 1, andre@37: timestamp, selected.indexes()[0].data().toInt()); andre@2: qDebug() << "Selection changed: " << idx.data(); andre@2: } andre@2: andre@53: void MetaDataView::selectFirstRow() { andre@53: qDebug() << "Selecting first row"; andre@53: selectRow(0); andre@53: } andre@53: andre@3: void MetaDataView::selectRow(int row) { andre@3: QItemSelectionModel *selection = mView->selectionModel(); andre@3: if (!mSortModel->hasIndex(row, 0)) { andre@3: qDebug() << "Invalid row: " << row; andre@3: return; andre@3: } andre@3: QModelIndex newIdx = mSortModel->index(row, 0); andre@53: selection->select(newIdx, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); andre@3: } andre@3: andre@3: void MetaDataView::selectPrevRow() { andre@3: QItemSelectionModel *selection = mView->selectionModel(); andre@3: QModelIndexList selected = selection->selectedIndexes(); andre@3: int row = 0, andre@3: col = 0; andre@3: if (selected.isEmpty()) { andre@3: qDebug() << "Selection empty. Start at row 0"; andre@3: if (!mSortModel->hasIndex(row, col)) { andre@3: qDebug() << "Empty model. Failed to advance."; andre@3: return; andre@3: } andre@3: } else { andre@3: QModelIndex old = selection->selectedIndexes().first(); andre@3: if (!mSortModel->hasIndex(old.row() - 1, old.column())) { andre@3: qDebug() << "No less rows."; andre@3: return; andre@3: } andre@3: row = old.row() - 1; andre@3: col = old.column(); andre@3: } andre@3: QModelIndex newIdx = mSortModel->index(row, col); andre@3: selection->select(newIdx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); andre@3: } andre@3: andre@2: void MetaDataView::selectNextRow() { andre@2: QItemSelectionModel *selection = mView->selectionModel(); andre@2: QModelIndexList selected = selection->selectedIndexes(); andre@2: int row = 0, andre@2: col = 0; andre@2: if (selected.isEmpty()) { andre@2: qDebug() << "Selection empty. Start at row 0"; andre@2: if (!mSortModel->hasIndex(row, col)) { andre@2: qDebug() << "Empty model. Failed to advance."; andre@2: return; andre@2: } andre@2: } else { andre@2: QModelIndex old = selection->selectedIndexes().first(); andre@2: if (!mSortModel->hasIndex(old.row() + 1, old.column())) { andre@2: qDebug() << "No more rows."; andre@2: return; andre@2: } andre@2: row = old.row() + 1; andre@2: col = old.column(); andre@2: } andre@2: QModelIndex newIdx = mSortModel->index(row, col); andre@2: selection->select(newIdx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); andre@2: } andre@28: andre@28: void MetaDataView::applyDefaultSort() { andre@28: QSettings settings; andre@28: QString sortField = settings.value(SORT_COLUMN_KEY, SORT_COLUMN_DEFAULT).toString(); andre@55: settings.setValue(SORT_COLUMN_KEY, sortField); andre@28: bool sortAsc = settings.value(SORT_ORDER_KEY, SORT_ORDER_VALUE).toBool(); andre@55: settings.setValue(SORT_ORDER_KEY, sortAsc); andre@28: andre@28: int idx = -1; andre@28: for (int i=0; i < mSortModel->columnCount(); i++) { andre@28: QString entry = mSortModel->headerData(i, Qt::Horizontal).toString(); andre@28: if (entry.toLower() == sortField.toLower()) { andre@37: idx = i; andre@28: break; andre@28: } andre@28: } andre@28: if (idx == -1) { andre@28: return; andre@28: } andre@28: qDebug() << "Applying default sort order on column " << idx; andre@28: mView->sortByColumn(idx, sortAsc ? Qt::AscendingOrder : Qt::DescendingOrder); andre@28: } andre@28: andre@35: void MetaDataView::resizeColsToHeaders() { andre@35: QFontMetrics fm(qApp->font()); andre@35: /* We do this manually here to avoid resizing to the real contents as andre@35: * we want the columns in the width of the header data. And we only andre@35: * want to increase that size. */ andre@35: for (int i=0; i < mSortModel->columnCount(); i++) { andre@35: const QString entry = mSortModel->headerData(i, Qt::Horizontal).toString(); andre@35: int w = fm.width(entry) + 20; andre@35: if (w > mView->horizontalHeader()->sectionSize(i)) { andre@35: mView->horizontalHeader()->resizeSection(i, w); andre@35: qDebug() << "Resizing " << i << " to: " << w; andre@35: } andre@35: } andre@35: }