/*************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "SpreadsheetExpression.h" #include "Sheet.h" #include "SheetObserver.h" #include "Utils.h" #include "Range.h" #include "SheetPy.h" #include #include #include #include #include #include #include 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 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 propNames = props.getDynamicPropertyNames(); for (std::vector::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()); 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 e; int col = 0; if (quoteChar) e = escaped_list_separator(escapeChar, delimiter, quoteChar); else e = escaped_list_separator('\0', delimiter, '\0'); tokenizer > tok(line, e); for(tokenizer >::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 usedCells = cells.getUsedCells(); std::set::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(prop)->getValue(); else if (prop->isDerivedFrom((PropertyFloat::getClassTypeId()))) field << static_cast(prop)->getValue(); else if (prop->isDerivedFrom((PropertyString::getClassTypeId()))) field << static_cast(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_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 Sheet::getColumnWidths() const { return columnWidths.getValues(); } /** * @brief Get a map with row indices and heights. * @return Map with results */ std::map Sheet::getRowHeights() const { return rowHeights.getValues(); } /** * @brief Remove all aliases. * */ void Sheet::removeAliases() { std::map::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(props.addDynamicProperty("App::PropertyFloat", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_Transient)); } else floatProp = static_cast(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(p); } else quantityProp = static_cast(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(prop); if (!stringProp) { if (prop) { props.removeDynamicProperty(key.toString().c_str()); propAddress.erase(prop); } stringProp = freecad_dynamic_cast(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(output)) { NumberExpression * number = static_cast(output); if (number->getUnit().isEmpty()) setFloatProperty(key, number->getValue()); else setQuantityProperty(key, number->getValue(), number->getUnit()); } else setStringProperty(key, freecad_dynamic_cast(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 dirtyCells = cells.getDirty(); // Always recompute cells that have failed for (std::set::const_iterator i = cellErrors.begin(); i != cellErrors.end(); ++i) { cells.recomputeDependencies(*i); dirtyCells.insert(*i); } // Push dirty cells onto queue for (std::set::const_iterator i = dirtyCells.begin(); i != dirtyCells.end(); ++i) { // Create queue and a graph structure to compute order of evaluation std::deque workQueue; DependencyList graph; std::map VertexList; std::map VertexIndexList; workQueue.push_back(*i); while (workQueue.size() > 0) { CellAddress currPos = workQueue.front(); std::set 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::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 make_order; // Sort graph topologically to find evaluation order try { boost::topological_sort(graph, std::front_inserter(make_order)); // Recompute cells std::list::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::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 & dirtyColumns = columnWidths.getDirty(); for (std::set::const_iterator i = dirtyColumns.begin(); i != dirtyColumns.end(); ++i) columnWidthChanged(*i, columnWidths.getValue(*i)); // Signal update of row heights const std::set & dirtyRows = rowHeights.getDirty(); for (std::set::const_iterator i = dirtyRows.begin(); i != dirtyRows.end(); ++i) rowHeightChanged(*i, rowHeights.getValue(*i)); //cells.clearDirty(); rowHeights.clearDirty(); columnWidths.clearDirty(); std::set ds(cells.getDocDeps()); // Make sure we don't reference ourselves ds.erase(this); std::vector 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 ds(cells.getDocDeps()); // Make sure we don't reference ourselves ds.erase(this); std::vector 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 Sheet::getUsedCells() const { std::vector usedCells; // Insert int usedSet std::set usedSet = cells.getUsedCells(); for (std::set::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 &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. * @param address Address of cell * @param alias New alias. */ void Sheet::setAlias(CellAddress address, const std::string &alias) { const Cell * cell = cells.getValueFromAlias(alias); if (cell != 0) throw Base::Exception("Alias already defined."); else cells.setAlias(address, 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 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 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 & 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 tmpResult = cells.getDeps(fullName); for (std::set::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 & 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(&from)->_dValue; _Unit = static_cast(&from)->_Unit; hasSetValue(); }