#include "nmParameterField.h" #include "iParaItemCtrl.h" #include "iParameter.h" #include "iSysParaHelper.h" #include "iUnitHelper.h" #include "mModuleDefines.h" #include #include #include #include #include #include #include #include namespace { /* Qt 4.8的lupdate需要显式标记间接翻译字符串。 */ static const char* const s_pFieldTranslationMarkers[] = { QT_TRANSLATE_NOOP("nmParameterField", "The parameter layout is unavailable."), QT_TRANSLATE_NOOP("nmParameterField", "Parameter definition not found: %1"), QT_TRANSLATE_NOOP("nmParameterField", "Failed to bind the unit for parameter: %1") }; QString fieldText(const char* pText) { Q_UNUSED(s_pFieldTranslationMarkers); return QCoreApplication::translate("nmParameterField", pText); } QString validationToolTip(const QString& sText, bool bValid) { if (bValid) { return sText; } // 无效提示使用富文本着色,业务文本必须先转义以免被当作HTML解析。 QString sEscaped = sText; sEscaped.replace("&", "&"); sEscaped.replace("<", "<"); sEscaped.replace(">", ">"); sEscaped.replace("\"", """); sEscaped.replace("\n", "
"); return "" + sEscaped + ""; } QChar superscriptDigit(QChar ch) { switch (ch.unicode()) { case '0': return QChar(0x2070); case '1': return QChar(0x00B9); case '2': return QChar(0x00B2); case '3': return QChar(0x00B3); case '4': return QChar(0x2074); case '5': return QChar(0x2075); case '6': return QChar(0x2076); case '7': return QChar(0x2077); case '8': return QChar(0x2078); case '9': return QChar(0x2079); default: return QChar(); } } QChar plainDigitFromSuperscript(QChar ch) { switch (ch.unicode()) { case 0x2070: return QLatin1Char('0'); case 0x00B9: return QLatin1Char('1'); case 0x00B2: return QLatin1Char('2'); case 0x00B3: return QLatin1Char('3'); case 0x2074: return QLatin1Char('4'); case 0x2075: return QLatin1Char('5'); case 0x2076: return QLatin1Char('6'); case 0x2077: return QLatin1Char('7'); case 0x2078: return QLatin1Char('8'); case 0x2079: return QLatin1Char('9'); default: return QChar(); } } QString toSuperscriptUnit(const QString& sUnit) { QString sResult; sResult.reserve(sUnit.size()); for (int nIndex = 0; nIndex < sUnit.size(); ++nIndex) { if (sUnit[nIndex] == QLatin1Char('^') && nIndex + 1 < sUnit.size()) { QString sDigits; int nDigitIndex = nIndex + 1; while (nDigitIndex < sUnit.size()) { QChar chSuperscript = superscriptDigit(sUnit[nDigitIndex]); if (chSuperscript.isNull()) { break; } sDigits.append(chSuperscript); ++nDigitIndex; } if (!sDigits.isEmpty()) { sResult.append(sDigits); nIndex = nDigitIndex - 1; continue; } } sResult.append(sUnit[nIndex]); } return sResult; } QString fromSuperscriptUnit(const QString& sUnit) { QString sResult; sResult.reserve(sUnit.size() * 2); bool bInSuperscriptDigits = false; for (int nIndex = 0; nIndex < sUnit.size(); ++nIndex) { QChar chDigit = plainDigitFromSuperscript(sUnit[nIndex]); if (!chDigit.isNull()) { if (!bInSuperscriptDigits) { sResult.append(QLatin1Char('^')); bInSuperscriptDigits = true; } sResult.append(chDigit); continue; } bInSuperscriptDigits = false; sResult.append(sUnit[nIndex]); } return sResult; } void appendUnique(QStringList& listUnits, const QString& sUnit) { if (!sUnit.isEmpty() && !listUnits.contains(sUnit)) { listUnits << sUnit; } } void appendUnitSpellingVariants(QStringList& listUnits, const QString& sUnit) { QString sVariant = toSuperscriptUnit(sUnit); appendUnique(listUnits, sVariant); sVariant = fromSuperscriptUnit(sUnit); appendUnique(listUnits, sVariant); sVariant = sUnit; sVariant.replace(QLatin1String("."), QString(QChar(0x00B7))); appendUnique(listUnits, sVariant); sVariant = sUnit; sVariant.replace(QString(QChar(0x00B7)), QLatin1String(".")); appendUnique(listUnits, sVariant); sVariant = sUnit; sVariant.replace(QLatin1String("/D"), QLatin1String("/d")); appendUnique(listUnits, sVariant); sVariant = sUnit; sVariant.replace(QLatin1String("/d"), QLatin1String("/D")); appendUnique(listUnits, sVariant); if (sUnit == QLatin1String("Darcy")) { appendUnique(listUnits, QLatin1String("D")); } else if (sUnit == QLatin1String("D")) { appendUnique(listUnits, QLatin1String("Darcy")); } } QStringList unitSpellingCandidates(const QString& sUnit) { QStringList listUnits; appendUnique(listUnits, sUnit); for (int nIndex = 0; nIndex < listUnits.size(); ++nIndex) { appendUnitSpellingVariants(listUnits, listUnits[nIndex]); } return listUnits; } QString resolveAvailableUnitSpelling(const QString& sUnit) { QStringList listUnits = unitSpellingCandidates(sUnit); for (int nIndex = 0; nIndex < listUnits.size(); ++nIndex) { if (iUnitHelper::getUnitGroupByUnit(listUnits[nIndex]) != nullptr) { return listUnits[nIndex]; } } return sUnit; } void resolveParameterUnitSpelling(iParameter* pParameter) { if (pParameter == nullptr || pParameter->m_sUnit.isEmpty()) { return; } // 参数XML和单位XML可能使用不同排版写法,绑定前切到当前单位表可识别的单位名。 QString sResolvedUnit = resolveAvailableUnitSpelling(pParameter->m_sUnit); if (sResolvedUnit != pParameter->m_sUnit) { pParameter->m_sUnit = sResolvedUnit; } } } nmParameterFieldInfo::nmParameterFieldInfo() : m_eEditorType(NM_PARAMETER_EDITOR_NUMBER), m_bUnitEnabled(true) { } nmParameterField::nmParameterField( const nmParameterFieldInfo& oFieldInfo, nmIParameterUnitService* pUnitService, QObject* pParent) : QObject(pParent), m_oFieldInfo(oFieldInfo), m_pUnitService(pUnitService), m_pParameter(nullptr), m_pLabel(nullptr), m_pLineEdit(nullptr), m_pValueComboBox(nullptr), m_pUnitComboBox(nullptr), m_pUnitLabel(nullptr), m_clrNormalText(), m_sExternalValidationError(), m_bVisible(true) { } nmParameterField::~nmParameterField() { m_pUnitService = nullptr; m_pParameter = nullptr; m_pLabel = nullptr; m_pLineEdit = nullptr; m_pValueComboBox = nullptr; m_pUnitComboBox = nullptr; m_pUnitLabel = nullptr; } bool nmParameterField::initialize(QGridLayout* pLayout, int nRow, int nColumnOffset, QString& sError) { if (pLayout == nullptr) { sError = fieldText("The parameter layout is unavailable."); return false; } // 第一步:文本和枚举字段不依赖XML数值定义。 if (isText()) { createTextEditor(pLayout, nRow, nColumnOffset); sError.clear(); return true; } if (isEnum()) { createEnumEditor(pLayout, nRow, nColumnOffset); sError.clear(); return true; } // 第二步:数值字段取得NM系列的基础参数定义。 m_pParameter = _paraHelper->getPara( m_oFieldInfo.m_sBaseParameterId, s_Nm_Serie); if (m_pParameter == nullptr) { sError = fieldText("Parameter definition not found: %1") .arg(m_oFieldInfo.m_sBaseParameterId); return false; } resolveParameterUnitSpelling(m_pParameter); // 第三步:创建标签、输入框和单位列。 return createNumericEditor(pLayout, nRow, nColumnOffset, sError); } QString nmParameterField::parameterId() const { return m_oFieldInfo.m_sParameterId; } QString nmParameterField::baseParameterId() const { return m_oFieldInfo.m_sBaseParameterId; } bool nmParameterField::isEnum() const { return m_oFieldInfo.m_eEditorType == NM_PARAMETER_EDITOR_ENUM; } bool nmParameterField::isText() const { return m_oFieldInfo.m_eEditorType == NM_PARAMETER_EDITOR_TEXT; } void nmParameterField::setValueRightAligned(bool bRightAligned) { if (m_pLineEdit != nullptr) { m_pLineEdit->setAlignment( (bRightAligned ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter); } } int nmParameterField::editorMinimumWidth() { return 140; } int nmParameterField::unitMinimumWidth() { return 86; } int nmParameterField::editorHeight() { return 25; } bool nmParameterField::createNumericEditor( QGridLayout* pLayout, int nRow, int nColumnOffset, QString& sError) { // 第一步:创建统一尺寸的标签和值输入框,保证各业务面板列对齐。 QWidget* pParentWidget = pLayout->parentWidget(); m_pLabel = new QLabel(m_pParameter->m_sAlias, pParentWidget); m_pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pLabel->setWordWrap(false); m_pLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); m_pLineEdit = new QLineEdit(pParentWidget); m_clrNormalText = m_pLineEdit->palette().color(QPalette::Text); m_pLineEdit->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_pLineEdit->setFixedHeight(editorHeight()); m_pLineEdit->setMinimumWidth(editorMinimumWidth()); m_pLineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); pLayout->addWidget(m_pLabel, nRow, nColumnOffset); pLayout->addWidget(m_pLineEdit, nRow, nColumnOffset + 1); // 第二步:有单位字段交给窗口服务绑定;无单位字段保留占位列。 if (m_oFieldInfo.m_bUnitEnabled && !m_pParameter->m_sUnit.isEmpty()) { m_pUnitComboBox = new QComboBox(pParentWidget); m_pUnitComboBox->setFixedHeight(editorHeight()); m_pUnitComboBox->setMinimumWidth(unitMinimumWidth()); m_pUnitComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); pLayout->addWidget(m_pUnitComboBox, nRow, nColumnOffset + 2); if (m_pUnitService == nullptr || !m_pUnitService->bindUnitField( m_pLineEdit, m_pUnitComboBox, m_oFieldInfo.m_sBaseParameterId)) { sError = fieldText( "Failed to bind the unit for parameter: %1") .arg(m_oFieldInfo.m_sBaseParameterId); return false; } } else { m_pUnitLabel = new QLabel(pParentWidget); m_pUnitLabel->setMinimumWidth(unitMinimumWidth()); m_pUnitLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); pLayout->addWidget(m_pUnitLabel, nRow, nColumnOffset + 2); } updateValidationAppearance(true); sError.clear(); return true; } void nmParameterField::createTextEditor(QGridLayout* pLayout, int nRow, int nColumnOffset) { QWidget* pParentWidget = pLayout->parentWidget(); m_pLabel = new QLabel(m_oFieldInfo.m_sLabel, pParentWidget); m_pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pLabel->setWordWrap(false); m_pLineEdit = new QLineEdit(pParentWidget); m_clrNormalText = m_pLineEdit->palette().color(QPalette::Text); m_pLineEdit->setFixedHeight(editorHeight()); m_pLineEdit->setMinimumWidth(editorMinimumWidth()); m_pLineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_pUnitLabel = new QLabel(pParentWidget); m_pUnitLabel->setMinimumWidth(unitMinimumWidth()); m_pUnitLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); pLayout->addWidget(m_pLabel, nRow, nColumnOffset); pLayout->addWidget(m_pLineEdit, nRow, nColumnOffset + 1); pLayout->addWidget(m_pUnitLabel, nRow, nColumnOffset + 2); } void nmParameterField::createEnumEditor(QGridLayout* pLayout, int nRow, int nColumnOffset) { QWidget* pParentWidget = pLayout->parentWidget(); m_pLabel = new QLabel(m_oFieldInfo.m_sLabel, pParentWidget); m_pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pLabel->setWordWrap(false); m_pValueComboBox = new QComboBox(pParentWidget); m_pValueComboBox->setFixedHeight(editorHeight()); m_pValueComboBox->setMinimumWidth(editorMinimumWidth()); m_pValueComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); for (int nIndex = 0; nIndex < m_oFieldInfo.m_listOptions.size(); ++nIndex) { const nmParameterEnumOption& oOption = m_oFieldInfo.m_listOptions[nIndex]; m_pValueComboBox->addItem(oOption.m_sText, oOption.m_sCode); } m_pUnitLabel = new QLabel(pParentWidget); m_pUnitLabel->setMinimumWidth(unitMinimumWidth()); m_pUnitLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); pLayout->addWidget(m_pLabel, nRow, nColumnOffset); pLayout->addWidget(m_pValueComboBox, nRow, nColumnOffset + 1); pLayout->addWidget(m_pUnitLabel, nRow, nColumnOffset + 2); } void nmParameterField::setBaseValue(const QVariant& oValue) { if (m_pLineEdit == nullptr || !oValue.isValid()) { return; } // 程序回填仍走控件标准写值接口,由所属页面的刷新标记屏蔽业务提交。 if (isText()) { m_pLineEdit->setText(oValue.toString()); } else if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) { m_pUnitService->setUnitBaseValue( m_pLineEdit, oValue.toDouble()); } else { m_pLineEdit->setText(formatBaseValue(oValue.toDouble())); } validate(); } QVariant nmParameterField::baseValue(bool& bValid) { bValid = validate(); if (!bValid || m_pLineEdit == nullptr) { return QVariant(); } // 单位字段始终向业务层返回基准单位值,界面显示单位不进入数据模型。 if (isText()) { return QVariant(m_pLineEdit->text().trimmed()); } if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) { return QVariant(m_pUnitService->unitBaseValue(m_pLineEdit)); } bool bNumberOk = false; double dValue = m_pLineEdit->text().trimmed().toDouble(&bNumberOk); return bNumberOk ? QVariant(dValue) : QVariant(); } void nmParameterField::setEnumCode(const QString& sCode) { if (m_pValueComboBox == nullptr) { return; } int nIndex = m_pValueComboBox->findData(sCode); m_pValueComboBox->setCurrentIndex(nIndex >= 0 ? nIndex : 0); } QString nmParameterField::enumCode() const { if (m_pValueComboBox == nullptr || m_pValueComboBox->currentIndex() < 0) { return QString(); } return m_pValueComboBox->itemData( m_pValueComboBox->currentIndex()).toString(); } void nmParameterField::setExternalValidationError(const QString& sError) { m_sExternalValidationError = sError; validate(); } bool nmParameterField::validate() { if (!m_bVisible || isEnum()) { return true; } if (m_pLineEdit == nullptr) { return false; } // 第一步:按编辑器类型执行本地格式、范围和整数约束校验。 bool bValid = false; if (isText()) { bValid = true; } else if (m_pParameter == nullptr) { bValid = false; } else if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) { bValid = m_pUnitService->isUnitInputValid(m_pLineEdit); } else { bool bNumberOk = false; double dValue = m_pLineEdit->text().trimmed() .toDouble(&bNumberOk); bValid = bNumberOk && dValue == dValue && dValue >= m_pParameter->m_dMin && dValue <= m_pParameter->m_dMax; if (bValid && m_oFieldInfo.m_eEditorType == NM_PARAMETER_EDITOR_INTEGER) { bValid = dValue == static_cast(dValue); } } // 第二步:叠加面板提供的跨字段业务规则,例如射孔区间关系。 bValid = bValid && m_sExternalValidationError.isEmpty(); updateValidationAppearance(bValid); return bValid; } void nmParameterField::setFieldVisible(bool bVisible) { m_bVisible = bVisible; if (m_pLabel != nullptr) { m_pLabel->setVisible(bVisible); } if (m_pLineEdit != nullptr) { m_pLineEdit->setVisible(bVisible); } if (m_pValueComboBox != nullptr) { m_pValueComboBox->setVisible(bVisible); } if (m_pUnitComboBox != nullptr) { m_pUnitComboBox->setVisible(bVisible); } if (m_pUnitLabel != nullptr) { m_pUnitLabel->setVisible(bVisible); } } bool nmParameterField::isFieldVisible() const { return m_bVisible; } QLineEdit* nmParameterField::valueEditor() const { return m_pLineEdit; } QComboBox* nmParameterField::enumEditor() const { return m_pValueComboBox; } QString nmParameterField::formatBaseValue(double dValue) const { if (m_pParameter == nullptr) { return QString::number(dValue, 'g', 15); } if (m_oFieldInfo.m_eEditorType == NM_PARAMETER_EDITOR_INTEGER || m_pParameter->m_nDigit == 0) { return QString::number(dValue, 'f', 0); } if (m_pParameter->m_bScientific) { return QString::number(dValue, 'e', m_pParameter->m_nDigit); } // 普通小数去除无意义尾零,避免界面出现与有效精度无关的冗长数字。 QString sValue = QString::number( dValue, 'f', m_pParameter->m_nDigit); while (sValue.contains('.') && sValue.endsWith('0')) { sValue.chop(1); } if (sValue.endsWith('.')) { sValue.chop(1); } return sValue; } void nmParameterField::updateValidationAppearance(bool bValid) { if (m_pLineEdit == nullptr) { return; } // 第一步:只调整文字颜色,不覆盖应用主题提供的输入框背景和边框。 QPalette oPalette = m_pLineEdit->palette(); oPalette.setColor(QPalette::Text, bValid ? m_clrNormalText : QColor(Qt::red)); m_pLineEdit->setPalette(oPalette); // 第二步:组合参数名称、当前单位范围和跨字段错误作为统一帮助信息。 QString sHelp = m_oFieldInfo.m_sLabel; if (m_pParameter != nullptr) { QString sCurrentUnit; iUnitGroup* pUnitGroup = nullptr; if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) { sCurrentUnit = m_pUnitService->currentUnit(m_pLineEdit); pUnitGroup = m_pUnitService->unitGroup(m_pLineEdit); } QString sRange = iParaItemCtrl::getScaleStrOf( m_pParameter, sCurrentUnit, pUnitGroup); sHelp = m_pParameter->m_sAlias; if (!m_pParameter->m_sName.isEmpty()) { if (!sHelp.isEmpty()) { sHelp += "[" + m_pParameter->m_sName + "]"; } else { sHelp = m_pParameter->m_sName; } } if (!sRange.isEmpty()) { if (!sHelp.isEmpty()) { sHelp += "\n"; } sHelp += sRange; } } if (!m_sExternalValidationError.isEmpty()) { if (!sHelp.isEmpty()) { sHelp += "\n"; } sHelp += m_sExternalValidationError; } m_pLineEdit->setToolTip(validationToolTip(sHelp, bValid)); m_pLineEdit->setWhatsThis(sHelp); if (m_pLabel != nullptr) { m_pLabel->setWhatsThis(sHelp); } }