// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2015 Eivind Kvedalen * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "Cell.h" #include "Sheet.h" FC_LOG_LEVEL_INIT("Spreadsheet", true, true) #ifdef _MSC_VER # define __func__ __FUNCTION__ # ifdef PropertySheet # undef PropertySheet // Microsoft's #define conflicts with the use below # endif #endif using namespace App; using namespace Base; using namespace Spreadsheet; ///////////////////////////////////////////////////////// // expose the read() function for simpler partial xml reading in setExpression() class ReaderPrivate: public Base::XMLReader { public: ReaderPrivate(const char* FileName, std::istream& is) : XMLReader(FileName, is) {} bool read() { return XMLReader::read(); } }; /////////////////////////////////////////////////////////// // clang-format off const int Cell::EXPRESSION_SET = 1; const int Cell::ALIGNMENT_SET = 4; const int Cell::STYLE_SET = 8; const int Cell::BACKGROUND_COLOR_SET = 0x10; const int Cell::FOREGROUND_COLOR_SET = 0x20; const int Cell::DISPLAY_UNIT_SET = 0x40; const int Cell::COMPUTED_UNIT_SET = 0x80; const int Cell::ALIAS_SET = 0x100; const int Cell::SPANS_SET = 0x200; const int Cell::MARK_SET = 0x40000000; const int Cell::EXCEPTION_SET = 0x20000000; const int Cell::PARSE_EXCEPTION_SET = 0x80000000; const int Cell::RESOLVE_EXCEPTION_SET= 0x01000000; /* Alignment */ const int Cell::ALIGNMENT_LEFT = 0x01; const int Cell::ALIGNMENT_HCENTER = 0x02; const int Cell::ALIGNMENT_RIGHT = 0x04; const int Cell::ALIGNMENT_HIMPLIED = 0x08; const int Cell::ALIGNMENT_HORIZONTAL = 0x0f; const int Cell::ALIGNMENT_TOP = 0x10; const int Cell::ALIGNMENT_VCENTER = 0x20; const int Cell::ALIGNMENT_BOTTOM = 0x40; const int Cell::ALIGNMENT_VIMPLIED = 0x80; const int Cell::ALIGNMENT_VERTICAL = 0xf0; // clang-format on /** * Construct a CellContent object. * @param _address The address of the cell (i.e. row and column) * @param _owner The spreadsheet that owns this cell. * */ Cell::Cell(const CellAddress& _address, PropertySheet* _owner) : address(_address) , owner(_owner) , used(0) , alignment(ALIGNMENT_HIMPLIED | ALIGNMENT_LEFT | ALIGNMENT_VIMPLIED | ALIGNMENT_VCENTER) , style() , foregroundColor(0, 0, 0, 1) , backgroundColor(1, 1, 1, 1) , displayUnit() , alias() , computedUnit() , rowSpan(1) , colSpan(1) , anchor() { assert(address.isValid()); } Cell::Cell(PropertySheet* _owner, const Cell& other) : address(other.address) , owner(_owner) , used(other.used) , expression(other.expression ? other.expression->copy() : nullptr) , alignment(other.alignment) , style(other.style) , foregroundColor(other.foregroundColor) , backgroundColor(other.backgroundColor) , displayUnit(other.displayUnit) , computedUnit(other.computedUnit) , rowSpan(other.rowSpan) , colSpan(other.colSpan) { setUsed(MARK_SET, false); setAlias(other.alias); setDirty(); } Cell& Cell::operator=(const Cell& rhs) { PropertySheet::AtomicPropertyChange signaller(*owner); address = rhs.address; setExpression(App::ExpressionPtr(rhs.expression ? rhs.expression->copy() : nullptr)); setAlignment(rhs.alignment); setStyle(rhs.style); setBackground(rhs.backgroundColor); setForeground(rhs.foregroundColor); setDisplayUnit(rhs.displayUnit.stringRep); setComputedUnit(rhs.computedUnit); setAlias(rhs.alias); setSpans(rhs.rowSpan, rhs.colSpan); setUsed(MARK_SET, false); setDirty(); signaller.tryInvoke(); return *this; } /** * Destroy a CellContent object. * */ Cell::~Cell() = default; /** * Set the expression tree to \a expr. * */ void Cell::setExpression(App::ExpressionPtr&& expr) { PropertySheet::AtomicPropertyChange signaller(*owner); owner->setDirty(address); /* Remove dependencies */ owner->removeDependencies(address); if (expr && !expr->comment.empty()) { if (!boost::starts_with(expr->comment, "sheet()->getFullName() << '.' << address.toString() ); } else { try { std::istringstream in(expr->comment); ReaderPrivate reader("", in); reader.read(); restore(reader, true); } catch (Base::Exception& e) { e.reportException(); FC_ERR( "Failed to restore style of cell " << owner->sheet()->getFullName() << '.' << address.toString() << ": " << e.what() ); } } expr->comment.clear(); } expression = std::move(expr); setUsed(EXPRESSION_SET, !!expression); /* Update dependencies */ owner->addDependencies(address); signaller.tryInvoke(); } /** * Get the expression tree. * */ const App::Expression* Cell::getExpression(bool withFormat) const { if (withFormat && expression) { if ((used & (ALIGNMENT_SET | STYLE_SET | FOREGROUND_COLOR_SET | BACKGROUND_COLOR_SET | DISPLAY_UNIT_SET | ALIAS_SET | SPANS_SET))) { std::ostringstream ss; save(ss, "", true); expression->comment = ss.str(); } } return expression.get(); } /** * Get string content. * */ bool Cell::getStringContent(std::string& s, bool persistent) const { if (expression) { s.clear(); if (expression->hasComponent()) { s = "=" + expression->toString(persistent); } else if (freecad_cast(expression.get())) { s = static_cast(expression.get())->getText(); s = "'" + s; } else if (freecad_cast(expression.get())) { s = "=" + expression->toString(); } else if (freecad_cast(expression.get())) { s = expression->toString(); } else { s = "=" + expression->toString(persistent); } return true; } else { s = ""; return false; } } void Cell::afterRestore() { auto expr = freecad_cast(expression.get()); if (expr) { setContent(expr->getText().c_str()); } } void Cell::setContent(const char* value) { PropertySheet::AtomicPropertyChange signaller(*owner); ExpressionPtr newExpr; clearException(); if (value) { Sheet* sheet = owner->sheet(); if (sheet && sheet->isRestoring()) { if (value[0] == '\0' || (value[0] == '\'' && value[1] == '\0')) { return; } expression = std::make_unique(sheet, value); setUsed(EXPRESSION_SET, true); return; } if (*value == '=') { try { newExpr = ExpressionPtr(App::ExpressionParser::parse(sheet, value + 1)); } catch (Base::Exception& e) { newExpr = std::make_unique(sheet, value); setParseException(e.what()); } } else if (*value == '\'') { if (value[1] == '\0') { value = nullptr; } else { newExpr = std::make_unique(sheet, value + 1); } } else if (*value != '\0') { // check if value is just a number char* end; errno = 0; const double float_value = strtod(value, &end); if (errno == 0) { const bool isEndEmpty = *end == '\0' || strspn(end, " \t\n\r") == strlen(end); if (isEndEmpty) { newExpr = std::make_unique(sheet, Quantity(float_value)); } } // if not a float, check if it is a quantity or compatible fraction const bool isStartingWithNumber = value != end; if (!newExpr && isStartingWithNumber) { try { ExpressionPtr parsedExpr(App::ExpressionParser::parse(sheet, value)); if (const auto fraction = freecad_cast(parsedExpr.get())) { if (fraction->getOperator() == OperatorExpression::UNIT) { const auto left = freecad_cast(fraction->getLeft()); const auto right = freecad_cast(fraction->getRight()); if (left && right) { newExpr = std::move(parsedExpr); } } else if (fraction->getOperator() == OperatorExpression::DIV) { // only the following types of fractions are ok: // 1/2, 1m/2, 1/2s, 1m/2s, 1/m // check for numbers in (de)nominator const bool isNumberNom = freecad_cast( fraction->getLeft() ); const bool isNumberDenom = freecad_cast( fraction->getRight() ); // check for numbers with units in (de)nominator const auto opNom = freecad_cast(fraction->getLeft()); const auto opDenom = freecad_cast( fraction->getRight() ); const bool isQuantityNom = opNom && opNom->getOperator() == OperatorExpression::UNIT; const bool isQuantityDenom = opDenom && opDenom->getOperator() == OperatorExpression::UNIT; // check for units in denomainator const auto uDenom = freecad_cast(fraction->getRight()); const bool isUnitDenom = uDenom && uDenom->is(); const bool isNomValid = isNumberNom || isQuantityNom; const bool isDenomValid = isNumberDenom || isQuantityDenom || isUnitDenom; if (isNomValid && isDenomValid) { newExpr = std::move(parsedExpr); } } } else if (const auto number = freecad_cast(parsedExpr.get())) { // NumbersExpressions can accept more than can be parsed with strtod. // Example: 12.34 and 12,34 are both valid NumberExpressions newExpr = std::move(parsedExpr); } } catch (...) { } } } if (!newExpr && value && *value != '\0') { newExpr = std::make_unique(sheet, value); } // trying to add an empty string will make newExpr = nullptr } // set expression, or delete the current expression by setting nullptr if empty string was // entered setExpression(std::move(newExpr)); signaller.tryInvoke(); } /** * Set alignment of this cell. Alignment is the or'ed value of * vertical and horizontal alignment, given by the constants * defined in the class. * */ void Cell::setAlignment(int _alignment) { if (_alignment != alignment) { PropertySheet::AtomicPropertyChange signaller(*owner); alignment = _alignment; setUsed( ALIGNMENT_SET, alignment != (ALIGNMENT_HIMPLIED | ALIGNMENT_LEFT | ALIGNMENT_VIMPLIED | ALIGNMENT_VCENTER) ); setDirty(); signaller.tryInvoke(); } } /** * Get alignment. * */ bool Cell::getAlignment(int& _alignment) const { _alignment = alignment; return isUsed(ALIGNMENT_SET); } /** * Set style to the given set \a _style. * */ void Cell::setStyle(const std::set& _style) { if (_style != style) { PropertySheet::AtomicPropertyChange signaller(*owner); style = _style; setUsed(STYLE_SET, !style.empty()); setDirty(); signaller.tryInvoke(); } } /** * Get the style of the cell. * */ bool Cell::getStyle(std::set& _style) const { _style = style; return isUsed(STYLE_SET); } /** * Set foreground (i.e text) color of the cell to \a color. * */ void Cell::setForeground(const Base::Color& color) { if (color != foregroundColor) { PropertySheet::AtomicPropertyChange signaller(*owner); foregroundColor = color; setUsed(FOREGROUND_COLOR_SET, foregroundColor != Base::Color(0, 0, 0, 1)); setDirty(); signaller.tryInvoke(); } } /** * Get foreground color of the cell. * */ bool Cell::getForeground(Base::Color& color) const { color = foregroundColor; return isUsed(FOREGROUND_COLOR_SET); } /** * Set background color of the cell to \a color. * */ void Cell::setBackground(const Base::Color& color) { if (color != backgroundColor) { PropertySheet::AtomicPropertyChange signaller(*owner); backgroundColor = color; setUsed(BACKGROUND_COLOR_SET, backgroundColor != Base::Color(1, 1, 1, 0)); setDirty(); signaller.tryInvoke(); } } /** * Get the background color of the cell into \a color. * * @returns true if the background color was previously set. * */ bool Cell::getBackground(Base::Color& color) const { color = backgroundColor; return isUsed(BACKGROUND_COLOR_SET); } /** * Set the display unit for the cell. * */ void Cell::setDisplayUnit(const std::string& unit) { DisplayUnit newDisplayUnit; if (!unit.empty()) { std::shared_ptr e( ExpressionParser::parseUnit(owner->sheet(), unit.c_str()) ); if (!e) { throw Base::UnitsMismatchError("Invalid unit"); } newDisplayUnit = DisplayUnit(unit, e->getUnit(), e->getScaler()); } if (newDisplayUnit != displayUnit) { PropertySheet::AtomicPropertyChange signaller(*owner); displayUnit = std::move(newDisplayUnit); setUsed(DISPLAY_UNIT_SET, !displayUnit.isEmpty()); setDirty(); signaller.tryInvoke(); } } /** * Get the display unit for the cell into unit. * * @returns true if the display unit was previously set. * */ bool Cell::getDisplayUnit(DisplayUnit& unit) const { unit = displayUnit; return isUsed(DISPLAY_UNIT_SET); } void Cell::setAlias(const std::string& n) { if (alias != n) { PropertySheet::AtomicPropertyChange signaller(*owner); owner->revAliasProp.erase(alias); // Update owner if (!n.empty()) { owner->aliasProp[address] = n; owner->revAliasProp[n] = address; } else { owner->aliasProp.erase(address); } if (!alias.empty()) { // The property may have been added in Sheet::updateAlias auto* docObj = static_cast(owner->getContainer()); docObj->removeDynamicProperty(alias.c_str()); } alias = n; setUsed(ALIAS_SET, !alias.empty()); setDirty(); signaller.tryInvoke(); } } bool Cell::getAlias(std::string& n) const { n = alias; return isUsed(ALIAS_SET); } /** * Set the computed unit for the cell to \a unit. * */ void Cell::setComputedUnit(const Base::Unit& unit) { PropertySheet::AtomicPropertyChange signaller(*owner); computedUnit = unit; setUsed(COMPUTED_UNIT_SET, computedUnit != Unit::One); setDirty(); signaller.tryInvoke(); } /** * Get the computed unit into \a unit. * * @returns true if the computed unit was previously set. * */ bool Cell::getComputedUnit(Base::Unit& unit) const { unit = computedUnit; return isUsed(COMPUTED_UNIT_SET); } /** * Set the cell's row and column span to \a rows and \a columns. This * is done when cells are merged. * */ void Cell::setSpans(int rows, int columns) { if (rows != rowSpan || columns != colSpan) { PropertySheet::AtomicPropertyChange signaller(*owner); rowSpan = (rows == -1 ? 1 : rows); colSpan = (columns == -1 ? 1 : columns); setUsed(SPANS_SET, (rowSpan != 1 || colSpan != 1)); setDirty(); signaller.tryInvoke(); } } /** * Get the row and column spans for the cell into \a rows and \a columns. * */ bool Cell::getSpans(int& rows, int& columns) const { rows = rowSpan; columns = colSpan; return isUsed(SPANS_SET); } void Cell::setException(const std::string& e, bool silent) { if (!silent && !e.empty() && owner && owner->sheet()) { FC_ERR(owner->sheet()->getFullName() << '.' << address.toString() << ": " << e); } exceptionStr = e; setUsed(EXCEPTION_SET); } void Cell::setParseException(const std::string& e) { if (!e.empty() && owner && owner->sheet()) { FC_ERR(owner->sheet()->getFullName() << '.' << address.toString() << ": " << e); } exceptionStr = e; setUsed(PARSE_EXCEPTION_SET); } void Cell::setResolveException(const std::string& e) { if (!e.empty() && owner && owner->sheet()) { FC_LOG(owner->sheet()->getFullName() << '.' << address.toString() << ": " << e); } exceptionStr = e; setUsed(RESOLVE_EXCEPTION_SET); } void Cell::clearResolveException() { setUsed(RESOLVE_EXCEPTION_SET, false); } void Cell::clearException() { exceptionStr.clear(); setUsed(EXCEPTION_SET, false); setUsed(RESOLVE_EXCEPTION_SET, false); setUsed(PARSE_EXCEPTION_SET, false); } void Cell::clearDirty() { if (owner) { owner->clearDirty(address); } } void Cell::setDirty() { if (owner) { owner->setDirty(address); } } /** * Move the cell to a new position given by \a _row and \a _col. * */ void Cell::moveAbsolute(CellAddress newAddress) { address = newAddress; } /** * Restore cell contents from \a reader. * */ void Cell::restore(Base::XMLReader& reader, bool checkAlias) { const char* style = reader.hasAttribute("style") ? reader.getAttribute("style") : nullptr; const char* alignment = reader.hasAttribute("alignment") ? reader.getAttribute("alignment") : nullptr; const char* content = reader.hasAttribute("content") ? reader.getAttribute("content") : ""; const char* foregroundColor = reader.hasAttribute("foregroundColor") ? reader.getAttribute("foregroundColor") : nullptr; const char* backgroundColor = reader.hasAttribute("backgroundColor") ? reader.getAttribute("backgroundColor") : nullptr; const char* displayUnit = reader.hasAttribute("displayUnit") ? reader.getAttribute("displayUnit") : nullptr; const char* alias = reader.hasAttribute("alias") ? reader.getAttribute("alias") : nullptr; const char* rowSpan = reader.hasAttribute("rowSpan") ? reader.getAttribute("rowSpan") : nullptr; const char* colSpan = reader.hasAttribute("colSpan") ? reader.getAttribute("colSpan") : nullptr; // Don't trigger multiple updates below; wait until everything is loaded by calling unfreeze() // below. PropertySheet::AtomicPropertyChange signaller(*owner); if (content) { setContent(content); } if (style) { using namespace boost; std::set styleSet; escaped_list_separator e('\0', '|', '\0'); std::string line = std::string(style); tokenizer> tok(line, e); for (tokenizer>::iterator i = tok.begin(); i != tok.end(); ++i) { styleSet.insert(*i); } setStyle(styleSet); } if (alignment) { int alignmentCode = 0; using namespace boost; escaped_list_separator e('\0', '|', '\0'); std::string line = std::string(alignment); tokenizer> tok(line, e); for (tokenizer>::iterator i = tok.begin(); i != tok.end(); ++i) { alignmentCode = decodeAlignment(*i, alignmentCode); } setAlignment(alignmentCode); } if (foregroundColor) { Base::Color color = decodeColor(foregroundColor, Base::Color(0, 0, 0, 1)); setForeground(color); } if (backgroundColor) { Base::Color color = decodeColor(backgroundColor, Base::Color(1, 1, 1, 1)); setBackground(color); } if (displayUnit) { setDisplayUnit(displayUnit); } if (alias && (!checkAlias || !owner->revAliasProp.count(alias))) { setAlias(alias); } if (rowSpan || colSpan) { int rs = rowSpan ? atoi(rowSpan) : 1; int cs = colSpan ? atoi(colSpan) : 1; setSpans(rs, cs); } } /** * Save cell contents into \a writer. * */ void Cell::save(Base::Writer& writer) const { save(writer.Stream(), writer.ind(), false); } void Cell::save(std::ostream& os, const char* indent, bool noContent) const { if (!isUsed()) { return; } os << indent << ""; if (!noContent) { os << std::endl; } } /** * Update the \a used member variable with mask (bitwise or'ed). * */ void Cell::setUsed(int mask, bool state) { if (state) { used |= mask; } else { used &= ~mask; } } /** * Determine whether the bits in \a mask are set in the \a used member variable. * */ bool Cell::isUsed(int mask) const { return (used & mask) == mask; } /** * Determine if the any of the contents of the cell is set a non-default value. * */ bool Cell::isUsed() const { return used != 0; } void Cell::visit(App::ExpressionVisitor& v) { if (expression) { expression->visit(v); } } /** * Decode alignment into its internal value. * * @param itemStr Alignment as a string * @param alignment Current alignment. This is or'ed with the one in \a itemStr. * * @returns New alignment. * */ int Cell::decodeAlignment(const std::string& itemStr, int alignment) { if (itemStr == "himplied") { if (!(alignment & ALIGNMENT_HORIZONTAL)) { alignment |= ALIGNMENT_LEFT; } alignment |= Cell::ALIGNMENT_HIMPLIED; } else if (itemStr == "left") { alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | Cell::ALIGNMENT_LEFT; } else if (itemStr == "center") { alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | Cell::ALIGNMENT_HCENTER; } else if (itemStr == "right") { alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | Cell::ALIGNMENT_RIGHT; } else if (itemStr == "vimplied") { if (!(alignment & ALIGNMENT_VERTICAL)) { alignment |= ALIGNMENT_VCENTER; } alignment |= Cell::ALIGNMENT_VIMPLIED; } else if (itemStr == "top") { alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | Cell::ALIGNMENT_TOP; } else if (itemStr == "vcenter") { alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | Cell::ALIGNMENT_VCENTER; } else if (itemStr == "bottom") { alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | Cell::ALIGNMENT_BOTTOM; } else if (!itemStr.empty()) { throw Base::ValueError("Invalid alignment."); } return alignment; } /** * Encode internal alignment value as a string. * * @param alignment Alignment as a binary value. * * @returns Alignment represented as a string. * */ std::string Cell::encodeAlignment(int alignment) { std::string s; if (alignment & Cell::ALIGNMENT_LEFT) { s += "left"; } if (alignment & Cell::ALIGNMENT_HCENTER) { s += "center"; } if (alignment & Cell::ALIGNMENT_RIGHT) { s += "right"; } if (alignment & Cell::ALIGNMENT_HIMPLIED) { s += "|himplied"; } if (alignment & Cell::ALIGNMENT_VERTICAL) { s += "|"; } if (alignment & Cell::ALIGNMENT_TOP) { s += "top"; } if (alignment & Cell::ALIGNMENT_VCENTER) { s += "vcenter"; } if (alignment & Cell::ALIGNMENT_BOTTOM) { s += "bottom"; } if (alignment & Cell::ALIGNMENT_VIMPLIED) { s += "|vimplied"; } return s; } /** * Encode \a color as a \#rrggbbaa string. * * @param color Color to encode. * * @returns String with encoded color. * */ std::string Cell::encodeColor(const Base::Color& color) { std::stringstream tmp; tmp << "#" << std::hex << std::setw(2) << std::setfill('0') << int(color.r * 255.0) << std::hex << std::setw(2) << std::setfill('0') << int(color.g * 255.0) << std::hex << std::setw(2) << std::setfill('0') << int(color.b * 255.0) << std::hex << std::setw(2) << std::setfill('0') << int(color.a * 255.0); return tmp.str(); } /** * Encode set of styles as a string. * * @param style Set of string describing the style. * * @returns Set encoded as a string. * */ std::string Cell::encodeStyle(const std::set& style) { std::string s; std::set::const_iterator j = style.begin(); std::set::const_iterator j_end = style.end(); while (j != j_end) { s += *j; ++j; if (j != j_end) { s += "|"; } } return s; } /** * Decode a string of the format \#rrggbb or \#rrggbbaa into a Color. * * @param color The color to decode. * @param defaultColor A default color in case the decoding fails. * * @returns Decoded color. * */ Base::Color Cell::decodeColor(const std::string& color, const Base::Color& defaultColor) { if (color.size() == 7 || color.size() == 9) { Base::Color c; if (color[0] != '#') { return defaultColor; } unsigned int value = strtoul(color.c_str() + 1, nullptr, 16); if (color.size() == 7) { value = (value << 8) | 0xff; } c.setPackedValue(value); return c; } else { return defaultColor; } } // roughly based on Spreadsheet/Gui/SheetModel.cpp std::string Cell::getFormattedQuantity() { QString qFormatted; App::CellAddress thisCell = getAddress(); Property* prop = owner->sheet()->getPropertyByName(thisCell.toString().c_str()); if (prop->isDerivedFrom()) { const App::PropertyString* stringProp = static_cast(prop); qFormatted = QString::fromUtf8(stringProp->getValue()); } else if (prop->isDerivedFrom()) { double rawVal = static_cast(prop)->getValue(); const App::PropertyQuantity* floatProp = static_cast(prop); DisplayUnit du; bool hasDisplayUnit = getDisplayUnit(du); double duScale = du.scaler; const Base::Unit& computedUnit = floatProp->getUnit(); qFormatted = QLocale().toString(rawVal, 'f', Base::UnitsApi::getDecimals()); if (hasDisplayUnit) { if (computedUnit == Unit::One || computedUnit == du.unit) { QString number = QLocale().toString(rawVal / duScale, 'f', Base::UnitsApi::getDecimals()); qFormatted = number + QString::fromStdString(" " + displayUnit.stringRep); } } } else if (prop->isDerivedFrom()) { double rawVal = static_cast(prop)->getValue(); DisplayUnit du; bool hasDisplayUnit = getDisplayUnit(du); double duScale = du.scaler; qFormatted = QLocale().toString(rawVal, 'f', Base::UnitsApi::getDecimals()); if (hasDisplayUnit) { QString number = QLocale().toString(rawVal / duScale, 'f', Base::UnitsApi::getDecimals()); qFormatted = number + QString::fromStdString(" " + displayUnit.stringRep); } } else if (prop->isDerivedFrom()) { double rawVal = static_cast(prop)->getValue(); DisplayUnit du; bool hasDisplayUnit = getDisplayUnit(du); double duScale = du.scaler; int iRawVal = std::round(rawVal); qFormatted = QLocale().toString(iRawVal); if (hasDisplayUnit) { QString number = QLocale().toString(rawVal / duScale, 'f', Base::UnitsApi::getDecimals()); qFormatted = number + QString::fromStdString(" " + displayUnit.stringRep); } } return qFormatted.toStdString(); }