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 @@ ...@@ -19,7 +19,7 @@
#include "Device/InputOutput/DataFormatUtils.h" #include "Device/InputOutput/DataFormatUtils.h"
#include <map> #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 // work e.g. with comma or semicolon
OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fin) OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fin)
{ {
...@@ -34,11 +34,11 @@ OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fi ...@@ -34,11 +34,11 @@ OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fi
while (std::getline(fin, line)) { while (std::getline(fin, line)) {
line = StringUtils::trim(line); line = StringUtils::trim(line);
try { 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 // work e.g. with comma or semicolon
std::vector<double> rowVec = DataFormatUtils::parse_doubles(line); std::vector<double> rowVec = DataFormatUtils::parse_doubles(line);
vecVec.push_back(rowVec); vecVec.push_back(rowVec);
} catch (...) { // #migration +++ This eats useful errors away... } catch (...) { // #bamigration +++ This eats useful errors away...
continue; continue;
} }
} }
...@@ -46,10 +46,10 @@ OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fi ...@@ -46,10 +46,10 @@ OutputData<double>* OutputDataReadReflectometry::readOutputData(std::istream& fi
// validate - There is at least one row and at least two columns // validate - There is at least one row and at least two columns
size_t nrows = vecVec.size(); size_t nrows = vecVec.size();
if (nrows < 1) 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(); size_t ncols = vecVec[0].size();
if (ncols < 2) 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: // Assign Q vs R, dR, dQ:
for (size_t row = 0; row < nrows; row++) { for (size_t row = 0; row < nrows; row++) {
......
...@@ -15,16 +15,11 @@ ...@@ -15,16 +15,11 @@
#include "GUI/coregui/DataLoaders/AbstractDataLoader.h" #include "GUI/coregui/DataLoaders/AbstractDataLoader.h"
#include <QString> #include <QString>
QString AbstractDataLoader::info() const void AbstractDataLoader::populateImportSettingsWidget(QWidget*) {}
{
return QString();
}
void AbstractDataLoader::populatePropertiesWidget(QWidget*) {} void AbstractDataLoader::applyImportSettings() {}
void AbstractDataLoader::applyProperties() {} void AbstractDataLoader::initWithDefaultImportSettings() {}
void AbstractDataLoader::initWithDefaultProperties() {}
QByteArray AbstractDataLoader::serialize() const QByteArray AbstractDataLoader::serialize() const
{ {
...@@ -33,14 +28,65 @@ QByteArray AbstractDataLoader::serialize() const ...@@ -33,14 +28,65 @@ QByteArray AbstractDataLoader::serialize() const
void AbstractDataLoader::deserialize(const QByteArray&) {} void AbstractDataLoader::deserialize(const QByteArray&) {}
QByteArray AbstractDataLoader::defaultProperties() const QByteArray AbstractDataLoader::defaultImportSettings() const
{ {
std::unique_ptr<AbstractDataLoader> cloned(clone()); std::unique_ptr<AbstractDataLoader> cloned(clone());
cloned->initWithDefaultProperties(); cloned->initWithDefaultImportSettings();
return cloned->serialize(); 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; ...@@ -21,6 +21,7 @@ class QGroupBox;
class QCustomPlot; class QCustomPlot;
class QTableWidget; class QTableWidget;
class RealDataItem; class RealDataItem;
class AbstractDataLoaderResultModel;
#include <QtCore> #include <QtCore>
...@@ -34,21 +35,30 @@ public: ...@@ -34,21 +35,30 @@ public:
//! The name shown in the format selection combo //! The name shown in the format selection combo
virtual QString name() const = 0; virtual QString name() const = 0;
//! Info about what this loader does. Is shown on the import dialog pane //! A name which can be used for save/load purposes (which will not change ever more)
virtual QString info() const; 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 //! Fills the widget on the import dialog pane. This base class' implementation does nothing
//! (meaning "no editable properties") //! (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 //! Read all values from the properties UI into the internal variables
virtual void applyProperties(); virtual void applyImportSettings();
//! Set import settings to defaults //! 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) //! Return the default import settings
virtual QString persistentClassName() const = 0; virtual QByteArray defaultImportSettings() const;
//! Create a complete clone, including all internal states //! Create a complete clone, including all internal states
virtual AbstractDataLoader* clone() const = 0; virtual AbstractDataLoader* clone() const = 0;
...@@ -56,30 +66,63 @@ public: ...@@ -56,30 +66,63 @@ public:
//! Returns every internal setting so it can be restored completely //! Returns every internal setting so it can be restored completely
virtual QByteArray serialize() const; virtual QByteArray serialize() const;
//! Initialize from serialization data //! Initialize from serialization data. If any error occurred, then a DeserializationException
// #baimport: how to deliver errors? VersionException...? //! 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); virtual void deserialize(const QByteArray& data);
//! Return the default import settings //! Sets the file contents to be imported. If the file was a compressed file, here already the
// #baimport Rename? //! decompressed content will be overhanded.
virtual QByteArray defaultProperties() const; virtual void setFileContents(const QByteArray& fileContent) = 0;
//! Plots the graph as a preview //! Returns the original file content. If not available any more (like for legacy project file
virtual void previewOfGraph(QCustomPlot* plotWidget) const = 0; //! import), then an empty array will be returned.
virtual QByteArray fileContent() const;
//! Import the given file, write the imported data in the realDataItem //! Guess appropriate settings (for example the separator in a CSV file). Is called only once,
virtual void importFile(const QString& filename, RealDataItem* item, QStringList* errors, //! directly after setting the file content.
QStringList* warnings) const = 0; 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: signals:
//! Emitted whenever an import setting changed //! Emitted whenever an import setting changed
// #baimport Rename? void importSettingsChanged();
void propertiesChanged();
//! 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 #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