770 lines
26 KiB
C++
770 lines
26 KiB
C++
/***************************************************************************
|
|
|
|
PMusrStep.cpp
|
|
|
|
Author: Andreas Suter
|
|
e-mail: andreas.suter@psi.ch
|
|
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* Copyright (C) 2007-2025 by Andreas Suter *
|
|
* andreas.suter@psi.ch *
|
|
* *
|
|
* This program 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. *
|
|
* *
|
|
* This program 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 this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
/**
|
|
* @file PMusrStep.cpp
|
|
* @brief Implementation of the PMusrStep and PModSelect dialog classes.
|
|
* @details This file contains the implementation of the GUI dialogs used
|
|
* by the musrStep application to modify fit parameter step sizes in
|
|
* muSR msr-files.
|
|
*
|
|
* @author Andreas Suter
|
|
* @date 2007-2025
|
|
* @copyright GNU General Public License v2 or later
|
|
*/
|
|
|
|
#include <QMessageBox>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QFile>
|
|
#include <QByteArray>
|
|
#include <QHBoxLayout>
|
|
#include <QVBoxLayout>
|
|
#include <QInputDialog>
|
|
#include <QLineEdit>
|
|
#include <QDoubleValidator>
|
|
#include <QTextStream>
|
|
#include <QPixmap>
|
|
#include <QGuiApplication>
|
|
#include <QScreen>
|
|
|
|
#include <QDebug>
|
|
|
|
#include "PMusrStep.h"
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
/**
|
|
* @brief Constructor for the PModSelect dialog.
|
|
* @details Creates and initializes the dialog UI with the following controls:
|
|
* - "Scale by Factor" button with associated factor input field
|
|
* - "Absolute Value" checkbox to toggle between multiplicative and absolute scaling
|
|
* - "Scale Automatically" button for automatic scaling based on parameter names
|
|
* - "Cancel" button to close the dialog without applying changes
|
|
*
|
|
* @param parent Pointer to the parent widget (default: Q_NULLPTR)
|
|
*/
|
|
PModSelect::PModSelect(QWidget *parent) :
|
|
QDialog(parent)
|
|
{
|
|
setWindowTitle("Modify Selected");
|
|
|
|
fScaleByFactor = std::make_unique<QPushButton>("Scale by &Factor");
|
|
fScaleByFactor->setWhatsThis("if pressed it will use the Factor value, and the absolut value selection to change the selected parameter steps.");
|
|
fFactorLabel = std::make_unique<QLabel>("Factor");
|
|
fFactorLineEdit = std::make_unique<QLineEdit>("0.01");
|
|
fFactorLineEdit->setValidator(new QDoubleValidator);
|
|
fAbsVal = std::make_unique<QCheckBox>("Absolute Value");
|
|
fAbsVal->setWhatsThis("if checked, the factor is used as an absolut value rather than a multiplication factor for the selected steps.");
|
|
|
|
QHBoxLayout *top = new QHBoxLayout;
|
|
top->addWidget(fScaleByFactor.get());
|
|
top->addWidget(fFactorLabel.get());
|
|
top->addWidget(fFactorLineEdit.get());
|
|
top->addWidget(fAbsVal.get());
|
|
|
|
fScaleAutomatic = std::make_unique<QPushButton>("Scale &Automatically");
|
|
fScaleAutomatic->setWhatsThis("Will try to reset the step size of the selected items based on some crude rules");
|
|
fCancel = std::make_unique<QPushButton>("&Cancel");
|
|
|
|
QHBoxLayout *bottom = new QHBoxLayout;
|
|
bottom->addWidget(fScaleAutomatic.get());
|
|
bottom->addWidget(fCancel.get());
|
|
|
|
QVBoxLayout *main = new QVBoxLayout;
|
|
main->addLayout(top);
|
|
main->addLayout(bottom);
|
|
|
|
setLayout(main);
|
|
|
|
connect(fAbsVal.get(), SIGNAL(stateChanged(int)), this, SLOT(absoluteValueStateChanged(int)));
|
|
connect(fScaleAutomatic.get(), SIGNAL(pressed()), this, SLOT(scaleAuto()));
|
|
connect(fScaleByFactor.get(), SIGNAL(pressed()), this, SLOT(getFactor()));
|
|
connect(fCancel.get(), SIGNAL(pressed()), this, SLOT(reject()));
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Handles state changes of the absolute value checkbox.
|
|
* @details Updates the UI labels to reflect the current mode:
|
|
* - When unchecked: Shows "Factor" label and "Scale by Factor" button text
|
|
* - When checked: Shows "Value" label and "Set Abs. Value" button text
|
|
*
|
|
* @param ival The new checkbox state (Qt::Checked or Qt::Unchecked)
|
|
*/
|
|
void PModSelect::absoluteValueStateChanged(int ival)
|
|
{
|
|
if (ival == Qt::Unchecked) {
|
|
fFactorLabel->setText("Factor");
|
|
fScaleByFactor->setText("Scale by &Factor");
|
|
} else if (ival == Qt::Checked) {
|
|
fFactorLabel->setText("Value");
|
|
fScaleByFactor->setText("Set Abs. Value");
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Triggers automatic scaling of selected parameters.
|
|
* @details Emits the scale() signal with automatic=true and a default
|
|
* factor of 0.01. The parent dialog will then apply scaling factors
|
|
* based on parameter naming conventions using its lookupTable() method.
|
|
*/
|
|
void PModSelect::scaleAuto()
|
|
{
|
|
emit scale(true, 0.01, false);
|
|
done(1);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Reads the user-specified factor and emits the scale signal.
|
|
* @details Retrieves the factor value from the input field and the
|
|
* absolute value checkbox state, then emits the scale() signal with
|
|
* automatic=false to indicate manual scaling mode.
|
|
*/
|
|
void PModSelect::getFactor()
|
|
{
|
|
double factor = fFactorLineEdit->text().toDouble();
|
|
bool state = fAbsVal->isChecked();
|
|
|
|
emit scale(false, factor, state);
|
|
done(1);
|
|
}
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
/**
|
|
* @brief Constructor for the PMusrStep main dialog.
|
|
* @details Initializes the dialog with the following operations:
|
|
* 1. Sets up the window title and icon based on the current theme
|
|
* 2. Reads and parses the msr-file to extract fit parameters
|
|
* 3. Creates the parameter table with columns for name, value, and step
|
|
* 4. Sets up selection buttons (Check Specific, Check All, Uncheck All)
|
|
* 5. Sets up modification buttons (Modify Automatic, Modify Selected)
|
|
* 6. Sets up action buttons (Save&Quit, Cancel)
|
|
* 7. Creates the PModSelect sub-dialog for detailed modification options
|
|
*
|
|
* The dialog height is automatically adjusted based on the number of
|
|
* parameters, with a maximum limit based on screen resolution.
|
|
*
|
|
* @param fln Path to the msr-file to open and edit
|
|
* @param parent Pointer to the parent widget (default: Q_NULLPTR)
|
|
*/
|
|
PMusrStep::PMusrStep(const char *fln, QWidget *parent) :
|
|
QDialog(parent),
|
|
fMsrFileName(fln)
|
|
{
|
|
setWindowTitle("musrStep");
|
|
QString str = QIcon::themeName();
|
|
bool isDarkTheme = false;
|
|
if (str.contains("dark", Qt::CaseInsensitive))
|
|
isDarkTheme = true;
|
|
|
|
if (isDarkTheme)
|
|
str = QString(":/icons/musrStep-22x22-dark.svg");
|
|
else
|
|
str = QString(":/icons/musrStep-22x22.svg");
|
|
setWindowIcon(QIcon(QPixmap(str)));
|
|
|
|
fValid = false;
|
|
|
|
QString title = QString("<b><font size=""14"">%1</font></b>").arg(fMsrFileName);
|
|
fTitleLabel = std::make_unique<QLabel>(title);
|
|
QLabel *icon = new QLabel();
|
|
if (isDarkTheme)
|
|
str = QString(":/icons/musrStep-32x32-dark.svg");
|
|
else
|
|
str = QString(":/icons/musrStep-32x32.svg");
|
|
icon->setPixmap(QPixmap(str));
|
|
|
|
QHBoxLayout *titleLayout = new QHBoxLayout;
|
|
titleLayout->addWidget(fTitleLabel.get());
|
|
titleLayout->addWidget(icon);
|
|
titleLayout->insertStretch(1);
|
|
|
|
int status = 0;
|
|
if ((status=readMsrFile()) == 1) {
|
|
fValid = true;
|
|
} else {
|
|
QString msg = QString("Failed to read msr-file: %1 (status=%2)").arg(fMsrFileName).arg(status);
|
|
QMessageBox::critical(nullptr, "ERROR", msg);
|
|
}
|
|
|
|
int height;
|
|
if (fParamVec.size() < 70)
|
|
height = 20*fParamVec.size();
|
|
else
|
|
height = 900;
|
|
// make sure that the minimal height is not larger than the screen resolution height
|
|
QScreen *screen = QGuiApplication::primaryScreen();
|
|
int hh = screen->geometry().height();
|
|
if (height > hh)
|
|
height = hh - 70;
|
|
setMinimumSize(400, height);
|
|
|
|
// populate dialog
|
|
fParamTable = std::make_unique<QTableWidget>(fParamVec.size(), 3);
|
|
QStringList strL;
|
|
strL << "name" << "value" << "step";
|
|
fParamTable->setHorizontalHeaderLabels(strL);
|
|
|
|
QTableWidgetItem *item;
|
|
for (int i=0; i<fParamVec.size(); i++) {
|
|
item = new QTableWidgetItem(fParamVec[i].name);
|
|
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable);
|
|
item->setCheckState(Qt::Unchecked);
|
|
fParamTable->setItem(i, 0, item);
|
|
item = new QTableWidgetItem(fParamVec[i].value);
|
|
item->setFlags(Qt::ItemIsEnabled);
|
|
fParamTable->setItem(i, 1, item);
|
|
item = new QTableWidgetItem(fParamVec[i].step);
|
|
fParamTable->setItem(i, 2, item);
|
|
}
|
|
fCheckSpecific = std::make_unique<QPushButton>("Check S&pecific");
|
|
fCheckSpecific->setWhatsThis("Allows to specify a template name which is used\nto select parameter names fitting to it.");
|
|
fCheckAll = std::make_unique<QPushButton>("Check A&ll");
|
|
fCheckAll->setWhatsThis("Select all parameter names,\nexcept the ones with step == 0");
|
|
fUnCheckAll = std::make_unique<QPushButton>("&Uncheck All");
|
|
fUnCheckAll->setWhatsThis("Unselect all parameter names");
|
|
|
|
QHBoxLayout *checkLayout = new QHBoxLayout;
|
|
checkLayout->addWidget(fCheckSpecific.get());
|
|
checkLayout->addWidget(fCheckAll.get());
|
|
checkLayout->addWidget(fUnCheckAll.get());
|
|
|
|
fModifyAuto = std::make_unique<QPushButton>("Modify &Automatic");
|
|
fModifyAuto->setWhatsThis("Will try to reset the step size,\nbased on some crude rules");
|
|
fModifyAuto->setDefault(true);
|
|
fModifySelected = std::make_unique<QPushButton>("&Modify Selected");
|
|
fModifySelected->setWhatsThis("Will call a dialog which all to specify how\nto proceed with the selected parameter steps.");
|
|
|
|
QHBoxLayout *modifyLayout = new QHBoxLayout;
|
|
modifyLayout->addWidget(fModifyAuto.get());
|
|
modifyLayout->addWidget(fModifySelected.get());
|
|
|
|
fSave = std::make_unique<QPushButton>("&Save&&Quit");
|
|
fCancel = std::make_unique<QPushButton>("&Cancel");
|
|
|
|
QHBoxLayout *buttomLayout = new QHBoxLayout;
|
|
buttomLayout->addWidget(fSave.get());
|
|
buttomLayout->addWidget(fCancel.get());
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
|
mainLayout->addLayout(titleLayout);
|
|
mainLayout->addWidget(fParamTable.get());
|
|
mainLayout->addLayout(checkLayout);
|
|
mainLayout->addLayout(modifyLayout);
|
|
mainLayout->addLayout(buttomLayout);
|
|
|
|
setLayout(mainLayout);
|
|
|
|
connect(fParamTable.get(), SIGNAL(cellChanged(int, int)), this, SLOT(handleCellChanged(int, int)));
|
|
|
|
connect(fCheckSpecific.get(), SIGNAL(pressed()), this, SLOT(checkSpecific()));
|
|
connect(fCheckAll.get(), SIGNAL(pressed()), this, SLOT(checkAll()));
|
|
connect(fUnCheckAll.get(), SIGNAL(pressed()), this, SLOT(unCheckAll()));
|
|
|
|
connect(fModifyAuto.get(), SIGNAL(pressed()), this, SLOT(modifyAuto()));
|
|
connect(fModifySelected.get(), SIGNAL(pressed()), this, SLOT(modifyChecked()));
|
|
connect(fSave.get(), SIGNAL(pressed()), this, SLOT(saveAndQuit()));
|
|
connect(fCancel.get(), SIGNAL(pressed()), this, SLOT(reject()));
|
|
|
|
fModSelect = new PModSelect(this);
|
|
connect(fModSelect, SIGNAL(scale(bool,double,bool)), this, SLOT(handleModSelect(bool,double,bool)));
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Handles changes to cells in the parameter table.
|
|
* @details Performs validation when table cells are modified:
|
|
* - Column 0 (name): Prevents selection of fixed parameters (step == 0)
|
|
* - Column 2 (step): Validates that entered values are valid numbers
|
|
*
|
|
* If an invalid step value is entered, the cell is restored to its
|
|
* previous value from fParamVec.
|
|
*
|
|
* @param row The row index of the changed cell
|
|
* @param column The column index of the changed cell
|
|
*/
|
|
void PMusrStep::handleCellChanged(int row, int column)
|
|
{
|
|
QString str;
|
|
bool ok;
|
|
if (column == 0) {
|
|
str = fParamTable->item(row, 2)->text();
|
|
if ((fParamTable->item(row, column)->checkState() == Qt::Checked) &&
|
|
((str == "0") || (str == "0.0"))) {
|
|
fParamTable->item(row, column)->setCheckState(Qt::Unchecked);
|
|
QMessageBox::warning(nullptr, "WARNING", "You cannot select a fixed value (step == 0).");
|
|
}
|
|
} else if (column == 2) {
|
|
str = fParamTable->item(row, column)->text();
|
|
str.toDouble(&ok); // result is of no interest
|
|
if (ok) {
|
|
fParamVec[row].step = str;
|
|
} else {
|
|
fParamTable->item(row, column)->setText(fParamVec[row].step);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Allows selection of parameters matching a template string.
|
|
* @details Opens an input dialog where the user can enter a template string.
|
|
* All parameters whose names contain this template (case-sensitive) are
|
|
* then checked, except for fixed parameters (step == 0 or "0.0").
|
|
*/
|
|
void PMusrStep::checkSpecific()
|
|
{
|
|
bool ok;
|
|
QString str = QInputDialog::getText(this, "Enter Param Name Template", "Template:",
|
|
QLineEdit::Normal, "", &ok);
|
|
if (!ok)
|
|
return;
|
|
|
|
QString step("");
|
|
for (int i=0; i<fParamTable->rowCount(); i++) {
|
|
if (fParamTable->item(i,0)->text().contains(str)) {
|
|
step = fParamTable->item(i,2)->text();
|
|
if ((step != "0") && (step != "0.0"))
|
|
fParamTable->item(i,0)->setCheckState(Qt::Checked);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Selects all non-fixed parameters in the table.
|
|
* @details Iterates through all rows and checks parameters whose step
|
|
* value is not "0" or "0.0" (fixed parameters remain unchecked).
|
|
*/
|
|
void PMusrStep::checkAll()
|
|
{
|
|
QString str("");
|
|
for (int i=0; i<fParamTable->rowCount(); i++) {
|
|
str = fParamTable->item(i,2)->text();
|
|
if ((str != "0") && (str != "0.0"))
|
|
fParamTable->item(i,0)->setCheckState(Qt::Checked);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Deselects all parameters in the table.
|
|
* @details Iterates through all rows and unchecks every parameter,
|
|
* regardless of whether it is fixed or not.
|
|
*/
|
|
void PMusrStep::unCheckAll()
|
|
{
|
|
for (int i=0; i<fParamTable->rowCount(); i++) {
|
|
fParamTable->item(i,0)->setCheckState(Qt::Unchecked);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Applies automatic step size modification to all non-fixed parameters.
|
|
* @details Iterates through all parameters and applies automatic scaling
|
|
* using lookupTable() and adoptStep(). Fixed parameters (step == 0) are
|
|
* skipped. Both the table display and the internal fParamVec are updated.
|
|
*/
|
|
void PMusrStep::modifyAuto()
|
|
{
|
|
QString str;
|
|
bool absVal;
|
|
double factor;
|
|
for (int i=0; i<fParamTable->rowCount(); i++) {
|
|
str = fParamTable->item(i,2)->text();
|
|
if ((str != "0") && (str != "0.0")) {
|
|
factor = lookupTable(fParamTable->item(i,0)->text(), absVal);
|
|
str = adoptStep(fParamTable->item(i,1)->text(), factor, absVal);
|
|
fParamTable->item(i,2)->setText(str);
|
|
fParamVec[i].step = str;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Opens the modification dialog for checked parameters.
|
|
* @details Shows the PModSelect dialog, which allows the user to choose
|
|
* between automatic scaling and manual factor-based scaling for the
|
|
* currently selected parameters.
|
|
*/
|
|
void PMusrStep::modifyChecked()
|
|
{
|
|
fModSelect->show();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Processes the scaling selection from the PModSelect dialog.
|
|
* @details Applies the specified scaling operation to all checked parameters.
|
|
* If automatic mode is selected, the factor is determined by lookupTable()
|
|
* for each parameter individually.
|
|
*
|
|
* @param automatic If true, use automatic scaling based on parameter names
|
|
* @param factor The scaling factor (used when automatic is false)
|
|
* @param absVal If true, factor is used as absolute value; if false, as multiplier
|
|
*/
|
|
void PMusrStep::handleModSelect(bool automatic, double factor, bool absVal)
|
|
{
|
|
QString str;
|
|
for (int i=0; i<fParamTable->rowCount(); i++) {
|
|
if (fParamTable->item(i,0)->checkState() == Qt::Checked) {
|
|
if (automatic) {
|
|
factor = lookupTable(fParamTable->item(i,0)->text(), absVal);
|
|
}
|
|
str = adoptStep(fParamTable->item(i,1)->text(), factor, absVal);
|
|
fParamTable->item(i,2)->setText(str);
|
|
fParamVec[i].step = str;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Saves the modified msr-file and closes the dialog.
|
|
* @details Calls writeMsrFile() to save all modifications, then
|
|
* accepts the dialog to close it with a success status.
|
|
*/
|
|
void PMusrStep::saveAndQuit()
|
|
{
|
|
writeMsrFile();
|
|
accept();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Initializes a PParam structure with empty strings.
|
|
* @details Sets all fields of the parameter structure to empty QString
|
|
* objects. Used before populating a new parameter from file data.
|
|
*
|
|
* @param param Reference to the PParam structure to initialize
|
|
*/
|
|
void PMusrStep::initParam(PParam ¶m)
|
|
{
|
|
param.number = "";
|
|
param.name = "";
|
|
param.value = "";
|
|
param.step = "";
|
|
param.posErr = "";
|
|
param.boundLow = "";
|
|
param.boundHigh = "";
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Determines the appropriate step size factor based on parameter name.
|
|
* @details Uses common muSR parameter naming conventions to identify
|
|
* parameter types and return appropriate scaling factors:
|
|
* - freq, frq, field: factor = 1e-3 (high precision for frequency/field)
|
|
* - lambda, sigma, rlx, rate: factor = 0.1 (relaxation rates)
|
|
* - phase, phs: factor = 5.0 (absolute value, for phase parameters)
|
|
* - N0, Nrm, N_bkg, Bgr: factor = 0.01 (normalization/background)
|
|
* - default: factor = 0.01
|
|
*
|
|
* The comparison is case-insensitive and matches the start of the
|
|
* parameter name.
|
|
*
|
|
* @param str The parameter name to look up
|
|
* @param absVal Output flag set to true if factor should be used as absolute value
|
|
* @return The scaling factor to apply
|
|
*/
|
|
double PMusrStep::lookupTable(const QString str, bool &absVal)
|
|
{
|
|
double factor = 0.01;
|
|
absVal = false;
|
|
|
|
if (str.startsWith("freq", Qt::CaseInsensitive) ||
|
|
str.startsWith("frq", Qt::CaseInsensitive) ||
|
|
str.startsWith("field", Qt::CaseInsensitive)) {
|
|
factor = 1.0e-3;
|
|
} else if (str.startsWith("lambda", Qt::CaseInsensitive) ||
|
|
str.startsWith("sigma", Qt::CaseInsensitive) ||
|
|
str.startsWith("rlx", Qt::CaseInsensitive) ||
|
|
str.startsWith("rate", Qt::CaseInsensitive)) {
|
|
factor = 0.1;
|
|
} else if (str.startsWith("phase", Qt::CaseInsensitive) ||
|
|
str.startsWith("phs", Qt::CaseInsensitive)) {
|
|
factor = 5.0;
|
|
absVal = true;
|
|
} else if (str.startsWith("N0", Qt::CaseInsensitive) ||
|
|
str.startsWith("Nrm", Qt::CaseInsensitive) ||
|
|
str.startsWith("N_bkg", Qt::CaseInsensitive) ||
|
|
str.startsWith("Bgr", Qt::CaseInsensitive)) {
|
|
factor = 0.01;
|
|
}
|
|
|
|
return factor;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Calculates the new step value based on current value and factor.
|
|
* @details Computes the new step size using either:
|
|
* - Absolute mode (absVal=true): Returns the factor directly as the step
|
|
* - Multiplicative mode (absVal=false): Returns factor * current_value
|
|
*
|
|
* @param str The current parameter value as a string
|
|
* @param factor The scaling factor to apply
|
|
* @param absVal If true, return factor directly; if false, multiply by value
|
|
* @return The new step value as a QString
|
|
*/
|
|
QString PMusrStep::adoptStep(const QString str, double factor, bool absVal)
|
|
{
|
|
bool ok;
|
|
double dval = str.toDouble(&ok);
|
|
|
|
QString step("");
|
|
if (absVal)
|
|
step = QString("%1").arg(factor);
|
|
else
|
|
step = QString("%1").arg(factor*dval);
|
|
|
|
return step;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Reads and parses the FITPARAMETER block from the msr-file.
|
|
* @details Opens the msr-file specified in fMsrFileName and extracts
|
|
* all parameters from the FITPARAMETER block (between FITPARAMETER
|
|
* and THEORY keywords). Each parameter line must have either 5 or 7
|
|
* space-separated fields:
|
|
* - 5 fields: number, name, value, step, posErr
|
|
* - 7 fields: above plus boundLow, boundHigh
|
|
*
|
|
* Comment lines (starting with #) and empty lines are ignored.
|
|
*
|
|
* @return 1 on success
|
|
* @return -1 if file cannot be opened
|
|
* @return -2 if a parameter line has invalid format
|
|
*/
|
|
int PMusrStep::readMsrFile()
|
|
{
|
|
fParamVec.clear();
|
|
|
|
QFile fin(fMsrFileName);
|
|
|
|
if (!fin.open(QIODevice::ReadOnly|QIODevice::Text))
|
|
return -1;
|
|
|
|
bool done = false, parameter = false;
|
|
QByteArray line;
|
|
QString str;
|
|
QStringList strL;
|
|
PParam param;
|
|
|
|
while(!done && !fin.atEnd()) {
|
|
line = fin.readLine();
|
|
str = line.data();
|
|
str = str.trimmed();
|
|
if (str.isEmpty() || str.startsWith("#")) {
|
|
continue;
|
|
} else if (str.startsWith("FITPARAMETER")) {
|
|
parameter = true;
|
|
continue;
|
|
} else if (str.startsWith("THEORY")) {
|
|
done = true;
|
|
continue;
|
|
}
|
|
|
|
if (parameter) {
|
|
strL = str.split(" ", Qt::SkipEmptyParts);
|
|
if ((strL.size() != 5) && (strL.size() != 7)) {
|
|
fin.close();
|
|
return -2;
|
|
}
|
|
initParam(param);
|
|
|
|
param.number = strL[0];
|
|
param.name = strL[1];
|
|
param.value = strL[2];
|
|
param.step = strL[3];
|
|
param.posErr = strL[4];
|
|
if (strL.size() == 7) {
|
|
param.boundLow = strL[5];
|
|
param.boundHigh= strL[6];
|
|
}
|
|
fParamVec.push_back(param);
|
|
}
|
|
}
|
|
|
|
fin.close();
|
|
|
|
return 1;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Writes the modified parameters back to the msr-file.
|
|
* @details Reads the entire original msr-file, then rewrites it with
|
|
* updated step values in the FITPARAMETER block. All other content
|
|
* is preserved unchanged. A "*** FIT DID NOT CONVERGE ***" marker is
|
|
* appended to indicate the file has been modified.
|
|
*
|
|
* @return 1 on success
|
|
* @return -1 if input file cannot be opened for reading
|
|
* @return -2 if output file cannot be opened for writing
|
|
*/
|
|
int PMusrStep::writeMsrFile()
|
|
{
|
|
// read whole msr-file
|
|
QFile fin(fMsrFileName);
|
|
|
|
if (!fin.open(QIODevice::ReadOnly|QIODevice::Text))
|
|
return -1;
|
|
|
|
QByteArray data = fin.readAll();
|
|
|
|
fin.close();
|
|
|
|
QFile fileOut(fMsrFileName);
|
|
|
|
if (!fileOut.open(QIODevice::WriteOnly|QIODevice::Text))
|
|
return -2;
|
|
|
|
QTextStream fout(&fileOut);
|
|
|
|
int idx = 0;
|
|
QString line, paramLine;
|
|
bool done = false;
|
|
bool paramBlock = false;
|
|
do {
|
|
line = getLine(data, idx);
|
|
if ((idx == -1) || (idx == data.size())) {
|
|
done = true;
|
|
} else {
|
|
if (line.startsWith("FITPARAMETER")) {
|
|
paramBlock = true;
|
|
} else if (line.startsWith("THEORY")) {
|
|
paramBlock = false;
|
|
}
|
|
if (paramBlock) {
|
|
paramLine = updateParamLine(line);
|
|
if (paramLine == "") // comment line, etc.
|
|
fout << line << "\n";
|
|
else
|
|
fout << paramLine << "\n";
|
|
} else {
|
|
fout << line << "\n";
|
|
}
|
|
}
|
|
} while (!done);
|
|
fout << "*** FIT DID NOT CONVERGE ***\n";
|
|
|
|
fileOut.close();
|
|
|
|
return 1;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Extracts a single line from a byte array at the given position.
|
|
* @details Searches for the next newline character starting at idx and
|
|
* extracts the text between the current position and the newline.
|
|
* The idx parameter is updated to point to the character after the newline.
|
|
*
|
|
* @param data Reference to the byte array containing file data
|
|
* @param idx Reference to the current position index (updated to next line start)
|
|
* @return The extracted line as a QString, or empty string if no newline found
|
|
*/
|
|
QString PMusrStep::getLine(QByteArray &data, int &idx)
|
|
{
|
|
int newIdx = data.indexOf('\n', idx);
|
|
QString line("");
|
|
|
|
if (newIdx != -1) {
|
|
line = data.mid(idx, newIdx-idx).data();
|
|
idx = newIdx+1;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/**
|
|
* @brief Reconstructs a parameter line with updated values.
|
|
* @details Searches fParamVec for a parameter matching the given line,
|
|
* then formats a new parameter line with proper field widths:
|
|
* - Column 1 (10 chars, right-aligned): parameter number
|
|
* - Column 2 (12+ chars, left-aligned): parameter name
|
|
* - Columns 3-5 (11 chars each): value, step, posErr
|
|
* - Columns 6-7 (11 chars each, optional): boundLow, boundHigh
|
|
*
|
|
* The parameter is identified by finding its name sandwiched between
|
|
* spaces in the input string to avoid partial matches.
|
|
*
|
|
* @param str The original parameter line to update
|
|
* @return The reconstructed parameter line, or empty string if no match found
|
|
*/
|
|
QString PMusrStep::updateParamLine(const QString str)
|
|
{
|
|
// find proper parameter index
|
|
int idx = -1;
|
|
QString paramStr;
|
|
for (int i=0; i<fParamVec.size(); i++) {
|
|
// parameter name need to be sandwiched between spaces to prevent that e.g. 'MinusOne' is overwritten by an existing 'One'
|
|
paramStr = " " + fParamVec[i].name + " ";
|
|
if (str.contains(paramStr)) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (idx == -1)
|
|
return QString("");
|
|
|
|
// construct new parameter line
|
|
QString result;
|
|
QString fstr;
|
|
QTextStream ss(&result);
|
|
ss.setFieldWidth(10);
|
|
ss.setFieldAlignment(QTextStream::AlignRight);
|
|
fstr = fParamVec[idx].number + " ";
|
|
ss << fstr;
|
|
ss.setFieldAlignment(QTextStream::AlignLeft);
|
|
int width = 12;
|
|
if (fParamVec[idx].name.length() >= 12)
|
|
width = fParamVec[idx].name.length()+1;
|
|
ss.setFieldWidth(width);
|
|
ss << fParamVec[idx].name;
|
|
ss.setFieldWidth(11);
|
|
ss << fParamVec[idx].value;
|
|
ss << fParamVec[idx].step;
|
|
ss << fParamVec[idx].posErr;
|
|
if (fParamVec[idx].boundLow != "") {
|
|
ss << fParamVec[idx].boundLow;
|
|
ss << fParamVec[idx].boundHigh;
|
|
}
|
|
|
|
return result;
|
|
}
|