view src/metadataview.cpp @ 82:9a03703622b8

Scroll to selection when invisible
author Andre Heinecke <andre.heinecke@intevation.de>
date Thu, 18 Jun 2015 17:58:34 +0200
parents 5923d569167b
children 3916cb3c9105
line wrap: on
line source
/* Copyright (C) 2015 by ETH Zürich
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "metadataview.h"
#include "qxtcsvmodel.h"
#include "filterwidget.h"
#include "constants.h"

#include <QTextCodec>
#include <QTableView>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QLabel>
#include <QDebug>
#include <QModelIndex>
#include <QHeaderView>
#include <QItemSelectionModel>
#include <QSettings>
#include <QFontMetrics>
#include <QApplication>

/**@brief Small wrapper around csv model to enable numerical sorting. */
class numericSortCSVModel : public QxtCsvModel
{
public:
    /**@brief returns the data as string, integer or double variant. */
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const {
        QVariant base = QxtCsvModel::data(index, role);
        bool ok = false;
        int intVal = base.toInt(&ok);
        if (ok) {
            return intVal;
        }
        double dblVal = base.toDouble(&ok);
        if (ok) {
            return dblVal;
        }
        return base;
    }
};

MetaDataView::MetaDataView(QWidget *parent, Qt::WindowFlags f) :
    QWidget(parent, f),
    mDateColIdx(-1) {
    /* Create models */
    mSortModel = new QSortFilterProxyModel;
    mCSVModel = new numericSortCSVModel;
    setupGUI();

    connect(mView->selectionModel(), &QItemSelectionModel::selectionChanged,
            this, &MetaDataView::viewSelectionChanged);
    connect(mSortModel, &QSortFilterProxyModel::dataChanged,
            this, &MetaDataView::dataChanged);
    return;
}

void MetaDataView::setupGUI() {
    QVBoxLayout *baseLayout = new QVBoxLayout;

    FilterWidget *filterWidget = new FilterWidget(mSortModel);
    connect(filterWidget, &FilterWidget::filterHasChanged,
            this, &MetaDataView::applyDefaultSort);
    connect(filterWidget, &FilterWidget::filterHasChanged,
            this, &MetaDataView::selectFirstRow);
    baseLayout->addWidget(filterWidget);

    mView = new QTableView;
    mView->setModel(mSortModel);

    mView->horizontalHeader()->setStretchLastSection(true);
//    mView->setColumnWidth(0, 60);
    mView->setSelectionBehavior(QAbstractItemView::SelectRows);
    mView->setSelectionMode(QAbstractItemView::SingleSelection);
    mView->setSortingEnabled(true);
    mView->setEditTriggers(QAbstractItemView::NoEditTriggers);

    baseLayout->addWidget(mView);
    mView->verticalHeader()->setVisible(false);

    setLayout(baseLayout);
}

QString MetaDataView::parseMetaData(const QString& filePath) {
    mCSVModel->setSource(filePath, true, ';', QTextCodec::codecForName("UTF8"));
    if (!mCSVModel->rowCount()) {
        return tr("Failed to parse file: '%1'").arg(filePath);
    }

    mSortModel->setSourceModel(mCSVModel);
    qDebug() << "Parsed: " << mCSVModel->rowCount() << " rows.";
    applyDefaultSort();
    resizeColsToHeaders();

    QSettings settings;
    const QString displayDate = settings.value(DATE_COLUMN_KEY, DATE_COLUMN_DEFAULT).toString();
    settings.setValue(DATE_COLUMN_KEY, displayDate);
    for (int i=0; i < mSortModel->columnCount(); i++) {
        QString entry = mSortModel->headerData(i, Qt::Horizontal).toString();
        if (entry.toLower() == displayDate.toLower()) {
            mDateColIdx = i;
            break;
        }
    }
    if (mDateColIdx == -1) {
        qDebug() << "Failed to find displayDate column: " << displayDate;
        mDateColIdx = DATE_COLUMN_FALLBACK_IDX;
    }
    setupHeaderTooltips();
    return QString();
}

void MetaDataView::dataChanged()
{
    QItemSelectionModel *selection = mView->selectionModel();
    QModelIndexList selected = selection->selectedIndexes();

    qDebug() << "Data Changed.";
    if (selected.isEmpty()) {
        /* Nothing selected still we need to emit this signal to update
         * the viewer otherwise selection changed handles it. */
        emit selectionChanged(QString(), 0, mSortModel->rowCount() - 1,
                QString(), 0);
    }
}

void MetaDataView::viewSelectionChanged(const QItemSelection& selected,
                                        const QItemSelection& deselected) {
    if (selected.indexes().isEmpty()) {
        /* Nothing selected */
        return;
    }
    /* One row selected */
    Q_ASSERT(selected.indexes().count() == mCSVModel->columnCount());
    const QModelIndex idx = selected.indexes()[FILENAME_COLUMN_IDX];
    const QString dateString = selected.indexes()[mDateColIdx].data().toString();
    bool ok;
    qint64 secondsSinceEpoch = dateString.toLongLong(&ok);
    QString timestamp = dateString;
    if (ok) {
        QDateTime datetime = QDateTime::fromMSecsSinceEpoch(secondsSinceEpoch * 1000);
        if (datetime.isValid()) {
            timestamp = datetime.toString(LONG_DATE_FORMAT);
        }
    }
    emit selectionChanged(idx.data().toString(), idx.row(), mSortModel->rowCount() - 1,
            timestamp, selected.indexes()[0].data().toInt());
    qDebug() << "Selection changed: " << idx.data();
    if (!isVisible()) {
        mView->scrollTo(selected.indexes()[0], QAbstractItemView::PositionAtTop);
    }
}

void MetaDataView::selectFirstRow() {
    qDebug() << "Selecting first row";
    selectRow(0);
}

void MetaDataView::selectRow(int row) {
    QItemSelectionModel *selection = mView->selectionModel();
    if (!mSortModel->hasIndex(row, 0)) {
        qDebug() << "Invalid row: " << row;
        return;
    }
    QModelIndex newIdx = mSortModel->index(row, 0);
    selection->select(newIdx, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
}

void MetaDataView::selectPrevRow() {
    QItemSelectionModel *selection = mView->selectionModel();
    QModelIndexList selected = selection->selectedIndexes();
    int row = 0,
        col = 0;
    if (selected.isEmpty()) {
        qDebug() << "Selection empty. Start at row 0";
        if (!mSortModel->hasIndex(row, col)) {
            qDebug() << "Empty model. Failed to advance.";
            return;
        }
    } else {
        QModelIndex old = selection->selectedIndexes().first();
        if (!mSortModel->hasIndex(old.row() - 1, old.column())) {
            qDebug() << "No less rows.";
            return;
        }
        row = old.row() - 1;
        col = old.column();
    }
    QModelIndex newIdx = mSortModel->index(row, col);
    selection->select(newIdx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
}

void MetaDataView::selectNextRow() {
    QItemSelectionModel *selection = mView->selectionModel();
    QModelIndexList selected = selection->selectedIndexes();
    int row = 0,
        col = 0;
    if (selected.isEmpty()) {
        qDebug() << "Selection empty. Start at row 0";
        if (!mSortModel->hasIndex(row, col)) {
            qDebug() << "Empty model. Failed to advance.";
            return;
        }
    } else {
        QModelIndex old = selection->selectedIndexes().first();
        if (!mSortModel->hasIndex(old.row() + 1, old.column())) {
            qDebug() << "No more rows.";
            return;
        }
        row = old.row() + 1;
        col = old.column();
    }
    QModelIndex newIdx = mSortModel->index(row, col);
    selection->select(newIdx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
}

void MetaDataView::applyDefaultSort() {
    QSettings settings;
    QString sortField = settings.value(SORT_COLUMN_KEY, SORT_COLUMN_DEFAULT).toString();
    settings.setValue(SORT_COLUMN_KEY, sortField);
    bool sortAsc = settings.value(SORT_ORDER_KEY, SORT_ORDER_VALUE).toBool();
    settings.setValue(SORT_ORDER_KEY, sortAsc);

    int idx = -1;
    for (int i=0; i < mSortModel->columnCount(); i++) {
        QString entry = mSortModel->headerData(i, Qt::Horizontal).toString();
        if (entry.toLower() == sortField.toLower()) {
            idx = i;
            break;
        }
    }
    if (idx == -1) {
        return;
    }
    qDebug() << "Applying default sort order on column " << idx;
    mView->sortByColumn(idx, sortAsc ? Qt::AscendingOrder : Qt::DescendingOrder);
}

void MetaDataView::resizeColsToHeaders() {
    QFontMetrics fm(qApp->font());
    /* We do this manually here to avoid resizing to the real contents as
     * we want the columns in the width of the header data. And we only
     * want to increase that size. */
    for (int i=0; i < mSortModel->columnCount(); i++) {
        const QString entry = mSortModel->headerData(i, Qt::Horizontal).toString();
        int w = fm.width(entry) + 20;
        if (w > mView->horizontalHeader()->sectionSize(i)) {
            mView->horizontalHeader()->resizeSection(i, w);
            qDebug() << "Resizing " << i << " to: " << w;
        }
    }
}

void MetaDataView::setupHeaderTooltips() {
    QSettings settings;
    settings.beginGroup(TOOLTIP_CONFIG_GROUP);
    for (int i=0; i < mSortModel->columnCount(); i++) {
        QString entry = mSortModel->headerData(i, Qt::Horizontal).toString();
        QString toolTip = settings.value(entry).toString();
        settings.setValue(entry, toolTip);
        if (!mSortModel->setHeaderData(i, Qt::Horizontal, toolTip, Qt::ToolTipRole)) {
            qDebug() << "Failed to set header data";
        }
    }
}
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)