/*************************************************************************** * Copyright (c) Eivind Kvedalen (eivind@kvedalen.name) 2015 * * * * 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_ #endif #include "Cell.h" #include "Utils.h" #include #include #include #include "Expression.h" #include "Sheet.h" #include #ifdef _MSC_VER #define __func__ __FUNCTION__ #endif using namespace App; using namespace Spreadsheet; 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; const int Cell::SPANS_UPDATED = 0x10000000; /* 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; /** * Construct a CellContent object. * * @param _row The row of the cell in the spreadsheet that contains is. * @param _col The column of the cell in the spreadsheet that contains is. * @param _owner The spreadsheet that owns this cell. * */ Cell::Cell(const CellAddress &_address, PropertySheet *_owner) : address(_address) , owner(_owner) , used(0) , expression(0) , alignment(ALIGNMENT_HIMPLIED | ALIGNMENT_LEFT | ALIGNMENT_VIMPLIED | ALIGNMENT_VCENTER) , style() , foregroundColor(0, 0, 0, 1) , backgroundColor(1, 1, 1, 1) , displayUnit() , computedUnit() , rowSpan(1) , colSpan(1) , anchor() { assert(address.isValid()); } /** * Destroy a CellContent object. * */ Cell::Cell(const Cell &other) : address(other.address) , owner(other.owner) , used(other.used) , expression(other.expression ? other.expression->copy() : 0) , style(other.style) , alignment(other.alignment) , foregroundColor(other.foregroundColor) , backgroundColor(other.backgroundColor) , displayUnit(other.displayUnit) , computedUnit(other.computedUnit) , colSpan(other.colSpan) , rowSpan(other.rowSpan) { } Cell &Cell::operator =(const Cell &rhs) { PropertySheet::Signaller signaller(*owner); used = 0; address = rhs.address; owner = rhs.owner; setExpression(rhs.expression ? rhs.expression->copy() : 0); setStyle(rhs.style); setAlignment(rhs.alignment); setForeground(rhs.foregroundColor); setBackground(rhs.backgroundColor); setDisplayUnit(rhs.displayUnit.stringRep); setComputedUnit(rhs.computedUnit); setSpans(rhs.rowSpan, rhs.colSpan); return *this; } Cell::~Cell() { if (expression) delete expression; } /** * Set the expression tree to \a expr. * */ void Cell::setExpression(Expression *expr) { PropertySheet::Signaller signaller(*owner); /* Remove dependencies */ owner->removeDependencies(address); if (expression) delete expression; expression = expr; setUsed(EXPRESSION_SET, expression != 0); /* Update dependencies */ owner->addDependencies(address); } /** * Get the expression tree. * */ const Expression *Cell::getExpression() const { return expression; } /** * Get string content. * */ bool Cell::getStringContent(std::string & s) const { if (expression) { if (freecad_dynamic_cast(expression)) { s = static_cast(expression)->getText(); char * end; errno = 0; strtod(s.c_str(), &end); if (!*end && errno == 0) s = "'" + s; } else if (freecad_dynamic_cast(expression)) s = "=" + expression->toString(); else if (freecad_dynamic_cast(expression)) s = expression->toString(); else s = "=" + expression->toString(); return true; } else { s = ""; return false; } } void Cell::setContent(const char * value) { PropertySheet::Signaller signaller(*owner); Expression * expr = 0; setUsed(PARSE_EXCEPTION_SET, false); if (value != 0) { if (*value == '=') { try { expr = ExpressionParser::parse(owner->sheet(), value + 1); } catch (Base::Exception & e) { QString msg = QString::fromUtf8("ERR: %1").arg(QString::fromUtf8(e.what())); expr = new StringExpression(owner->sheet(), value); setUsed(PARSE_EXCEPTION_SET); } } else if (*value == '\'') expr = new StringExpression(owner->sheet(), value + 1); else if (*value != '\0') { char * end; errno = 0; double float_value = strtod(value, &end); if (!*end && errno == 0) expr = new NumberExpression(owner->sheet(), float_value); else { try { expr = ExpressionParser::parse(owner->sheet(), value); if (expr) delete expr->eval(); } catch (Base::Exception &) { expr = new StringExpression(owner->sheet(), value); } } } } setExpression(expr); } /** * 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::Signaller signaller(*owner); alignment = _alignment; setUsed(ALIGNMENT_SET, alignment != (ALIGNMENT_HIMPLIED | ALIGNMENT_LEFT | ALIGNMENT_VIMPLIED | ALIGNMENT_VCENTER)); } } /** * 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::Signaller signaller(*owner); style = _style; setUsed(STYLE_SET, style.size() > 0); } } /** * 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 Color &color) { if (color != foregroundColor) { PropertySheet::Signaller signaller(*owner); foregroundColor = color; setUsed(FOREGROUND_COLOR_SET, foregroundColor != App::Color(0, 0, 0, 1)); } } /** * Get foreground color of the cell. * */ bool Cell::getForeground(Color &color) const { color = foregroundColor; return isUsed(FOREGROUND_COLOR_SET); } /** * Set background color of the cell to \a color. * */ void Cell::setBackground(const Color &color) { if (color != backgroundColor) { PropertySheet::Signaller signaller(*owner); backgroundColor = color; setUsed(BACKGROUND_COLOR_SET, backgroundColor != App::Color(1, 1, 1, 1)); } } /** * Get the background color of the cell into \a color. * * @returns true if the background color was previously set. * */ bool Cell::getBackground(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.size() > 0) { std::auto_ptr e(ExpressionParser::parseUnit(owner->sheet(), unit.c_str())); newDisplayUnit = DisplayUnit(unit, e->getUnit(), e->getScaler()); } if (newDisplayUnit != displayUnit) { PropertySheet::Signaller signaller(*owner); displayUnit = newDisplayUnit; setUsed(DISPLAY_UNIT_SET, !displayUnit.isEmpty()); } } /** * 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::Signaller signaller(*owner); alias = n; setUsed(ALIAS_SET, !alias.empty()); } } 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::Signaller signaller(*owner); computedUnit = unit; setUsed(COMPUTED_UNIT_SET, !computedUnit.isEmpty()); } /** * 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::Signaller signaller(*owner); rowSpan = rows; colSpan = columns; setUsed(SPANS_SET, (rowSpan != 1 || colSpan != 1) ); setUsed(SPANS_UPDATED); } } /** * 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) { exceptionStr = e; setUsed(EXCEPTION_SET); } void Cell::setParseException(const std::string &e) { exceptionStr = e; setUsed(PARSE_EXCEPTION_SET); } void Cell::setResolveException(const std::string &e) { exceptionStr = e; setUsed(RESOLVE_EXCEPTION_SET); } void Cell::clearResolveException() { setUsed(RESOLVE_EXCEPTION_SET, false); } void Cell::clearException() { if (!isUsed(PARSE_EXCEPTION_SET)) exceptionStr = ""; setUsed(EXCEPTION_SET, false); } void Cell::clearDirty() { owner->clearDirty(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) { const char* style = reader.hasAttribute("style") ? reader.getAttribute("style") : 0; const char* alignment = reader.hasAttribute("alignment") ? reader.getAttribute("alignment") : 0; const char* content = reader.hasAttribute("content") ? reader.getAttribute("content") : ""; const char* foregroundColor = reader.hasAttribute("foregroundColor") ? reader.getAttribute("foregroundColor") : 0; const char* backgroundColor = reader.hasAttribute("backgroundColor") ? reader.getAttribute("backgroundColor") : 0; const char* displayUnit = reader.hasAttribute("displayUnit") ? reader.getAttribute("displayUnit") : 0; const char* alias = reader.hasAttribute("alias") ? reader.getAttribute("alias") : 0; const char* rowSpan = reader.hasAttribute("rowSpan") ? reader.getAttribute("rowSpan") : 0; const char* colSpan = reader.hasAttribute("colSpan") ? reader.getAttribute("colSpan") : 0; // Don't trigger multiple updates below; wait until everything is loaded by calling unfreeze() below. PropertySheet::Signaller 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) { Color color = decodeColor(foregroundColor, Color(0, 0, 0, 1)); setForeground(color); } if (backgroundColor) { Color color = decodeColor(backgroundColor, Color(1, 1, 1, 1)); setBackground(color); } if (displayUnit) setDisplayUnit(displayUnit); if (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 { if (!isUsed()) return; writer.Stream() << writer.ind() << "" << 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; owner->setDirty(address); } /** * 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(ExpressionVisitor &v) { if (expression) v.visit(expression); } /** * Decode aligment 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") alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | 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") alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | 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 throw Base::Exception("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 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. * */ Color Cell::decodeColor(const std::string & color, const Color & defaultColor) { if (color.size() == 7 || color.size() == 9) { Color c; if (color[0] != '#') return defaultColor; unsigned int value = strtoul(color.c_str() + 1, 0, 16); if (color.size() == 7) value = (value << 8) | 0xff; c.setPackedValue(value); return c; } else return defaultColor; }