FreeCAD/src/Mod/Spreadsheet/App/Sheet.cpp

1334 lines
37 KiB
C++

/***************************************************************************
* 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 <boost/tokenizer.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/assign.hpp>
#include <boost/graph/topological_sort.hpp>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DynamicProperty.h>
#include <Base/Exception.h>
#include <Base/Placement.h>
#include <Base/Reader.h>
#include <Base/Writer.h>
#include <Base/Tools.h>
#include "SpreadsheetExpression.h"
#include "Sheet.h"
#include "SheetObserver.h"
#include "Utils.h"
#include "Range.h"
#include "SheetPy.h"
#include <ostream>
#include <fstream>
#include <string>
#include <iomanip>
#include <boost/regex.hpp>
#include <boost/bind.hpp>
#include <deque>
using namespace Base;
using namespace App;
using namespace Spreadsheet;
PROPERTY_SOURCE(Spreadsheet::Sheet, App::DocumentObject)
typedef boost::adjacency_list <
boost::vecS, // class OutEdgeListS : a Sequence or an AssociativeContainer
boost::vecS, // class VertexListS : a Sequence or a RandomAccessContainer
boost::directedS, // class DirectedS : This is a directed graph
boost::no_property, // class VertexProperty:
boost::no_property, // class EdgeProperty:
boost::no_property, // class GraphProperty:
boost::listS // class EdgeListS:
> DependencyList;
typedef boost::graph_traits<DependencyList> Traits;
typedef Traits::vertex_descriptor Vertex;
typedef Traits::edge_descriptor Edge;
/**
* Construct a new Sheet object.
*/
Sheet::Sheet()
: DocumentObject()
, props(this)
, cells(this)
{
ADD_PROPERTY_TYPE(docDeps, (0), "Spreadsheet", (PropertyType)(Prop_Transient|Prop_ReadOnly|Prop_Hidden), "Dependencies");
ADD_PROPERTY_TYPE(cells, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden), "Cell contents");
ADD_PROPERTY_TYPE(columnWidths, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden), "Column widths");
ADD_PROPERTY_TYPE(rowHeights, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden), "Row heights");
docDeps.setSize(0);
onRenamedDocumentConnection = GetApplication().signalRenameDocument.connect(boost::bind(&Spreadsheet::Sheet::onRenamedDocument, this, _1));
onRelabledDocumentConnection = GetApplication().signalRelabelDocument.connect(boost::bind(&Spreadsheet::Sheet::onRelabledDocument, this, _1));
}
/**
* @brief Sheet::~Sheet
*
* The destructor; clear properties to release all memory.
*
*/
Sheet::~Sheet()
{
clearAll();
}
/**
* Clear all cells in the sheet.
*/
void Sheet::clearAll()
{
cells.clear();
std::vector<std::string> propNames = props.getDynamicPropertyNames();
for (std::vector<std::string>::const_iterator i = propNames.begin(); i != propNames.end(); ++i)
props.removeDynamicProperty((*i).c_str());
propAddress.clear();
cellErrors.clear();
columnWidths.clear();
rowHeights.clear();
removedAliases.clear();
docDeps.setValues(std::vector<DocumentObject*>());
for (ObserverMap::iterator i = observers.begin(); i != observers.end(); ++i)
delete i->second;
observers.clear();
}
/**
* Import a file into the spreadsheet object.
*
* @param filename Name of file to import
* @param delimiter The field delimiter charater used.
* @param quoteChar Quote character, if any (set to '\0' to disable).
* @param escapeChar The escape character used, if any (set to '0' to disable).
*
* @returns True if successful, false if something failed.
*/
bool Sheet::importFromFile(const std::string &filename, char delimiter, char quoteChar, char escapeChar)
{
std::ifstream file;
int row = 0;
PropertySheet::Signaller signaller(cells);
clearAll();
file.open(filename.c_str(), std::ios::in);
if (file.is_open()) {
std::string line;
while (std::getline(file, line)) {
using namespace boost;
try {
escaped_list_separator<char> e;
int col = 0;
if (quoteChar)
e = escaped_list_separator<char>(escapeChar, delimiter, quoteChar);
else
e = escaped_list_separator<char>('\0', delimiter, '\0');
tokenizer<escaped_list_separator<char> > tok(line, e);
for(tokenizer<escaped_list_separator<char> >::iterator i = tok.begin(); i != tok.end();++i) {
if ((*i).size() > 0)
setCell(CellAddress(row, col), (*i).c_str());
col++;
}
}
catch (...) {
return false;
}
++row;
}
file.close();
return true;
}
else
return false;
}
/**
* Write an escaped version of the string \a s to the stream \a out.
*
* @param s The string to write.
* @param quoteChar The quote character.
* @param escapeChar The escape character.
* @param stream The stream to output the escaped string to.
*
*/
static void writeEscaped(std::string const& s, char quoteChar, char escapeChar, std::ostream & out) {
out << quoteChar;
for (std::string::const_iterator i = s.begin(), end = s.end(); i != end; ++i) {
unsigned char c = *i;
if (c != quoteChar)
out << c;
else {
out << escapeChar;
out << c;
}
}
out << quoteChar;
}
/**
* Export spreadsheet data to file.
*
* @param filename Filename to store data to.
* @param delimiter Field delimiter
* @param quoteChar Quote character ('\0' to disable)
* @param escapeChar Escape character ('\0' to disable)
*
* @returns True if store is successful, false if something failed.
*
*/
bool Sheet::exportToFile(const std::string &filename, char delimiter, char quoteChar, char escapeChar) const
{
std::ofstream file;
int prevRow = -1, prevCol = -1;
file.open(filename.c_str(), std::ios::out | std::ios::ate | std::ios::binary);
if (!file.is_open())
return false;
std::set<CellAddress> usedCells = cells.getUsedCells();
std::set<CellAddress>::const_iterator i = usedCells.begin();
while (i != usedCells.end()) {
Property * prop = getProperty(*i);
if (prevRow != -1 && prevRow != i->row()) {
for (int j = prevRow; j < i->row(); ++j)
file << std::endl;
prevCol = 0;
}
if (prevCol != -1 && i->col() != prevCol) {
for (int j = prevCol; j < i->col(); ++j)
file << delimiter;
}
std::stringstream field;
if (prop->isDerivedFrom((PropertyQuantity::getClassTypeId())))
field << static_cast<PropertyQuantity*>(prop)->getValue();
else if (prop->isDerivedFrom((PropertyFloat::getClassTypeId())))
field << static_cast<PropertyFloat*>(prop)->getValue();
else if (prop->isDerivedFrom((PropertyString::getClassTypeId())))
field << static_cast<PropertyString*>(prop)->getValue();
else
assert(0);
std::string str = field.str();
if (quoteChar && str.find(quoteChar) != std::string::npos)
writeEscaped(str, quoteChar, escapeChar, file);
else
file << str;
prevRow = i->row();
prevCol = i->col();
++i;
}
file << std::endl;
file.close();
return true;
}
/**
* Merge a rectangle specified by \a range into one logical cell.
* Data in all but the upper right cell are cleared when the cells are merged.
*
* @param range Range to merge.
* @returns True if the cells were merged, false if the merge was unsuccessful.
*
*/
bool Sheet::mergeCells(const Range & range)
{
return cells.mergeCells(range.from(), range.to());
}
/**
* Split a previously merged cell specified by \a address into its individual cells.
* The address can point to any of the cells that make up the merged cell.
*
* @param address Address of cell to split
*
*/
void Sheet::splitCell(CellAddress address)
{
cells.splitCell(address);
}
/**
* Get contents of the cell specified by \a address, or 0 if it is not defined
*
* @returns A CellContent object or 0.
*/
Cell *Sheet::getCell(CellAddress address)
{
return cells.getValue(address);
}
/**
* Get cell contents specified by \a address.
*
* @param address
*/
Cell *Sheet::getNewCell(CellAddress address)
{
Cell * cell = getCell(address);
if (cell == 0)
cell = cells.createCell(address);
return cell;
}
/**
* Set cell given by \a address to \a contents.
*
* @param address Address of cell to set.
* @param contents New contents of given cell.
*
*/
void Sheet::setCell(const char * address, const char * contents)
{
assert(address != 0 && contents != 0);
setCell(CellAddress(address), contents);
}
/**
* Set cell at \a address to \a value. If a merged cell is specified, the upper right corner of the
* merged cell must be specified.
*
* @param address Row position of cell.
* @param value String value of expression.
*
*/
void Sheet::setCell(CellAddress address, const char * value)
{
assert(value != 0);
if (*value == '\0') {
clear(address, false);
return;
}
// Update expression, delete old first if necessary
Cell * cell = getNewCell(address);
if (cell->getExpression()) {
setContent(address, 0);
}
setContent(address, value);
// Recompute dependencies
touch();
}
/**
* Get the Python object for the Sheet.
*
* @returns The Python object.
*/
PyObject *Sheet::getPyObject(void)
{
if (PythonObject.is(Py::_None())){
// ref counter is set to 1
PythonObject = Py::Object(new SheetPy(this),true);
}
return Py::new_reference_to(PythonObject);
}
/**
* Get the Cell Property for the cell at \a key.
*
* @returns The Property object.
*
*/
Property * Sheet::getProperty(CellAddress key) const
{
return props.getDynamicPropertyByName(key.toString().c_str());
}
/**
* @brief Get a dynamic property.
* @param addr Name of dynamic propeerty.
* @return Pointer to property, or 0 if it does not exist.
*/
Property * Sheet::getProperty(const char * addr) const
{
return props.getDynamicPropertyByName(addr);
}
/**
* Get the address as \a address of the Property \a prop. This function
* throws an exception if the property is not found.
*
*/
void Sheet::getCellAddress(const Property *prop, CellAddress & address)
{
std::map<const Property*, CellAddress >::const_iterator i = propAddress.find(prop);
if (i != propAddress.end())
address = i->second;
else
throw Base::Exception("Property is not a cell");
}
/**
* @brief Get a map with column indices and widths.
* @return Map with results.
*/
std::map<int, int> Sheet::getColumnWidths() const
{
return columnWidths.getValues();
}
/**
* @brief Get a map with row indices and heights.
* @return Map with results
*/
std::map<int, int> Sheet::getRowHeights() const
{
return rowHeights.getValues();
}
/**
* @brief Remove all aliases.
*
*/
void Sheet::removeAliases()
{
std::map<CellAddress, std::string>::iterator i = removedAliases.begin();
while (i != removedAliases.end()) {
props.removeDynamicProperty(i->second.c_str());
++i;
}
removedAliases.clear();
}
/**
* Update internal structure when document is set for this property.
*/
void Sheet::onSettingDocument()
{
cells.documentSet();
}
/**
* Set the property for cell \key to a PropertyFloat with the value \a value.
* If the Property exists, but of wrong type, the previous Property is destroyed and recreated as the correct type.
*
* @param key The address of the cell we want to create a Property for
* @param value The value we want to assign to the Property.
*
*/
Property * Sheet::setFloatProperty(CellAddress key, double value)
{
Property * prop = props.getPropertyByName(key.toString().c_str());
PropertyFloat * floatProp;
if (!prop || prop->getTypeId() != PropertyFloat::getClassTypeId()) {
if (prop) {
props.removeDynamicProperty(key.toString().c_str());
propAddress.erase(prop);
}
floatProp = freecad_dynamic_cast<PropertyFloat>(props.addDynamicProperty("App::PropertyFloat", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_Transient));
}
else
floatProp = static_cast<PropertyFloat*>(prop);
propAddress[floatProp] = key;
floatProp->setValue(value);
return floatProp;
}
/**
* Set the property for cell \key to a PropertyQuantity with \a value and \a unit.
* If the Property exists, but of wrong type, the previous Property is destroyed and recreated as the correct type.
*
* @param key The address of the cell we want to create a Property for
* @param value The value we want to assign to the Property.
* @param unit The associated unit for \a value.
*
*/
Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base::Unit & unit)
{
Property * prop = props.getPropertyByName(key.toString().c_str());
PropertySpreadsheetQuantity * quantityProp;
if (!prop || prop->getTypeId() != PropertySpreadsheetQuantity::getClassTypeId()) {
if (prop) {
props.removeDynamicProperty(key.toString().c_str());
propAddress.erase(prop);
}
Property * p = props.addDynamicProperty("Spreadsheet::PropertySpreadsheetQuantity", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_Transient);
quantityProp = freecad_dynamic_cast<PropertySpreadsheetQuantity>(p);
}
else
quantityProp = static_cast<PropertySpreadsheetQuantity*>(prop);
propAddress[quantityProp] = key;
quantityProp->setValue(value);
quantityProp->setUnit(unit);
cells.setComputedUnit(key, unit);
return quantityProp;
}
/**
* Set the property for cell \key to a PropertyString with \a value.
* If the Property exists, but of wrong type, the previous Property is destroyed and recreated as the correct type.
*
* @param key The address of the cell we want to create a Property for
* @param value The value we want to assign to the Property.
*
*/
Property * Sheet::setStringProperty(CellAddress key, const std::string & value)
{
Property * prop = props.getPropertyByName(key.toString().c_str());
PropertyString * stringProp = freecad_dynamic_cast<PropertyString>(prop);
if (!stringProp) {
if (prop) {
props.removeDynamicProperty(key.toString().c_str());
propAddress.erase(prop);
}
stringProp = freecad_dynamic_cast<PropertyString>(props.addDynamicProperty("App::PropertyString", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_Transient));
}
propAddress[stringProp] = key;
stringProp->setValue(value.c_str());
return stringProp;
}
/**
* @brief Update the alias for the cell at \a key.
* @param key Cell to update.
*/
void Sheet::updateAlias(CellAddress key)
{
std::string alias;
Property * prop = props.getDynamicPropertyByName(key.toString().c_str());
if (!prop)
return;
Cell * cell = getCell(key);
if (cell && cell->getAlias(alias)) {
Property * aliasProp = props.getDynamicPropertyByName(alias.c_str());
/* Update or create alias? */
if (aliasProp) {
// Type of alias and property must always be the same
if (aliasProp->getTypeId() != prop->getTypeId()) {
props.removeDynamicProperty(alias.c_str());
aliasProp = 0;
}
}
if (!aliasProp)
aliasProp = props.addDynamicProperty(prop->getTypeId().getName(), alias.c_str(), 0, 0, Prop_ReadOnly | Prop_Transient);
aliasProp->Paste(*prop);
}
}
/**
* Update the Propery given by \a key. This will also eventually trigger recomputations of cells depending on \a key.
*
* @param key The address of the cell we want to recompute.
*
*/
void Sheet::updateProperty(CellAddress key)
{
Cell * cell = getCell(key);
if (cell != 0) {
Expression * output;
const Expression * input = cell->getExpression();
if (input) {
output = input->eval();
}
else {
std::string s;
if (cell->getStringContent(s))
output = new StringExpression(this, s);
else
output = new StringExpression(this, "");
}
/* Eval returns either NumberExpression or StringExpression objects */
if (freecad_dynamic_cast<NumberExpression>(output)) {
NumberExpression * number = static_cast<NumberExpression*>(output);
if (number->getUnit().isEmpty())
setFloatProperty(key, number->getValue());
else
setQuantityProperty(key, number->getValue(), number->getUnit());
}
else
setStringProperty(key, freecad_dynamic_cast<StringExpression>(output)->getText().c_str());
delete output;
}
else
clear(key);
cellUpdated(key);
}
/**
* Retrieve a specifc Property given by \a name.
* This function might throw an exception if something fails, but might also
* return 0 in case the property is not found.
*
* @returns The property, or 0 if not found.
*
*/
Property *Sheet::getPropertyByName(const char* name) const
{
Property * prop = getProperty(name);
if (prop)
return prop;
else
return DocumentObject::getPropertyByName(name);
}
/**
* @brief Get name of a property, given a pointer to it.
* @param prop Pointer to property.
* @return Pointer to string.
*/
const char *Sheet::getPropertyName(const Property *prop) const
{
const char * name = props.getPropertyName(prop);
if (name)
return name;
else
return PropertyContainer::getPropertyName(prop);
}
/**
* @brief Recompute cell at address \a p.
* @param p Address of cell.
*/
void Sheet::recomputeCell(CellAddress p)
{
Cell * cell = cells.getValue(p);
std::string docName = getDocument()->Label.getValue();
std::string docObjName = std::string(getNameInDocument());
std::string name = docName + "#" + docObjName + "." + p.toString();
try {
if (cell) {
cell->clearException();
cell->clearResolveException();
}
updateProperty(p);
cells.clearDirty(p);
cellErrors.erase(p);
}
catch (const Base::Exception & e) {
QString msg = QString::fromUtf8("ERR: %1").arg(QString::fromUtf8(e.what()));
setStringProperty(p, Base::Tools::toStdString(msg));
if (cell)
cell->setException(e.what());
// Mark as erronous
cellErrors.insert(p);
}
updateAlias(p);
if (!cell || cell->spansChanged())
cellSpanChanged(p);
}
/**
* Update the document properties.
*
*/
DocumentObjectExecReturn *Sheet::execute(void)
{
// Remove all aliases first
removeAliases();
// Get dirty cells that we have to recompute
std::set<CellAddress> dirtyCells = cells.getDirty();
// Always recompute cells that have failed
for (std::set<CellAddress>::const_iterator i = cellErrors.begin(); i != cellErrors.end(); ++i) {
cells.recomputeDependencies(*i);
dirtyCells.insert(*i);
}
// Push dirty cells onto queue
for (std::set<CellAddress>::const_iterator i = dirtyCells.begin(); i != dirtyCells.end(); ++i) {
// Create queue and a graph structure to compute order of evaluation
std::deque<CellAddress> workQueue;
DependencyList graph;
std::map<CellAddress, Vertex> VertexList;
std::map<Vertex, CellAddress> VertexIndexList;
workQueue.push_back(*i);
while (workQueue.size() > 0) {
CellAddress currPos = workQueue.front();
std::set<CellAddress> s;
// Get other cells that depends on the current cell (currPos)
providesTo(currPos, s);
workQueue.pop_front();
// Insert into map of CellPos -> Index, if it doesn't exist already
if (VertexList.find(currPos) == VertexList.end()) {
VertexList[currPos] = add_vertex(graph);
VertexIndexList[VertexList[currPos]] = currPos;
}
// Process cells that depend on the current cell
std::set<CellAddress>::const_iterator i = s.begin();
while (i != s.end()) {
// Insert into map of CellPos -> Index, if it doesn't exist already
if (VertexList.find(*i) == VertexList.end()) {
VertexList[*i] = add_vertex(graph);
VertexIndexList[VertexList[*i]] = *i;
workQueue.push_back(*i);
}
// Add edge to graph to signal dependency
add_edge(VertexList[currPos], VertexList[*i], graph);
++i;
}
}
// Compute cells
std::list<Vertex> make_order;
// Sort graph topologically to find evaluation order
try {
boost::topological_sort(graph, std::front_inserter(make_order));
// Recompute cells
std::list<Vertex>::const_iterator i = make_order.begin();
while (i != make_order.end()) {
recomputeCell(VertexIndexList[*i]);
++i;
}
}
catch (std::exception) {
// Cycle detected; flag all with errors
std::map<CellAddress, Vertex>::const_iterator i = VertexList.begin();
while (i != VertexList.end()) {
Cell * cell = cells.getValue(i->first);
// Mark as erronous
cellErrors.insert(i->first);
if (cell)
cell->setException("Circular dependency.");
updateProperty(i->first);
updateAlias(i->first);
++i;
}
}
}
// Signal update of column widths
const std::set<int> & dirtyColumns = columnWidths.getDirty();
for (std::set<int>::const_iterator i = dirtyColumns.begin(); i != dirtyColumns.end(); ++i)
columnWidthChanged(*i, columnWidths.getValue(*i));
// Signal update of row heights
const std::set<int> & dirtyRows = rowHeights.getDirty();
for (std::set<int>::const_iterator i = dirtyRows.begin(); i != dirtyRows.end(); ++i)
rowHeightChanged(*i, rowHeights.getValue(*i));
//cells.clearDirty();
rowHeights.clearDirty();
columnWidths.clearDirty();
std::set<DocumentObject*> ds(cells.getDocDeps());
// Make sure we don't reference ourselves
ds.erase(this);
std::vector<DocumentObject*> dv(ds.begin(), ds.end());
docDeps.setValues(dv);
purgeTouched();
if (cellErrors.size() == 0)
return DocumentObject::StdReturn;
else
return new DocumentObjectExecReturn("One or more cells failed contains errors.", this);
}
/**
* Determine whether this object needs to be executed to update internal structures.
*
*/
short Sheet::mustExecute(void) const
{
if (cellErrors.size() > 0 || cells.isTouched() || columnWidths.isTouched() || rowHeights.isTouched())
return 1;
else if (cells.getDocDeps().size() == 0)
return 0;
else
return -1;
}
/**
* Clear the cell at \a address. If \a all is false, only the text or expression
* contents are cleared. If \a all is true everything in the cell
* is cleared, including color, style, etc.
*
* @param address Address of cell to clear
* @param all Whether we should clear all or not.
*
*/
void Sheet::clear(CellAddress address, bool all)
{
Cell * cell = getCell(address);
std::string addr = address.toString();
Property * prop = props.getDynamicPropertyByName(addr.c_str());
// Remove alias, if defined
std::string aliasStr;
if (cell && cell->getAlias(aliasStr))
props.removeDynamicProperty(aliasStr.c_str());
cells.clear(address);
// Update dependencies
std::set<DocumentObject*> ds(cells.getDocDeps());
// Make sure we don't reference ourselves
ds.erase(this);
std::vector<DocumentObject*> dv(ds.begin(), ds.end());
docDeps.setValues(dv);
propAddress.erase(prop);
props.removeDynamicProperty(addr.c_str());
}
/**
* Get row an column span for the cell at (row, col).
*
* @param address Address of cell
* @param rows The number of unit cells this cell spans
* @param cols The number of unit rows this cell spans
*
*/
void Sheet::getSpans(CellAddress address, int &rows, int &cols) const
{
return cells.getSpans(address, rows, cols);
}
/**
* Determine whether this cell is merged with another or not.
*
* @param adderss
*
* @returns True if cell is merged, false if not.
*
*/
bool Sheet::isMergedCell(CellAddress address) const
{
return cells.isMergedCell(address);
}
/**
* @brief Set column with of column \a col to \a width-
* @param col Index of column.
* @param width New width of column.
*/
void Sheet::setColumnWidth(int col, int width)
{
columnWidths.setValue(col, width);
}
/**
* @brief Get column with of column at index \a col.
* @param col
* @return
*/
int Sheet::getColumnWidth(int col) const
{
return columnWidths.getValue(col);
}
/**
* @brief Set row height of row given by index in \row to \a height.
* @param row Row index.
* @param height New height of row.
*/
void Sheet::setRowHeight(int row, int height)
{
rowHeights.setValue(row, height);
}
/**
* @brief Get height of row at index \a row.
* @param row Index of row.
* @return Height
*/
int Sheet::getRowHeight(int row) const
{
return rowHeights.getValue(row);
}
/**
* Get a vector of strings with addresses of all used cells.
*
* @returns vector of strings.
*
*/
std::vector<std::string> Sheet::getUsedCells() const
{
std::vector<std::string> usedCells;
// Insert int usedSet
std::set<CellAddress> usedSet = cells.getUsedCells();
for (std::set<CellAddress>::const_iterator i = usedSet.begin(); i != usedSet.end(); ++i)
usedCells.push_back(i->toString());
return usedCells;
}
/**
* Insert \a count columns at before column \a col in the spreadsheet.
*
* @param col Address of column where the columns are inserted.
* @param count Number of columns to insert
*
*/
void Sheet::insertColumns(int col, int count)
{
cells.insertColumns(col, count);
}
/**
* Remove \a count columns at column \a col.
*
* @param col Address of first column to remove.
* @param count Number of columns to remove.
*
*/
void Sheet::removeColumns(int col, int count)
{
cells.removeColumns(col, count);
}
/**
* Inser \a count rows at row \a row.
*
* @param row Address of row where the rows are inserted.
* @param count Number of rows to insert.
*
*/
void Sheet::insertRows(int row, int count)
{
cells.insertRows(row, count);
}
/**
* Remove \a count rows starting at \a row from the spreadsheet.
*
* @param row Address of first row to remove.
* @param count Number of rows to remove.
*
*/
void Sheet::removeRows(int row, int count)
{
cells.removeRows(row, count);
}
/**
* @brief Set content of cell at \a address to \a value.
* @param address Address of cell
* @param value New value
*/
void Sheet::setContent(CellAddress address, const char *value)
{
cells.setContent(address, value);
}
/**
* @brief Set alignment of content in cell at \a address to \a alignment.
* @param address Address of cell
* @param alignment New alignment
*/
void Sheet::setAlignment(CellAddress address, int alignment)
{
cells.setAlignment(address, alignment);
}
/**
* @brief Set style of cell at \a address to \a style.
* @param address Address of cell
* @param style New style
*/
void Sheet::setStyle(CellAddress address, const std::set<std::string> &style)
{
cells.setStyle(address, style);
}
/**
* @brief Set foreground (text color) of cell at address \a address to \a color.
* @param address Address of cell
* @param color New color
*/
void Sheet::setForeground(CellAddress address, const Color &color)
{
cells.setForeground(address, color);
}
/**
* @brief Set background color of cell at address \a address to \a color.
* @param address Address of cell
* @param color New color
*/
void Sheet::setBackground(CellAddress address, const Color &color)
{
cells.setBackground(address, color);
}
/**
* @brief Set display unit of cell at address \a address to \a unit.
* @param address Address of cell
* @param unit New unit
*/
void Sheet::setDisplayUnit(CellAddress address, const std::string &unit)
{
cells.setDisplayUnit(address, unit);
}
/**
* @brief Set computed unit for cell at address \a address to \a unit.
* @param address Address of cell
* @param unit New unit.
*/
void Sheet::setComputedUnit(CellAddress address, const Base::Unit &unit)
{
cells.setComputedUnit(address, unit);
}
/**
* @brief Set alias for cell at address \a address to \a alias. If the alias
* is an empty string, the existing alias is removed.
* @param address Address of cell
* @param alias New alias.
*/
void Sheet::setAlias(CellAddress address, const std::string &alias)
{
std::string existingAlias = getAddressFromAlias(alias);
if (existingAlias.size() > 0) {
if (existingAlias == address.toString()) // Same as old?
return;
else
throw Base::Exception("Alias already defined");
}
else if (alias.size() == 0) // Empty?
cells.setAlias(address, "");
else if (isValidAlias(alias)) // Valid?
cells.setAlias(address, alias);
else
throw Base::Exception("Invalid alias");
}
/**
* @brief Get cell given an alias string
* @param alias Alias for cell
*
* @returns Name of cell, or empty string if not defined
*/
std::string Sheet::getAddressFromAlias(const std::string &alias) const
{
const Cell * cell = cells.getValueFromAlias(alias);
if (cell)
return cell->getAddress().toString();
else
return std::string();
}
/**
* @brief Determine whether a given alias candiate is valid or not.
*
* A candidate is valid is the string is syntactically correct,
* and the alias does not conflict with an existing property.
*
*/
bool Sheet::isValidAlias(const std::string & candidate)
{
// Valid syntactically?
if (!cells.isValidAlias(candidate))
return false;
// Existing alias? Then it's ok
if (getAddressFromAlias(candidate).size() > 0 )
return true;
// Check to see that is does not crash with any other property in the Sheet object.
if (getPropertyByName(candidate.c_str()))
return false;
else
return true;
}
/**
* @brief Set row and column span for the cell at address \a address to \a rows and \a columns.
* @param address Address to upper right corner of cell
* @param rows Rows to span
* @param columns Columns to span
*/
void Sheet::setSpans(CellAddress address, int rows, int columns)
{
cells.setSpans(address, rows, columns);
}
/**
* @brief Called when a document object is renamed.
* @param docObj Renamed document object.
*/
void Sheet::renamedDocumentObject(const DocumentObject * docObj)
{
cells.renamedDocumentObject(docObj);
cells.touch();
}
/**
* @brief Called when alias \a alias at \a address is removed.
* @param address Address of alias.
* @param alias Removed alias.
*/
void Sheet::aliasRemoved(CellAddress address, const std::string & alias)
{
removedAliases[address] = alias;
}
/**
* @brief Return a set of dependencies links for cell at \a address.
* @param address Address of cell
* @return Set of dependencies.
*/
std::set<std::string> Sheet::dependsOn(CellAddress address) const
{
return cells.getDeps(address);
}
/**
* @brief Compute links to cells that cell at \a address provides input to.
* @param address Address of cell
* @param result Set of links.
*/
void Sheet::providesTo(CellAddress address, std::set<std::string> & result) const
{
const char * docName = getDocument()->Label.getValue();
const char * docObjName = getNameInDocument();
std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + address.toString();
std::set<CellAddress> tmpResult = cells.getDeps(fullName);
for (std::set<CellAddress>::const_iterator i = tmpResult.begin(); i != tmpResult.end(); ++i)
result.insert(std::string(docName) + "#" + std::string(docObjName) + "." + i->toString());
}
/**
* @brief Compute links to cells that cell at \a address provides input to.
* @param address Address of cell
* @param result Set of links.
*/
void Sheet::providesTo(CellAddress address, std::set<CellAddress> & result) const
{
const char * docName = getDocument()->Label.getValue();
const char * docObjName = getNameInDocument();
std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + address.toString();
result = cells.getDeps(fullName);
}
void Sheet::onDocumentRestored()
{
cells.resolveAll();
execute();
}
/**
* @brief Slot called when a document is relabelled.
* @param document Relabelled document.
*/
void Sheet::onRelabledDocument(const Document &document)
{
cells.renamedDocument(&document);
cells.purgeTouched();
}
/**
* @brief Unimplemented.
* @param document
*/
void Sheet::onRenamedDocument(const Document &document)
{
}
/**
* @brief Create a document observer for this sheet. Used to track changes.
* @param document document to observer.
*/
void Sheet::observeDocument(Document * document)
{
ObserverMap::const_iterator it = observers.find(document->getName());
if (it != observers.end()) {
// An observer already exists, increase reference counter for it
it->second->ref();
}
else {
// Create a new observer
SheetObserver * observer = new SheetObserver(document, &cells);
observers[document->getName()] = observer;
}
}
TYPESYSTEM_SOURCE(Spreadsheet::PropertySpreadsheetQuantity, App::PropertyQuantity);
Property *PropertySpreadsheetQuantity::Copy() const
{
PropertySpreadsheetQuantity * obj = new PropertySpreadsheetQuantity();
obj->_dValue = _dValue;
obj->_Unit = _Unit;
return obj;
}
void PropertySpreadsheetQuantity::Paste(const Property &from)
{
aboutToSetValue();
_dValue = static_cast<const PropertySpreadsheetQuantity*>(&from)->_dValue;
_Unit = static_cast<const PropertySpreadsheetQuantity*>(&from)->_Unit;
hasSetValue();
}