Commit b8ff8811 authored by m.puchner's avatar m.puchner
Browse files

Merge branch 'WIP_RW_Project' into 'develop'

Reflectometry import - read/write project, show import warnings/errors, item selection, UI improvements

See merge request !11
parents 7cffc9b2 b773501f
Pipeline #33452 passed with stage
in 19 minutes and 49 seconds
......@@ -19,7 +19,7 @@
#include "Device/InputOutput/DataFormatUtils.h"
#include <map>
// #migration +++ this works only if separator is space or tab; it does not
// #bamigration +++ this works only if separator is space or tab; it does not
// work e.g. with comma or semicolon
OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fin)
{
......@@ -34,11 +34,11 @@ OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fi
while (std::getline(fin, line)) {
line = StringUtils::trim(line);
try {
// #migration +++ this works only if separator is space or tab; it does not
// #bamigration +++ this works only if separator is space or tab; it does not
// work e.g. with comma or semicolon
std::vector<double> rowVec = DataFormatUtils::parse_doubles(line);
vecVec.push_back(rowVec);
} catch (...) { // #migration +++ This eats useful errors away...
} catch (...) { // #bamigration +++ This eats useful errors away...
continue;
}
}
......@@ -46,10 +46,10 @@ OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fi
// validate - There is at least one row and at least two columns
size_t nrows = vecVec.size();
if (nrows < 1)
throw std::runtime_error("Import1dTextData: no numerical values found");
throw std::runtime_error("No numerical values found");
size_t ncols = vecVec[0].size();
if (ncols < 2)
throw std::runtime_error("Import1dTextData: Minimum 2 columns required");
throw std::runtime_error("Minimum 2 columns required");
// Assign Q vs R, dR, dQ:
for (size_t row = 0; row < nrows; row++) {
......
......@@ -15,16 +15,11 @@
#include "GUI/coregui/DataLoaders/AbstractDataLoader.h"
#include <QString>
QString AbstractDataLoader::info() const
{
return QString();
}
void AbstractDataLoader::populateImportSettingsWidget(QWidget*) {}
void AbstractDataLoader::populatePropertiesWidget(QWidget*) {}
void AbstractDataLoader::applyImportSettings() {}
void AbstractDataLoader::applyProperties() {}
void AbstractDataLoader::initWithDefaultProperties() {}
void AbstractDataLoader::initWithDefaultImportSettings() {}
QByteArray AbstractDataLoader::serialize() const
{
......@@ -33,14 +28,65 @@ QByteArray AbstractDataLoader::serialize() const
void AbstractDataLoader::deserialize(const QByteArray&) {}
QByteArray AbstractDataLoader::defaultProperties() const
QByteArray AbstractDataLoader::defaultImportSettings() const
{
std::unique_ptr<AbstractDataLoader> cloned(clone());
cloned->initWithDefaultProperties();
cloned->initWithDefaultImportSettings();
return cloned->serialize();
}
bool AbstractDataLoader::fillImportDetailsTable(QTableWidget*, bool, bool, bool) const
void AbstractDataLoader::guessSettings() {}
int AbstractDataLoader::numErrors() const
{
return 0;
}
int AbstractDataLoader::numLineRelatedErrors() const
{
return 0;
}
QStringList AbstractDataLoader::lineUnrelatedErrors() const
{
return {};
}
AbstractDataLoaderResultModel* AbstractDataLoader::createResultModel() const
{
return nullptr;
}
QByteArray AbstractDataLoader::fileContent() const
{
return {};
}
void AbstractDataLoader::setRealDataItem(RealDataItem* item)
{
m_item = item;
}
RealDataItem* AbstractDataLoader::realDataItem()
{
return m_item;
}
const RealDataItem* AbstractDataLoader::realDataItem() const
{
return m_item;
}
QDataStream& operator<<(QDataStream& stream, const AbstractDataLoader& s)
{
stream << s.serialize();
return stream;
}
QDataStream& operator>>(QDataStream& stream, AbstractDataLoader& s)
{
return false;
QByteArray b;
stream >> b;
s.deserialize(b);
return stream;
}
......@@ -21,6 +21,7 @@ class QGroupBox;
class QCustomPlot;
class QTableWidget;
class RealDataItem;
class AbstractDataLoaderResultModel;
#include <QtCore>
......@@ -34,21 +35,30 @@ public:
//! The name shown in the format selection combo
virtual QString name() const = 0;
//! Info about what this loader does. Is shown on the import dialog pane
virtual QString info() const;
//! A name which can be used for save/load purposes (which will not change ever more)
virtual QString persistentClassName() const = 0;
//! Define the real data item on which the import shall work.
void setRealDataItem(RealDataItem* item);
//! The real data item on which the import shall work.
RealDataItem* realDataItem();
//! The real data item on which the import shall work.
const RealDataItem* realDataItem() const;
//! Fills the widget on the import dialog pane. This base class' implementation does nothing
//! (meaning "no editable properties")
virtual void populatePropertiesWidget(QWidget* parent);
virtual void populateImportSettingsWidget(QWidget* parent);
//! Read all values from the properties UI into the internal variables
virtual void applyProperties();
virtual void applyImportSettings();
//! Set import settings to defaults
virtual void initWithDefaultProperties();
virtual void initWithDefaultImportSettings();
//! A name which can be used for save/load purposes (which will not change ever more)
virtual QString persistentClassName() const = 0;
//! Return the default import settings
virtual QByteArray defaultImportSettings() const;
//! Create a complete clone, including all internal states
virtual AbstractDataLoader* clone() const = 0;
......@@ -56,30 +66,63 @@ public:
//! Returns every internal setting so it can be restored completely
virtual QByteArray serialize() const;
//! Initialize from serialization data
// #baimport: how to deliver errors? VersionException...?
//! Initialize from serialization data. If any error occurred, then a DeserializationException
//! has to be thrown.
//! The complete state has to be restored. Therefore if e.g. errors occurred in the former
//! serialization, but errors are not serialized, then they have to be regenerated/recalculated
//! in here.
virtual void deserialize(const QByteArray& data);
//! Return the default import settings
// #baimport Rename?
virtual QByteArray defaultProperties() const;
//! Sets the file contents to be imported. If the file was a compressed file, here already the
//! decompressed content will be overhanded.
virtual void setFileContents(const QByteArray& fileContent) = 0;
//! Plots the graph as a preview
virtual void previewOfGraph(QCustomPlot* plotWidget) const = 0;
//! Returns the original file content. If not available any more (like for legacy project file
//! import), then an empty array will be returned.
virtual QByteArray fileContent() const;
//! Import the given file, write the imported data in the realDataItem
virtual void importFile(const QString& filename, RealDataItem* item, QStringList* errors,
QStringList* warnings) const = 0;
//! Guess appropriate settings (for example the separator in a CSV file). Is called only once,
//! directly after setting the file content.
virtual void guessSettings();
//! Process the file contents. Can be called more than once, e.g. if the import settings have
//! changed.
//! Any error has to be stored in the loader (see numErrors()).
virtual void processContents() = 0;
//! Number of errors found while processing the content. An error means that either a particular
//! content (line) can't be used or may be suspicious (line related error), or that the whole
//! content can't be used (e.g. only 1 line present).
virtual int numErrors() const;
//! Number of errors related to a specific line. Such an error means that a particular
//! content (line) can't be used or may be suspicious.
virtual int numLineRelatedErrors() const;
//! Errors not related to a particular line.
virtual QStringList lineUnrelatedErrors() const;
//! Create a table model which contains the import information like original file content, raw
//! content, processed content
//! The returned pointer will be owned by the caller.
//! This base class' implementation does nothing (return nullptr).
virtual AbstractDataLoaderResultModel* createResultModel() const;
//! Fill the import details table with information from the last call to importFile
//! return false if not supported.
//! This base implementation returns false.
virtual bool fillImportDetailsTable(QTableWidget* table, bool fileContent, bool rawContent,
bool processedContent) const;
signals:
//! Emitted whenever an import setting changed
// #baimport Rename?
void propertiesChanged();
void importSettingsChanged();
//! Emitted whenever contents have been processed
void contentsProcessed();
protected:
enum class Error { DifferendNumberOfColumns };
protected:
RealDataItem* m_item; //< The real-data-item which owns this loader. Never delete this!
};
QDataStream& operator<<(QDataStream& stream, const AbstractDataLoader& s);
QDataStream& operator>>(QDataStream& stream, AbstractDataLoader& s);
#endif // BORNAGAIN_GUI_COREGUI_DATALOADERS_ABSTRACTDATALOADER_H
// ************************************************************************************************
//
// BornAgain: simulate and fit reflection and scattering
//
//! @file GUI/coregui/DataLoaders/AbstractDataLoaderResultModel.cpp
//! @brief Implements class AbstractDataLoaderResultModel
//!
//! @homepage http://www.bornagainproject.org
//! @license GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
// ************************************************************************************************
#include "GUI/coregui/DataLoaders/AbstractDataLoaderResultModel.h"
#include <QColor>
#include <QIcon>
#include <QVariant>
namespace {
struct Palette {
static QColor backgroundColorFileContent;
static QColor backgroundColorRawContent;
static QColor backgroundColorProcessedContent;
static QColor backgroundColorErrors;
static QColor skippedLineTextColor;
};
QColor Palette::backgroundColorFileContent(239, 237, 248);
QColor Palette::backgroundColorRawContent(247, 240, 210);
QColor Palette::backgroundColorProcessedContent(191, 232, 242);
QColor Palette::backgroundColorErrors(247, 140, 146);
QColor Palette::skippedLineTextColor(Qt::lightGray);
} // namespace
AbstractDataLoaderResultModel::AbstractDataLoaderResultModel()
{
m_warningIcon = QIcon(":/images/warning_16x16.png");
}
int AbstractDataLoaderResultModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
if (parent.isValid())
return 0;
const auto colTypes = {ColumnType::line, ColumnType::fileContent, ColumnType::raw,
ColumnType::processed, ColumnType::error};
int cols = 0;
for (ColumnType type : colTypes)
cols += columnCount(type);
return cols;
}
int AbstractDataLoaderResultModel::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : rowCount();
}
QVariant AbstractDataLoaderResultModel::data(const QModelIndex& index,
int role /*= Qt::DisplayRole*/) const
{
if (!index.isValid())
return QVariant();
const auto colType = columnType(index);
if (role == Qt::BackgroundRole) {
switch (colType) {
case ColumnType::line:
return Palette::backgroundColorFileContent;
case ColumnType::fileContent:
return Palette::backgroundColorFileContent;
case ColumnType::raw:
return rowIsSkipped(index) ? QVariant() : Palette::backgroundColorRawContent;
case ColumnType::processed:
return rowIsSkipped(index) || rowHasError(index)
? QVariant()
: Palette::backgroundColorProcessedContent;
case ColumnType::error:
return rowIsSkipped(index) || !rowHasError(index) ? QVariant()
: Palette::backgroundColorErrors;
default:
return QVariant();
}
}
if (role == Qt::ForegroundRole) {
if (colType == ColumnType::fileContent && rowIsSkipped(index))
return Palette::skippedLineTextColor;
return QVariant();
}
if (role == Qt::DisplayRole) {
switch (colType) {
case ColumnType::line:
return QString::number(index.row() + 1);
case ColumnType::fileContent: {
QString lineContent =
cellText(colType, index.row(), index.column() - firstSectionOfColumnType(colType));
lineContent.replace("\t", " --> ");
return lineContent;
}
case ColumnType::raw:
case ColumnType::error: {
if (!rowIsSkipped(index))
return cellText(colType, index.row(),
index.column() - firstSectionOfColumnType(colType));
}
case ColumnType::processed: {
if (!rowIsSkipped(index) && !rowHasError(index))
return cellText(colType, index.row(),
index.column() - firstSectionOfColumnType(colType));
}
}
return QVariant();
}
if (role == Qt::DecorationRole && (colType == ColumnType::line || colType == ColumnType::error)
&& rowHasError(index))
return m_warningIcon;
if (role == Qt::TextAlignmentRole && colType == ColumnType::line)
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
if (role == Qt::ToolTipRole && colType == ColumnType::line && rowHasError(index))
return cellText(ColumnType::error, index.row(), 0);
return QVariant();
}
QVariant AbstractDataLoaderResultModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (columnType(section)) {
case ColumnType::line:
return "Line";
case ColumnType::fileContent:
return "File content (text)";
case ColumnType::raw:
return QString("Column %1 raw")
.arg(section - firstSectionOfColumnType(ColumnType::raw) + 1);
case ColumnType::processed:
return headerTextOfCalculatedColumn(section
- firstSectionOfColumnType(ColumnType::processed));
case ColumnType::error:
return "Parser warnings";
default:
return QVariant();
}
}
return QAbstractTableModel::headerData(section, orientation, role);
}
QVector<int> AbstractDataLoaderResultModel::sectionsOfColumnType(ColumnType type) const
{
QVector<int> sections;
for (int section = firstSectionOfColumnType(type); section <= lastSectionOfColumnType(type);
section++)
if (section >= 0)
sections << section;
return sections;
}
AbstractDataLoaderResultModel::ColumnType
AbstractDataLoaderResultModel::columnType(int section) const
{
const auto colTypes = {ColumnType::line, ColumnType::fileContent, ColumnType::raw,
ColumnType::processed, ColumnType::error};
for (ColumnType type : colTypes) {
const int firstSection = firstSectionOfColumnType(type);
if (firstSection < 0)
continue;
if (section >= firstSection && section <= lastSectionOfColumnType(type))
return type;
}
return ColumnType::none;
}
AbstractDataLoaderResultModel::ColumnType
AbstractDataLoaderResultModel::columnType(const QModelIndex& index) const
{
return columnType(index.column());
}
int AbstractDataLoaderResultModel::firstSectionOfColumnType(ColumnType type) const
{
const int lineColumnCount = columnCount(ColumnType::line);
const int fileContentColumnCount = columnCount(ColumnType::fileContent);
if (type == ColumnType::line)
return lineColumnCount > 0 ? 0 : -1;
if (type == ColumnType::fileContent)
return columnCount(ColumnType::fileContent) > 0 ? lineColumnCount : -1;
if (type == ColumnType::raw) {
const bool hasRawContent = columnCount(ColumnType::raw) > 0;
return hasRawContent ? lineColumnCount + fileContentColumnCount : -1;
}
if (type == ColumnType::processed) {
const bool hasProcessedContent = columnCount(ColumnType::processed) > 0;
return hasProcessedContent
? (lineColumnCount + fileContentColumnCount + columnCount(ColumnType::raw))
: -1;
}
if (type == ColumnType::error) {
const bool hasParsingErrors = columnCount(ColumnType::error) > 0;
return hasParsingErrors
? (lineColumnCount + fileContentColumnCount + columnCount(ColumnType::raw)
+ columnCount(ColumnType::processed))
: -1;
}
return -1;
}
int AbstractDataLoaderResultModel::lastSectionOfColumnType(ColumnType type) const
{
const int firstSection = firstSectionOfColumnType(type);
if (firstSection == -1)
return -1;
return firstSection + columnCount(type) - 1;
}
bool AbstractDataLoaderResultModel::rowIsSkipped(const QModelIndex& index) const
{
return rowIsSkipped(index.row());
}
bool AbstractDataLoaderResultModel::rowHasError(const QModelIndex& index) const
{
return rowHasError(index.row());
}
// ************************************************************************************************
//
// BornAgain: simulate and fit reflection and scattering
//
//! @file GUI/coregui/DataLoaders/AbstractDataLoaderResultModel.h
//! @brief Defines class AbstractDataLoaderResultModel
//!
//! @homepage http://www.bornagainproject.org
//! @license GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
// ************************************************************************************************
#ifndef GUI_COREGUI_DATALOADERS_ABSTRACTDATALOADERRESULTMODEL_H
#define GUI_COREGUI_DATALOADERS_ABSTRACTDATALOADERRESULTMODEL_H
#include <QAbstractItemModel>
#include <QIcon>
//! Base class for result tables of data loaders. Derive from this class and return an instance in
//! YourDataLoader::createResultModel().
//! The result table is the one on the right side when importing CSV files.
class AbstractDataLoaderResultModel : public QAbstractTableModel {
public:
enum class ColumnType { none, line, fileContent, raw, processed, error };
AbstractDataLoaderResultModel();
// overrides of QAbstractTableModel
public:
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int rowCount(const QModelIndex& parent) const override;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
public:
//! The table header sections which belong to the given column type. Empty if this column type
//! is not present at all.
QVector<int> sectionsOfColumnType(ColumnType type) const;
protected:
//! Returns whether the row given in the index is a skipped row. Only override this for
//! performance reasons.
virtual bool rowIsSkipped(const QModelIndex& index) const;
//! Returns whether the row given in the index contains errors. Only override this for
//! performance reasons.
virtual bool rowHasError(const QModelIndex& index) const;
//! Returns whether the row given in the index is a skipped row. Row counting starts with 0.
virtual bool rowIsSkipped(int row) const = 0;
//! Returns whether the row given in the index contains errors. Row counting starts with 0.
virtual bool rowHasError(int row) const = 0;
//! Return the table header text for the given column. For convenience, column starts at 0 for
//! first _calculated_ column, therefore it is not the same as the "real" section in the table
//! header.
virtual QString headerTextOfCalculatedColumn(int column) const = 0;
//! The row count of the result table.
virtual int rowCount() const = 0;
//! The number of existing columns related to the given column type. 0 if the type is not
//! present at all.
virtual int columnCount(ColumnType type) const = 0;
//! The text of the given cell. For convenience, column starts at 0 for the given column type,
//! therefore it is not the same as the "real" section in the table header. This method will not
//! be called for every row/column present in the table. Instead, optimizations will be done
//! before calling it. E.g. the calculated values for lines which contain errors will never be
//! called. Also raw or calculated contents will not be queried if a line is skipped.
virtual QString cellText(ColumnType type, int row, int column) const = 0;
private:
//! Calculates the column type of the given index.
ColumnType columnType(const QModelIndex& index) const;
//! Calculates the column type of the real header view section.
ColumnType columnType(int section) const;
//! Calculates the first real header view section of the given column type.
//! Returns -1 if the column type does not exist.
int firstSectionOfColumnType(ColumnType type) const;
//! Calculates the last real header view section of the given column type.
//! Returns -1 if the co