/* Copyright (C) 2013 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, * or distributed except according to the terms contained in the LICENSE file. */ #include "blackmisc/pq/measurementunit.h" #include "blackmisc/pq/physicalquantity.h" #include "blackmisc/pq/pqstring.h" #include "blackmisc/propertyindexref.h" #include "blackmisc/pq/length.h" #include "blackmisc/pq/pressure.h" #include "blackmisc/pq/frequency.h" #include "blackmisc/pq/mass.h" #include "blackmisc/pq/temperature.h" #include "blackmisc/pq/speed.h" #include "blackmisc/pq/angle.h" #include "blackmisc/pq/time.h" #include "blackmisc/pq/acceleration.h" #include "blackmisc/propertyindexvariantmap.h" #include "blackmisc/comparefunctions.h" #include "blackmisc/stringutils.h" #include "blackmisc/dictionary.h" #include "blackmisc/verify.h" #include #include #include #include #include #include #include #include #include #include namespace BlackMisc { namespace PhysicalQuantities { template const MU &CPhysicalQuantity::getUnit() const { return m_unit; } template void CPhysicalQuantity::setUnit(const MU &unit) { m_unit = unit; } template void CPhysicalQuantity::setUnitBySymbol(const QString &unitName) { m_unit = CMeasurementUnit::unitFromSymbol(unitName); } template QString CPhysicalQuantity::getUnitSymbol() const { return m_unit.getSymbol(true); } template CPhysicalQuantity::CPhysicalQuantity(double value, MU unit) : m_value(unit.isNull() ? 0.0 : value), m_unit(unit) { Q_ASSERT_X(!std::isnan(value), Q_FUNC_INFO, "nan value"); } template CPhysicalQuantity::CPhysicalQuantity(const QString &unitString) : m_value(0.0), m_unit(MU::nullUnit()) { this->parseFromString(unitString); } template bool CPhysicalQuantity::equals(const CPhysicalQuantity &other) const { if (this == &other) return true; if (this->isNull()) return other.isNull(); if (other.isNull()) return false; double diff = std::abs(m_value - other.value(m_unit)); return diff <= m_unit.getEpsilon(); } template CPhysicalQuantity &CPhysicalQuantity::operator +=(const CPhysicalQuantity &other) { m_value += other.value(m_unit); return *this; } template void CPhysicalQuantity::addValueSameUnit(double value) { m_value += value; } template void CPhysicalQuantity::substractValueSameUnit(double value) { m_value -= value; } template CPhysicalQuantity &CPhysicalQuantity::operator -=(const CPhysicalQuantity &other) { m_value -= other.value(m_unit); return *this; } template bool CPhysicalQuantity::isZeroEpsilonConsidered() const { return m_unit.isEpsilon(m_value); } template bool CPhysicalQuantity::isPositiveWithEpsilonConsidered() const { return !this->isZeroEpsilonConsidered() && m_value > 0; } template bool CPhysicalQuantity::isNegativeWithEpsilonConsidered() const { return !this->isZeroEpsilonConsidered() && m_value < 0; } template const PQ &CPhysicalQuantity::makePositive() { if (this->isNull() || qFuzzyIsNull(m_value)) { return *this->derived(); } if (m_value < 0) { m_value *= -1.0; } return *this->derived(); } template const PQ &CPhysicalQuantity::makeNegative() { if (this->isNull() || qFuzzyIsNull(m_value)) { return *this->derived(); } if (m_value > 0) { m_value *= -1.0; } return *this->derived(); } template PQ CPhysicalQuantity::abs() const { if (this->isNull() || qFuzzyIsNull(m_value)) { return *this->derived(); } if (m_value >= 0) { return *this->derived(); } PQ copy(*this->derived()); return copy.makePositive(); } template void CPhysicalQuantity::marshallToDbus(QDBusArgument &argument) const { constexpr double NaN = std::numeric_limits::quiet_NaN(); argument << (this->isNull() ? NaN : this->value(UnitClass::defaultUnit())); } template void CPhysicalQuantity::unmarshallFromDbus(const QDBusArgument &argument) { argument >> m_value; m_unit = UnitClass::defaultUnit(); if (std::isnan(m_value)) { this->setNull(); } } template void CPhysicalQuantity::marshallToDbus(QDBusArgument &argument, LosslessTag) const { argument << m_value; argument << m_unit; } template void CPhysicalQuantity::unmarshallFromDbus(const QDBusArgument &argument, LosslessTag) { argument >> m_value; argument >> m_unit; } template void CPhysicalQuantity::marshalToDataStream(QDataStream &stream) const { constexpr double NaN = std::numeric_limits::quiet_NaN(); stream << (this->isNull() ? NaN : this->value(UnitClass::defaultUnit())); } template void CPhysicalQuantity::unmarshalFromDataStream(QDataStream &stream) { stream >> m_value; m_unit = UnitClass::defaultUnit(); if (std::isnan(m_value)) { this->setNull(); } } template CPhysicalQuantity &CPhysicalQuantity::operator *=(double factor) { m_value *= factor; return *this; } template PQ CPhysicalQuantity::operator *(double factor) const { PQ copy = *derived(); copy *= factor; return copy; } template CPhysicalQuantity &CPhysicalQuantity::operator /=(double divisor) { m_value /= divisor; return *this; } template PQ CPhysicalQuantity::operator /(double divisor) const { PQ copy = *derived(); copy /= divisor; return copy; } template PQ CPhysicalQuantity::operator -() const { PQ copy = *derived(); copy *= -1; return copy; } template bool CPhysicalQuantity::lessThan(const CPhysicalQuantity &other) const { if (*this == other) return false; if (isNull() < other.isNull()) { return true; } if (isNull() > other.isNull()) { return false; } if (isNull() && other.isNull()) { return false; } return (m_value < other.value(m_unit)); } template PQ &CPhysicalQuantity::switchUnit(const MU &newUnit) { // NULL check: https://discordapp.com/channels/539048679160676382/539925070550794240/593151683698229258 if (m_unit == newUnit || this->isNull()) { return *derived(); } if (newUnit.isNull()) { this->setNull(); } else { m_value = newUnit.convertFrom(m_value, m_unit); m_unit = newUnit; } return *derived(); } template PQ CPhysicalQuantity::switchedUnit(const MU &newUnit) const { if (m_unit == newUnit || this->isNull()) { return *derived(); } PQ copy(*derived()); copy.switchUnit(newUnit); return copy; } template bool CPhysicalQuantity::isNull() const { return m_unit.isNull(); } template void CPhysicalQuantity::setNull() { m_value = 0; m_unit = MU::nullUnit(); } template double CPhysicalQuantity::value() const { if (this->isNull()) { return 0.0; } return m_value; } template void CPhysicalQuantity::setCurrentUnitValue(double value) { if (!this->isNull()) { m_value = value; } } template void CPhysicalQuantity::setValueSameUnit(double baseValue) { m_value = baseValue; } template QString CPhysicalQuantity::valueRoundedWithUnit(const MU &unit, int digits, bool withGroupSeparator, bool i18n) const { Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null"); if (this->isNull()) { return this->convertToQString(i18n); } return unit.makeRoundedQStringWithUnit(this->value(unit), digits, withGroupSeparator, i18n); } template QString CPhysicalQuantity::valueRoundedWithUnit(int digits, bool withGroupSeparator, bool i18n) const { if (this->isNull()) { return QStringLiteral("null"); } return this->valueRoundedWithUnit(m_unit, digits, withGroupSeparator, i18n); } template void CPhysicalQuantity::roundToEpsilon() { if (this->isNull()) { return; } m_value = m_unit.roundToEpsilon(m_value); } template double CPhysicalQuantity::valueRounded(MU unit, int digits) const { Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null"); return unit.roundValue(this->value(unit), digits); } template int CPhysicalQuantity::valueInteger(MU unit) const { Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null"); const double v = this->value(unit); return qRound(v); } template int CPhysicalQuantity::valueInteger() const { return this->valueInteger(m_unit); } template bool CPhysicalQuantity::isInteger() const { if (this->isNull()) { return false; } const double diff = std::abs(this->value() - this->valueInteger()); return diff <= m_unit.getEpsilon(); } template double CPhysicalQuantity::valueRounded(int digits) const { return this->valueRounded(m_unit, digits); } template QString CPhysicalQuantity::valueRoundedAsString(MU unit, int digits) const { if (this->isNull()) { return QStringLiteral("null"); } const double v = this->valueRounded(unit, digits); return QString::number(v, 'f', digits); } template double CPhysicalQuantity::value(MU unit) const { Q_ASSERT_X(!unit.isNull(), Q_FUNC_INFO, "Cannot convert to null"); return unit.convertFrom(m_value, m_unit); } template QString CPhysicalQuantity::convertToQString(bool i18n) const { if (this->isNull()) { return QStringLiteral("null"); } return this->valueRoundedWithUnit(this->getUnit(), -1, i18n); } template const PQ &CPhysicalQuantity::maxValue(const PQ &pq1, const PQ &pq2) { if (pq1.isNull()) { return pq2; } if (pq2.isNull()) { return pq1; } return pq1 > pq2 ? pq1 : pq2; } template const PQ &CPhysicalQuantity::minValue(const PQ &pq1, const PQ &pq2) { if (pq1.isNull()) { return pq2; } if (pq2.isNull()) { return pq1; } return pq1 < pq2 ? pq1 : pq2; } template const PQ &CPhysicalQuantity::null() { static const PQ n(0, MU::nullUnit()); return n; } template uint CPhysicalQuantity::getValueHash() const { // there is no double qHash // also unit and rounding has to be considered return qHash(this->valueRoundedWithUnit(MU::defaultUnit())); } template QJsonObject CPhysicalQuantity::toJson() const { QJsonObject json; json.insert("value", QJsonValue(m_value)); json.insert("unit", QJsonValue(m_unit.getSymbol())); return json; } template void CPhysicalQuantity::convertFromJson(const QJsonObject &json) { const QJsonValue unit = json.value("unit"); const QJsonValue value = json.value("value"); if (unit.isUndefined()) { throw CJsonException("Missing 'unit'"); } if (value.isUndefined()) { throw CJsonException("Missing 'value'"); } this->setUnitBySymbol(unit.toString()); m_value = value.toDouble(); } template void CPhysicalQuantity::parseFromString(const QString &value, CPqString::SeparatorMode mode) { *this = CPqString::parse(value, mode); } template void CPhysicalQuantity::parseFromString(const QString &value, CPqString::SeparatorMode mode, const MU &defaultUnitIfMissing) { if (is09OrSeparatorOnlyString(value)) { const QString v = value + defaultUnitIfMissing.getSymbol(); this->parseFromString(v, mode); } else { this->parseFromString(value, mode); } } template void CPhysicalQuantity::parseFromString(const QString &value) { *this = CPqString::parse(value, CPqString::SeparatorQtDefault); } template PQ CPhysicalQuantity::parsedFromString(const QString &value, CPqString::SeparatorMode mode, const MU &defaultUnitIfMissing) { QString v = value; if (is09OrSeparatorOnlyString(value)) { v = value + defaultUnitIfMissing.getSymbol(); } // no idea why I cannot call pq.parseFromString(v, mode, defaultUnitIfMissing); PQ pq; pq.parseFromString(v, mode); return pq; } template PQ CPhysicalQuantity::parsedFromString(const QString &value, CPqString::SeparatorMode mode) { PQ pq; pq.parseFromString(value, mode); return pq; } template QVariant CPhysicalQuantity::propertyByIndex(CPropertyIndexRef index) const { if (index.isMyself()) { return QVariant::fromValue(*derived()); } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexValue: return QVariant::fromValue(m_value); case IndexUnit: return QVariant::fromValue(m_unit); case IndexValueRounded0DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(0)); case IndexValueRounded1DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(1)); case IndexValueRounded2DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(2)); case IndexValueRounded3DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(3)); case IndexValueRounded6DigitsWithUnit: return QVariant::fromValue(this->valueRoundedWithUnit(6)); default: return Mixin::Index::propertyByIndex(index); } } template void CPhysicalQuantity::setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant) { if (index.isMyself()) { (*this) = variant.value(); return; } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexValue: m_value = variant.toDouble(); break; case IndexUnit: m_unit = variant.value(); break; case IndexValueRounded0DigitsWithUnit: case IndexValueRounded1DigitsWithUnit: case IndexValueRounded2DigitsWithUnit: case IndexValueRounded3DigitsWithUnit: case IndexValueRounded6DigitsWithUnit: this->parseFromString(variant.toString()); break; default: Mixin::Index::setPropertyByIndex(index, variant); break; } } template int CPhysicalQuantity::comparePropertyByIndex(CPropertyIndexRef index, const PQ &pq) const { if (index.isMyself()) { return compareImpl(*derived(), pq); } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexValue: return Compare::compare(m_value, pq.m_value); default: break; } BLACK_VERIFY_X(false, Q_FUNC_INFO, qUtf8Printable("No comparison for index " + index.toQString())); return 0; } template int CPhysicalQuantity::compareImpl(const PQ &a, const PQ &b) { if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; } } template PQ const *CPhysicalQuantity::derived() const { return static_cast(this); } template PQ *CPhysicalQuantity::derived() { return static_cast(this); } // see here for the reason of thess forward instantiations // https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl //! \cond PRIVATE template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; template class BLACKMISC_EXPORT_DEFINE_TEMPLATE CPhysicalQuantity; //! \endcond } // namespace } // namespace