Dimensionless quantities have all exponents equal to zero. Such quantities are simply numbers. The associated unit is the unit one, symbol 1, although this is rarely explicitly written. See chapter 2.3.3 Dimensions of quantities, The International System of Units, 9th edition.
1147 lines
32 KiB
C++
1147 lines
32 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name> *
|
|
* *
|
|
* 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 "PreCompiled.h"
|
|
|
|
#ifndef _PreComp_
|
|
#include <QLocale>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <boost/tokenizer.hpp>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#endif
|
|
|
|
#include <App/ExpressionParser.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Quantity.h>
|
|
#include <Base/Reader.h>
|
|
#include <Base/UnitsApi.h>
|
|
#include <Base/Writer.h>
|
|
|
|
#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, "<Cell ")) {
|
|
FC_WARN("Unknown style of cell " << owner->sheet()->getFullName() << '.'
|
|
<< address.toString());
|
|
}
|
|
else {
|
|
try {
|
|
std::istringstream in(expr->comment);
|
|
ReaderPrivate reader("<memory>", 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<App::StringExpression*>(expression.get())) {
|
|
s = static_cast<App::StringExpression*>(expression.get())->getText();
|
|
s = "'" + s;
|
|
}
|
|
else if (freecad_cast<App::ConstantExpression*>(expression.get())) {
|
|
s = "=" + expression->toString();
|
|
}
|
|
else if (freecad_cast<App::NumberExpression*>(expression.get())) {
|
|
s = expression->toString();
|
|
}
|
|
else {
|
|
s = "=" + expression->toString(persistent);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else {
|
|
s = "";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Cell::afterRestore()
|
|
{
|
|
auto expr = freecad_cast<StringExpression*>(expression.get());
|
|
if (expr) {
|
|
setContent(expr->getText().c_str());
|
|
}
|
|
}
|
|
|
|
void Cell::setContent(const char* value)
|
|
{
|
|
PropertySheet::AtomicPropertyChange signaller(*owner);
|
|
ExpressionPtr newExpr;
|
|
|
|
clearException();
|
|
if (value) {
|
|
if (owner->sheet()->isRestoring()) {
|
|
if (value[0] == '\0' || (value[0] == '\'' && value[1] == '\0')) {
|
|
return;
|
|
}
|
|
expression = std::make_unique<App::StringExpression>(owner->sheet(), value);
|
|
setUsed(EXPRESSION_SET, true);
|
|
return;
|
|
}
|
|
if (*value == '=') {
|
|
try {
|
|
newExpr = ExpressionPtr(App::ExpressionParser::parse(owner->sheet(), value + 1));
|
|
}
|
|
catch (Base::Exception& e) {
|
|
newExpr = std::make_unique<App::StringExpression>(owner->sheet(), value);
|
|
setParseException(e.what());
|
|
}
|
|
}
|
|
else if (*value == '\'') {
|
|
if (value[1] == '\0') {
|
|
value = nullptr;
|
|
}
|
|
else {
|
|
newExpr = std::make_unique<App::StringExpression>(owner->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<App::NumberExpression>(owner->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(owner->sheet(), value));
|
|
|
|
if (const auto fraction = freecad_cast<OperatorExpression*>(parsedExpr.get())) {
|
|
if (fraction->getOperator() == OperatorExpression::UNIT) {
|
|
const auto left = freecad_cast<NumberExpression*>(fraction->getLeft());
|
|
const auto right = freecad_cast<UnitExpression*>(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<NumberExpression*>(fraction->getLeft());
|
|
const bool isNumberDenom =
|
|
freecad_cast<NumberExpression*>(fraction->getRight());
|
|
|
|
// check for numbers with units in (de)nominator
|
|
const auto opNom =
|
|
freecad_cast<OperatorExpression*>(fraction->getLeft());
|
|
const auto opDenom =
|
|
freecad_cast<OperatorExpression*>(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<UnitExpression*>(fraction->getRight());
|
|
const bool isUnitDenom = uDenom && uDenom->is<UnitExpression>();
|
|
|
|
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<NumberExpression*>(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<App::StringExpression>(owner->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<std::string>& _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<std::string>& _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<App::UnitExpression> 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<App::DocumentObject*>(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<const char*>("style") : nullptr;
|
|
const char* alignment =
|
|
reader.hasAttribute("alignment") ? reader.getAttribute<const char*>("alignment") : nullptr;
|
|
const char* content =
|
|
reader.hasAttribute("content") ? reader.getAttribute<const char*>("content") : "";
|
|
const char* foregroundColor = reader.hasAttribute("foregroundColor")
|
|
? reader.getAttribute<const char*>("foregroundColor")
|
|
: nullptr;
|
|
const char* backgroundColor = reader.hasAttribute("backgroundColor")
|
|
? reader.getAttribute<const char*>("backgroundColor")
|
|
: nullptr;
|
|
const char* displayUnit = reader.hasAttribute("displayUnit")
|
|
? reader.getAttribute<const char*>("displayUnit")
|
|
: nullptr;
|
|
const char* alias =
|
|
reader.hasAttribute("alias") ? reader.getAttribute<const char*>("alias") : nullptr;
|
|
const char* rowSpan =
|
|
reader.hasAttribute("rowSpan") ? reader.getAttribute<const char*>("rowSpan") : nullptr;
|
|
const char* colSpan =
|
|
reader.hasAttribute("colSpan") ? reader.getAttribute<const char*>("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<std::string> styleSet;
|
|
|
|
escaped_list_separator<char> e('\0', '|', '\0');
|
|
std::string line = std::string(style);
|
|
tokenizer<escaped_list_separator<char>> tok(line, e);
|
|
|
|
for (tokenizer<escaped_list_separator<char>>::iterator i = tok.begin(); i != tok.end();
|
|
++i) {
|
|
styleSet.insert(*i);
|
|
}
|
|
setStyle(styleSet);
|
|
}
|
|
if (alignment) {
|
|
int alignmentCode = 0;
|
|
using namespace boost;
|
|
|
|
escaped_list_separator<char> e('\0', '|', '\0');
|
|
std::string line = std::string(alignment);
|
|
tokenizer<escaped_list_separator<char>> tok(line, e);
|
|
|
|
for (tokenizer<escaped_list_separator<char>>::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 << "<Cell ";
|
|
|
|
if (!noContent) {
|
|
os << "address=\"" << address.toString() << "\" ";
|
|
|
|
if (isUsed(EXPRESSION_SET)) {
|
|
std::string content;
|
|
getStringContent(content, true);
|
|
os << "content=\"" << App::Property::encodeAttribute(content) << "\" ";
|
|
}
|
|
}
|
|
|
|
if (isUsed(ALIGNMENT_SET)) {
|
|
os << "alignment=\"" << encodeAlignment(alignment) << "\" ";
|
|
}
|
|
|
|
if (isUsed(STYLE_SET)) {
|
|
os << "style=\"" << encodeStyle(style) << "\" ";
|
|
}
|
|
|
|
if (isUsed(FOREGROUND_COLOR_SET)) {
|
|
os << "foregroundColor=\"" << encodeColor(foregroundColor) << "\" ";
|
|
}
|
|
|
|
if (isUsed(BACKGROUND_COLOR_SET)) {
|
|
os << "backgroundColor=\"" << encodeColor(backgroundColor) << "\" ";
|
|
}
|
|
|
|
if (isUsed(DISPLAY_UNIT_SET)) {
|
|
os << "displayUnit=\"" << App::Property::encodeAttribute(displayUnit.stringRep) << "\" ";
|
|
}
|
|
|
|
if (isUsed(ALIAS_SET)) {
|
|
os << "alias=\"" << App::Property::encodeAttribute(alias) << "\" ";
|
|
}
|
|
|
|
if (isUsed(SPANS_SET)) {
|
|
os << "rowSpan=\"" << rowSpan << "\" ";
|
|
os << "colSpan=\"" << colSpan << "\" ";
|
|
}
|
|
|
|
os << "/>";
|
|
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<std::string>& style)
|
|
{
|
|
std::string s;
|
|
std::set<std::string>::const_iterator j = style.begin();
|
|
std::set<std::string>::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<App::PropertyString>()) {
|
|
const App::PropertyString* stringProp = static_cast<const App::PropertyString*>(prop);
|
|
qFormatted = QString::fromUtf8(stringProp->getValue());
|
|
}
|
|
else if (prop->isDerivedFrom<App::PropertyQuantity>()) {
|
|
double rawVal = static_cast<App::PropertyQuantity*>(prop)->getValue();
|
|
const App::PropertyQuantity* floatProp = static_cast<const App::PropertyQuantity*>(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<App::PropertyFloat>()) {
|
|
double rawVal = static_cast<const App::PropertyFloat*>(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<App::PropertyInteger>()) {
|
|
double rawVal = static_cast<const App::PropertyInteger*>(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();
|
|
}
|