Files
qelectrotech-source-mirror/sources/materiallist/materiallist.h
T
Kellermorph 8b54ea400b Fix material list reading, selection and article fields
Follow-up to #1088, covering its review comments and one behaviour
change found while testing the feature:

- MaterialList::load() names the columns from the machine key line when
  the file has one, instead of from the translated label line above it.
  A catalogue written in another language fills the element fields
  again, and saving no longer replaces the key line with labels.
- The entry picked after "New entry" is found by comparing the columns
  one by one instead of comparing the two maps as a whole: the entry
  form leaves the empty columns out, so the record never matched the
  line that had just been written and the search was cleared for
  nothing. The search is now only given up when it really hides the new
  line.
- Applying a catalogue entry pushes an undo command only when the live
  edit is on. In the properties window, where there is none, the fields
  wait for "Apply", so "Cancel" gives the element its own values back
  instead of leaving the picked part in place.
- Cells a file holds past the header are kept in MaterialRecord::extra
  and written back, so appending an article never shortens a line.
- An empty cell of a column describing the article itself (MaterialList::
  isArticleBound: description, designation, manufacturer, order number,
  supplier, model, ratings, dimensions, auxiliary block) clears the
  field, so an element never keeps the manufacturer of the part picked
  before. An empty cell of any other column (function, comment, notes,
  plant, location, quantity, unity), and any column the file does not
  hold at all, leaves the field alone.
- Comments left where the review asked for them: the corner button
  lookup, the ';' separator fallback, the search filter cost.
2026-09-28 22:38:41 +02:00

127 lines
4.6 KiB
C++

/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MATERIALLIST_H
#define MATERIALLIST_H
#include <QByteArray>
#include <QChar>
#include <QList>
#include <QMap>
#include <QSize>
#include <QString>
#include <QStringList>
/**
@brief One line of the material file.
Keys are column names : either a canonical element information key
(description, manufacturer...) or the raw name of a column added by
the user, which QElectroTech does not know about and therefore never
Transfers to an element, but keeps intact when a row is appended.
*/
struct MaterialRecord
{
QMap<QString, QString> values;
//Cells written after the last column of the header, kept as they
//are so that appending an article never shortens a line the user
//wrote wider than the header itself.
QStringList extra;
QString value(const QString &column) const {return values.value(column);}
void setValue(const QString &column, const QString &value) {values.insert(column, value);}
bool operator==(const MaterialRecord &other) const {return values == other.values;}
};
/**
@brief The whole content of a material file.
Both the column names and the separator are read from the file rather
than imposed, so a file edited in a spreadsheet keeps working : the
separator is detected (;, tab or ,), unknown columns are preserved, and
only the header cells that can be matched to a known element
information are canonicalized.
*/
struct MaterialListData
{
QStringList columns;
QList<MaterialRecord> records;
QChar separator = QLatin1Char(';');
bool isEmpty() const {return columns.isEmpty() && records.isEmpty();}
};
/**
@brief The MaterialList class reads and writes the material file (a CSV
file listing purchasable articles) and maps its columns onto element
informations.
This is a pure data class : it does not know anything about widgets,
so it stays usable from the tests and from the command line.
*/
class MaterialList
{
public:
//Settings
static QString settingsKey();
static QString configuredPath();
static void setConfiguredPath(const QString &path);
static QString defaultPath();
static QString defaultFileName();
//Geometry of the selection window, remembered from one opening
//to the next : column widths are part of how the user reads his
//own catalogue and must not be lost when the window closes.
static QByteArray savedHeaderState();
static void saveHeaderState(const QByteArray &state);
static QSize savedDialogSize();
static void saveDialogSize(const QSize &size);
//Columns
static QStringList defaultColumns();
static QStringList columnsForBlock(int block);
static QString elementInfoKey(const QString &column, int block);
static bool isArticleBound(const QString &column);
static QString translatedColumn(const QString &column);
static QStringList translatedHeader(const QStringList &columns);
//File io
static bool load(const QString &path, MaterialListData *data, QString *error = nullptr);
static bool writeFile(const QString &path, const MaterialListData &data, QString *error = nullptr);
static bool appendRecord(const QString &path, const MaterialRecord &record, QString *error = nullptr);
static bool createFile(const QString &path, QString *error = nullptr);
static bool isEmptyFile(const QString &path);
//Csv helpers, public only to keep them testable
static QList<QStringList> parseCsv(const QString &content, QChar separator);
static QByteArray serializeCsv(const QList<QStringList> &header_lines,
const QList<QStringList> &rows,
QChar separator);
static QChar detectSeparator(const QString &first_line);
private:
static QString canonicalColumn(const QString &header_cell,
const QStringList &already_used,
const QMap<QString, QString> &alias_map);
static QMap<QString, QString> headerAliasMap();
static bool isMachineHeaderLine(const QStringList &label_line,
const QStringList &candidate,
const QMap<QString, QString> &alias_map);
};
#endif // MATERIALLIST_H