/* Copyright (C) 2013 VATSIM Community / authors * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef BLACKMISC_PQBASE_H #define BLACKMISC_PQBASE_H #include "blackmisc/valueobject.h" #include "blackmisc/mathematics.h" #include #include #include #include #include #include #include #include namespace BlackMisc { namespace PhysicalQuantities { /*! * \brief Base class for all units, such as meter, hertz. */ class CMeasurementUnit : public CValueObject { protected: /*! * Abstract strategy pattern that encapsulates a unit conversion strategy. */ class Converter : public QSharedData { public: /*! * Virtual destructor. */ virtual ~Converter() {} /*! * Convert from this unit to default unit. * \param factor * \return */ virtual double toDefault(double factor) const = 0; /*! * Convert from default unit to this unit. * \param factor * \return */ virtual double fromDefault(double factor) const = 0; }; /*! * \brief Converter for default values, such as None, used with public constructor */ struct NilConverter : public Converter { virtual double toDefault(double) const { return 0.0; } virtual double fromDefault(double) const { return 0.0; } }; /*! * Concrete strategy pattern for converting unit that does nothing. */ struct IdentityConverter : public Converter { virtual double toDefault(double factor) const { return factor; } virtual double fromDefault(double factor) const { return factor; } }; /*! * Concrete strategy pattern for converting unit with linear conversion. * \tparam Policy a policy class with static method factor() returning double */ template struct LinearConverter : public Converter { virtual double toDefault(double factor) const { return factor * Policy::factor(); } virtual double fromDefault(double factor) const { return factor / Policy::factor(); } }; /*! * Concrete strategy pattern for converting unit with offset linear conversion. * \tparam Policy a policy class with static methods factor() and offset() returning double */ template struct AffineConverter : public Converter { virtual double toDefault(double factor) const { return (factor - Policy::offset()) * Policy::factor(); } virtual double fromDefault(double factor) const { return factor / Policy::factor() + Policy::offset(); } }; /*! * Concrete strategy pattern for converting unit with one subdivision conversion. * \tparam FactorPolicy a policy class with static method factor() returning double * \tparam SubdivPolicy a policy class with static methods fraction() and subfactor() returning double */ template struct SubdivisionConverter : public Converter { virtual double toDefault(double factor) const { using BlackMisc::Math::CMath; double part2 = CMath::fract(factor) * SubdivPolicy::fraction(); factor = CMath::trunc(factor) + part2 / SubdivPolicy::subfactor(); return factor * FactorPolicy::factor(); } virtual double fromDefault(double factor) const { using BlackMisc::Math::CMath; factor /= FactorPolicy::factor(); double part2 = CMath::fract(factor) * SubdivPolicy::subfactor(); return CMath::trunc(factor) + part2 / SubdivPolicy::fraction(); } }; /*! * Concrete strategy pattern for converting unit with two subdivision conversions. * \tparam FactorPolicy a policy class with static method factor() returning double * \tparam SubdivPolicy a policy class with static methods fraction() and subfactor() returning double */ template struct SubdivisionConverter2 : public Converter { virtual double toDefault(double factor) const { using BlackMisc::Math::CMath; double part2 = CMath::fract(factor) * SubdivPolicy::fraction(); double part3 = CMath::fract(part2) * SubdivPolicy::fraction(); factor = CMath::trunc(factor) + (CMath::trunc(part2) + part3 / SubdivPolicy::subfactor()) / SubdivPolicy::subfactor(); return factor * FactorPolicy::factor(); } virtual double fromDefault(double factor) const { using BlackMisc::Math::CMath; factor /= FactorPolicy::factor(); double part2 = CMath::fract(factor) * SubdivPolicy::subfactor(); double part3 = CMath::fract(part2) * SubdivPolicy::subfactor(); return CMath::trunc(factor) + (CMath::trunc(part2) + part3 / SubdivPolicy::fraction()) / SubdivPolicy::fraction(); } }; //! Metapolicy that can be used to modify template parameters of converters //! @{ struct One { static double factor() { return 1; } //!< factor \return }; template struct Two { static double factor() { return Policy::factor() * 2.0; } //!< factor \return }; template struct Milli { static double factor() { return Policy::factor() / 1000.0; } //!< factor \return }; template struct Centi { static double factor() { return Policy::factor() / 100.0; } //!< factor \return }; template struct Hecto { static double factor() { return Policy::factor() * 100.0; } //!< factor \return }; template struct Kilo { static double factor() { return Policy::factor() * 1000.0; } //!< factor \return }; template struct Mega { static double factor() { return Policy::factor() * 1e+6; } //!< factor \return }; template struct Giga { static double factor() { return Policy::factor() * 1e+9; } //!< factor \return }; template struct InEachHundred { static double fraction() { return 100.0f; } //!< fraction \return static double subfactor() { return float(Subfactor); } //!< subfactor \return }; //! @} private: QString m_name; //!< name, e.g. "meter" QString m_symbol; //!< unit name, e.g. "m" double m_epsilon; //!< values with differences below epsilon are the equal int m_displayDigits; //!< standard rounding for string conversions QSharedDataPointer m_converter; //!< strategy pattern allows an arbitrary conversion method as per object protected: /*! * Construct a unit with custom conversion * \param name * \param symbol * \param displayDigits * \param epsilon */ template CMeasurementUnit(const QString &name, const QString &symbol, const Converter &, int displayDigits, double epsilon) : m_name(name), m_symbol(symbol), m_epsilon(epsilon), m_displayDigits(displayDigits), m_converter(new Converter) {} /*! * \brief String for streaming operators is full name * \return */ virtual QString stringForStreaming() const { return this->getName(false); } /*! * \brief Name as stringification * \param i18n * \return */ virtual QString convertToQString(bool i18n = false) const { return this->getSymbol(i18n); } /*! * \copydoc CValueObject::getMetaTypeId */ virtual int getMetaTypeId() const; /*! * \copydoc CValueObject::isA */ virtual bool isA(int metaTypeId) const; /*! * \copydoc CValueObject::compareImpl */ virtual int compareImpl(const CValueObject &other) const; /*! * \brief Stream to DBus * \param argument */ virtual void marshallToDbus(QDBusArgument &argument) const { argument << this->m_symbol; } /*! * \brief Stream from DBus */ virtual void unmarshallFromDbus(const QDBusArgument &) { // the concrete implementations will override this default // this is required so I can also stream None (*this) = CMeasurementUnit::None(); } public: /*! * \brief Default constructor for meta system * \remarks Only public because the need, to use this with the metasystem */ CMeasurementUnit() : m_name("none"), m_symbol(""), m_epsilon(0), m_displayDigits(0), m_converter(new NilConverter()) {} /*! * \brief Virtual method to return QVariant, used with DBUS QVariant lists * \return */ virtual QVariant toQVariant() const { // used with None! return QVariant::fromValue(*this); } /*! * \brief Equal operator == * \param other * \return */ bool operator == (const CMeasurementUnit &other) const; /*! * \brief Unequal operator != * \param other * \return */ bool operator != (const CMeasurementUnit &other) const; /*! * \brief Name such as "meter" * \param i18n * \return */ QString getName(bool i18n = false) const { return i18n ? QCoreApplication::translate("CMeasurementUnit", this->m_name.toStdString().c_str()) : this->m_name; } /*! * \brief Unit name such as "m" * \param i18n * \return */ QString getSymbol(bool i18n = false) const { return i18n ? QCoreApplication::translate("CMeasurementUnit", this->m_symbol.toStdString().c_str()) : this->m_symbol; } /*! * \brief Value hash * \return */ virtual uint getValueHash() const { return qHash(this->getName()); } /*! * \brief Rounded value * \param value * \param digits * \return */ double roundValue(double value, int digits = -1) const; /*! * Rounded string utility method, virtual so units can have * specialized formatting * \param value * \param digits * \param i18n * \return */ virtual QString makeRoundedQString(double value, int digits = -1, bool i18n = false) const; /*! * \brief Value rounded with unit, e.g. "5.00m", "30kHz" * \param value * \param digits * \param i18n * \return */ virtual QString makeRoundedQStringWithUnit(double value, int digits = -1, bool i18n = false) const; /*! * \brief Threshold for rounding * \return */ double getEpsilon() const { return this->m_epsilon; } /*! * \brief getDisplayDigits * \return */ int getDisplayDigits() const { return this->m_displayDigits; } /*! * Convert from other unit to this unit. * \param */ double convertFrom(double value, const CMeasurementUnit &unit) const; /*! * \brief Is given value <= epsilon? * \param value * \return */ bool isEpsilon(double value) const { if (value == 0) return true; return abs(value) <= this->m_epsilon; } // -------------------------------------------------------------------- // -- static // -------------------------------------------------------------------- /*! * \brief Unit from symbol * \param symbol must be a valid unit symbol (without i18n) or empty string (empty means default unit) * \param strict strict check means if unit is not found, program terminates * \return */ template static const U &unitFromSymbol(const QString &symbol, bool strict = true) { if (symbol.isEmpty()) return U::defaultUnit(); const QList &units = U::allUnits(); for (int i = 0; i < units.size(); ++i) { if (units.at(i).getSymbol() == symbol) return units.at(i); } if (strict) qFatal("Illegal unit name"); return U::defaultUnit(); } /*! * \brief Valid unit symbol * \param symbol must be a valid unit symbol (without i18n) or empty string (empty means default unit) * \return */ template static bool isValidUnitSymbol(const QString &symbol) { if (symbol.isEmpty()) return false; const QList &units = U::allUnits(); for (int i = 0; i < units.size(); ++i) { if (units.at(i).getSymbol() == symbol) return true; } return false; } /*! * \brief Dimensionless unit * \return */ static CMeasurementUnit &None() { static CMeasurementUnit none("none", "", NilConverter(), 0, 0); return none; } /*! * \brief Metadata, mainly needed for None */ static void registerMetadata(); }; } // namespace } // namespace Q_DECLARE_METATYPE(BlackMisc::PhysicalQuantities::CMeasurementUnit) #endif // guard