diff --git a/ChangeLog.txt b/ChangeLog.txt index 3f7f2df0a..c7c450c15 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -40,6 +40,7 @@ - [#794] Better control over scale value. - [#392] Improve feature: Show progress bar while opening pattern file. - [#732] Tape app. Improve Database dialog. +- [#804] New feature. Import measurements from CSV file in Tape app. # Version 0.5.1 - [#683] Tool Seam allowance's dialog is off screen on small resolutions. diff --git a/src/app/tape/tmainwindow.cpp b/src/app/tape/tmainwindow.cpp index f09c681b9..aa523f652 100644 --- a/src/app/tape/tmainwindow.cpp +++ b/src/app/tape/tmainwindow.cpp @@ -34,6 +34,7 @@ #include "dialogs/dialogtapepreferences.h" #include "../vpatterndb/calculator.h" #include "../vpatterndb/pmsystems.h" +#include "../vpatterndb/measurements.h" #include "../ifc/ifcdef.h" #include "../ifc/xml/vvitconverter.h" #include "../ifc/xml/vvstconverter.h" @@ -41,6 +42,7 @@ #include "../vmisc/vlockguard.h" #include "../vmisc/vsysexits.h" #include "../vmisc/qxtcsvmodel.h" +#include "../vmisc/dialogs/dialogexporttocsv.h" #include "vlitepattern.h" #include "../qmuparser/qmudef.h" #include "../vtools/dialogs/support/dialogeditwrongformula.h" @@ -308,6 +310,8 @@ bool TMainWindow::LoadFile(const QString &path) } MeasurementGUI(); + + ui->actionImportFromCSV->setEnabled(true); } catch (VException &e) { @@ -387,6 +391,8 @@ void TMainWindow::FileNew() InitWindow(); MeasurementGUI(); + + ui->actionImportFromCSV->setEnabled(true); } else { @@ -946,6 +952,56 @@ void TMainWindow::ShowWindow() const } } +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::ImportDataFromCSV() +{ + if (m == nullptr || m->Type() == MeasurementsType::Unknown) + { + return; + } + + const QString filters = tr("Comma-Separated Values") + QLatin1String(" (*.csv)"); + const QString suffix("csv"); + + QString fileName = QFileDialog::getOpenFileName(this, tr("Import from CSV"), QDir::homePath(), filters, nullptr, + QFileDialog::DontUseNativeDialog); + + if (fileName.isEmpty()) + { + return; + } + + QFileInfo f( fileName ); + if (f.suffix().isEmpty() && f.suffix() != suffix) + { + fileName += QLatin1String(".") + suffix; + } + + DialogExportToCSV dialog(this); + dialog.SetWithHeader(qApp->Settings()->GetCSVWithHeader()); + dialog.SetSelectedMib(qApp->Settings()->GetCSVCodec()); + dialog.SetSeparator(qApp->Settings()->GetCSVSeparator()); + + if (dialog.exec() == QDialog::Accepted) + { + qApp->Settings()->SetCSVSeparator(dialog.GetSeparator()); + qApp->Settings()->SetCSVCodec(dialog.GetSelectedMib()); + qApp->Settings()->SetCSVWithHeader(dialog.IsWithHeader()); + + QxtCsvModel csv(fileName, nullptr, dialog.IsWithHeader(), dialog.GetSeparator(), + QTextCodec::codecForMib(dialog.GetSelectedMib())); + + if (m->Type() == MeasurementsType::Individual) + { + ImportIndividualMeasurements(csv); + } + else + { + ImportMultisizeMeasurements(csv); + } + } +} + //--------------------------------------------------------------------------------------------------------------------- #if defined(Q_OS_MAC) void TMainWindow::AboutToShowDockMenu() @@ -1074,7 +1130,7 @@ void TMainWindow::Remove() { MFields(false); - ui->actionExportToCSV->setEnabled(false); + ui->actionExportToCSV->setEnabled(false); ui->lineEditName->blockSignals(true); ui->lineEditName->setText(""); @@ -1880,6 +1936,7 @@ void TMainWindow::SetupMenu() ui->actionSaveAs->setShortcuts(QKeySequence::SaveAs); connect(ui->actionExportToCSV, &QAction::triggered, this, &TMainWindow::ExportDataToCSV); + connect(ui->actionImportFromCSV, &QAction::triggered, this, &TMainWindow::ImportDataFromCSV); connect(ui->actionReadOnly, &QAction::triggered, this, [this](bool ro) { if (not mIsReadOnly) @@ -2440,10 +2497,7 @@ void TMainWindow::RefreshTable(bool freshCall) ui->tableWidget->horizontalHeader()->setStretchLastSection(true); ui->tableWidget->blockSignals(false); - if (ui->tableWidget->rowCount() > 0) - { - ui->actionExportToCSV->setEnabled(true); - } + ui->actionExportToCSV->setEnabled(ui->tableWidget->rowCount() > 0); } //--------------------------------------------------------------------------------------------------------------------- @@ -3011,6 +3065,243 @@ bool TMainWindow::IgnoreLocking(int error, const QString &path) return true; } +//--------------------------------------------------------------------------------------------------------------------- +QString TMainWindow::CheckMName(const QString &name) const +{ + if (name.isEmpty()) + { + throw VException(tr("Measurement name in is empty.")); + } + + if (name.indexOf(CustomMSign) == 0) + { + QRegularExpression rx(NameRegExp()); + if (not rx.match(name).hasMatch()) + { + throw VException(tr("Merasurement '%1' doesn't match regex pattern.").arg(name)); + } + + if (not data->IsUnique(name)) + { + throw VException(tr("Merasurement '%1' already used in file.").arg(name)); + } + } + else + { + if (not AllGroupNames().toSet().contains(name)) + { + throw VException(tr("Measurement '%1' is not one of known measurements.").arg(name)); + } + + if (not data->IsUnique(name)) + { + throw VException(tr("Merasurement '%1' already used in file.").arg(name)); + } + } + + return name; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::ShowError(const QString &text) +{ + QMessageBox messageBox(this); + messageBox.setIcon(QMessageBox::Critical); + messageBox.setText(text); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.setDefaultButton(QMessageBox::Ok); + messageBox.exec(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::RefreshDataAfterImport() +{ + const int currentRow = ui->tableWidget->currentRow(); + search->AddRow(currentRow); + RefreshData(); + search->RefreshList(ui->lineEditFind->text()); + + ui->tableWidget->selectRow(currentRow); + ui->actionExportToCSV->setEnabled(true); + MeasurementsWasSaved(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::ImportIndividualMeasurements(const QxtCsvModel &csv) +{ + const int columns = csv.columnCount(); + const int rows = csv.rowCount(); + + if (columns < 2) + { + ShowError(tr("Individual measurements require at least 2 columns.")); + return; + } + + struct IndividualMeasurement + { + IndividualMeasurement() + : name(), + value("0"), + fullName(), + description() + {} + + QString name; + QString value; + QString fullName; + QString description; + }; + + QVector measurements; + + for(int i=0; i < rows; ++i) + { + try + { + IndividualMeasurement measurement; + measurement.name = CheckMName(qApp->TrVars()->MFromUser(csv.text(i, 0).simplified())); + measurement.value = VTranslateVars::TryFormulaFromUser(csv.text(i, 1), qApp->Settings()->GetOsSeparator()); + + if (columns >= 3) + { + measurement.fullName = csv.text(i, 2).simplified(); + } + + if (columns >= 4) + { + measurement.description = csv.text(i, 3).simplified(); + } + + measurements.append(measurement); + } + catch (VException &e) + { + ShowError(tr("Error in row %1.").arg(i) + QLatin1Char(' ') + e.ErrorMessage()); + return; + } + } + + for(int i=0; i < measurements.size(); ++i) + { + m->AddEmpty(measurements.at(i).name, measurements.at(i).value); + + if (not measurements.at(i).fullName.isEmpty()) + { + m->SetMFullName(measurements.at(i).name, measurements.at(i).fullName); + } + + if (not measurements.at(i).description.isEmpty()) + { + m->SetMDescription(measurements.at(i).name, measurements.at(i).description); + } + } + + RefreshDataAfterImport(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::ImportMultisizeMeasurements(const QxtCsvModel &csv) +{ + const int columns = csv.columnCount(); + const int rows = csv.rowCount(); + + if (columns < 4) + { + ShowError(tr("Multisize measurements require at least 4 columns.")); + return; + } + + auto ConverToDouble = [this](QString text, const QString &error) + { + text = VTranslateVars::TryFormulaFromUser(text, qApp->Settings()->GetOsSeparator()); + bool ok = false; + QLocale::c(); + const qreal value = QLocale::c().toDouble(text, &ok); + if (not ok) + { + throw VException(error); + } + return value; + }; + + struct MultisizeMeasurement + { + MultisizeMeasurement() + : name(), + base(0), + heightIncrease(0), + sizeIncrease(0), + fullName(), + description() + {} + + QString name; + qreal base; + qreal heightIncrease; + qreal sizeIncrease; + QString fullName; + QString description; + }; + + QVector measurements; + + for(int i=0; i < rows; ++i) + { + try + { + MultisizeMeasurement measurement; + measurement.name = CheckMName(qApp->TrVars()->MFromUser(csv.text(i, 0).simplified())); + + measurement.base = ConverToDouble(csv.text(i, 1), + tr("Cannot convert base size value to double in column 2.")); + + measurement.heightIncrease = ConverToDouble(csv.text(i, 2), + tr("Cannot convert height increase value to double in column 3.")); + + measurement.sizeIncrease = ConverToDouble(csv.text(i, 3), + tr("Cannot convert size increase value to double in column 4.")); + + if (columns >= 5) + { + measurement.fullName = csv.text(i, 4).simplified(); + } + + if (columns >= 6) + { + measurement.description = csv.text(i, 5).simplified(); + } + + measurements.append(measurement); + } + catch (VException &e) + { + ShowError(tr("Error in row %1.").arg(i) + QLatin1Char(' ') + e.ErrorMessage()); + return; + } + } + + for(int i=0; i < measurements.size(); ++i) + { + m->AddEmpty(measurements.at(i).name); + m->SetMBaseValue(measurements.at(i).name, measurements.at(i).base); + m->SetMSizeIncrease(measurements.at(i).name, measurements.at(i).sizeIncrease); + m->SetMHeightIncrease(measurements.at(i).name, measurements.at(i).heightIncrease); + + if (not measurements.at(i).fullName.isEmpty()) + { + m->SetMFullName(measurements.at(i).name, measurements.at(i).fullName); + } + + if (not measurements.at(i).description.isEmpty()) + { + m->SetMDescription(measurements.at(i).name, measurements.at(i).description); + } + } + + RefreshDataAfterImport(); +} + //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::SetDecimals() { diff --git a/src/app/tape/tmainwindow.h b/src/app/tape/tmainwindow.h index 5ee066f90..1fde6c3c2 100644 --- a/src/app/tape/tmainwindow.h +++ b/src/app/tape/tmainwindow.h @@ -43,6 +43,7 @@ namespace Ui } class QLabel; +class QxtCsvModel; class TMainWindow : public VAbstractMainWindow { @@ -86,6 +87,7 @@ private slots: bool FileSaveAs(); void AboutToShowWindowMenu(); void ShowWindow() const; + void ImportDataFromCSV(); #if defined(Q_OS_MAC) void AboutToShowDockMenu(); @@ -213,6 +215,13 @@ private: template void HackWidget(T **widget); + + QString CheckMName(const QString &name) const; + void ShowError(const QString &text); + void RefreshDataAfterImport(); + + void ImportIndividualMeasurements(const QxtCsvModel &csv); + void ImportMultisizeMeasurements(const QxtCsvModel &csv); }; #endif // TMAINWINDOW_H diff --git a/src/app/tape/tmainwindow.ui b/src/app/tape/tmainwindow.ui index b6030abd9..96e70e411 100644 --- a/src/app/tape/tmainwindow.ui +++ b/src/app/tape/tmainwindow.ui @@ -844,6 +844,7 @@ + @@ -1182,6 +1183,17 @@ QAction::NoRole + + + false + + + Import from CSV + + + Import from CSV + + diff --git a/src/libs/vformat/vmeasurements.cpp b/src/libs/vformat/vmeasurements.cpp index f5706771b..961a978c6 100644 --- a/src/libs/vformat/vmeasurements.cpp +++ b/src/libs/vformat/vmeasurements.cpp @@ -811,14 +811,7 @@ QDomElement VMeasurements::MakeEmpty(const QString &name, const QString &formula } else { - if (formula.isEmpty()) - { - SetAttribute(element, AttrValue, QString("0")); - } - else - { - SetAttribute(element, AttrValue, formula); - } + SetAttribute(element, AttrValue, formula.isEmpty() ? QString("0") : formula); } return element; diff --git a/src/libs/vmisc/qxtcsvmodel.cpp b/src/libs/vmisc/qxtcsvmodel.cpp index a5644303b..16dd03cd5 100644 --- a/src/libs/vmisc/qxtcsvmodel.cpp +++ b/src/libs/vmisc/qxtcsvmodel.cpp @@ -78,11 +78,11 @@ QxtCsvModel::QxtCsvModel(QObject *parent) : QAbstractTableModel(parent) \sa setSource */ -QxtCsvModel::QxtCsvModel(QIODevice *file, QObject *parent, bool withHeader, QChar separator) +QxtCsvModel::QxtCsvModel(QIODevice *file, QObject *parent, bool withHeader, QChar separator, QTextCodec* codec) : QAbstractTableModel(parent) { QXT_INIT_PRIVATE(QxtCsvModel) - setSource(file, withHeader, separator); + setSource(file, withHeader, separator, codec); } /*! @@ -95,12 +95,12 @@ QxtCsvModel::QxtCsvModel(QIODevice *file, QObject *parent, bool withHeader, QCha \sa setSource */ -QxtCsvModel::QxtCsvModel(const QString &filename, QObject *parent, bool withHeader, QChar separator) +QxtCsvModel::QxtCsvModel(const QString &filename, QObject *parent, bool withHeader, QChar separator, QTextCodec* codec) : QAbstractTableModel(parent) { QXT_INIT_PRIVATE(QxtCsvModel) QFile src(filename); - setSource(&src, withHeader, separator); + setSource(&src, withHeader, separator, codec); } QT_WARNING_POP @@ -214,14 +214,7 @@ void QxtCsvModel::setSource(QIODevice *file, bool withHeader, QChar separator, Q QChar ch, buffer(0); bool readCR = false; QTextStream stream(file); - if (codec) - { - stream.setCodec(codec); - } - else - { - stream.setAutoDetectUnicode(true); - } + codec ? stream.setCodec(codec) : stream.setAutoDetectUnicode(true); while (not stream.atEnd()) { if (buffer != QChar(0)) diff --git a/src/libs/vmisc/qxtcsvmodel.h b/src/libs/vmisc/qxtcsvmodel.h index 3c380eba8..5f617e2ae 100644 --- a/src/libs/vmisc/qxtcsvmodel.h +++ b/src/libs/vmisc/qxtcsvmodel.h @@ -55,9 +55,10 @@ class QxtCsvModel : public QAbstractTableModel Q_OBJECT public: explicit QxtCsvModel(QObject *parent = nullptr); - explicit QxtCsvModel(QIODevice *file, QObject *parent = nullptr, bool withHeader = false, QChar separator = ','); + explicit QxtCsvModel(QIODevice *file, QObject *parent = nullptr, bool withHeader = false, QChar separator = ',', + QTextCodec *codec = nullptr); explicit QxtCsvModel(const QString &filename, QObject *parent = nullptr, bool withHeader = false, - QChar separator = ','); + QChar separator = ',', QTextCodec *codec = nullptr); virtual ~QxtCsvModel() Q_DECL_EQ_DEFAULT; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; diff --git a/src/libs/vpatterndb/vtranslatemeasurements.cpp b/src/libs/vpatterndb/vtranslatemeasurements.cpp index 5959477f7..306accff6 100644 --- a/src/libs/vpatterndb/vtranslatemeasurements.cpp +++ b/src/libs/vpatterndb/vtranslatemeasurements.cpp @@ -73,6 +73,22 @@ bool VTranslateMeasurements::MeasurementsFromUser(QString &newFormula, int posit return false; } +//--------------------------------------------------------------------------------------------------------------------- +QString VTranslateMeasurements::MFromUser(const QString &measurement) const +{ + QMap::const_iterator i = measurements.constBegin(); + while (i != measurements.constEnd()) + { + const QString translated = i.value().translate(qApp->Settings()->GetLocale()); + if (measurement == translated) + { + return i.key(); + } + ++i; + } + return measurement; +} + //--------------------------------------------------------------------------------------------------------------------- QString VTranslateMeasurements::MToUser(const QString &measurement) const { diff --git a/src/libs/vpatterndb/vtranslatemeasurements.h b/src/libs/vpatterndb/vtranslatemeasurements.h index 97cdfb690..106441583 100644 --- a/src/libs/vpatterndb/vtranslatemeasurements.h +++ b/src/libs/vpatterndb/vtranslatemeasurements.h @@ -43,6 +43,7 @@ public: bool MeasurementsFromUser(QString &newFormula, int position, const QString &token, int &bias) const; + QString MFromUser(const QString &measurement) const; QString MToUser(const QString &measurement) const; QString MNumber(const QString &measurement) const; QString MFormula(const QString &measurement) const;