/*************************************************************************** * Copyright (c) 2002 Jürgen Riegel * * Copyright (c) 2014 Luke Parry * * * * 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 # include # include # include # include # include #endif #include #include #include //TODO: should be in DrawTemplate.h?? #include #include #include #include #include "DrawPage.h" #include "DrawSVGTemplate.h" #include "DrawSVGTemplatePy.h" #include "DrawUtil.h" using namespace TechDraw; PROPERTY_SOURCE(TechDraw::DrawSVGTemplate, TechDraw::DrawTemplate) DrawSVGTemplate::DrawSVGTemplate() { static const char *group = "Template"; ADD_PROPERTY_TYPE(PageResult, (nullptr), group, App::Prop_Output, "Embedded SVG code for template. For system use."); //n/a for users ADD_PROPERTY_TYPE(Template, (""), group, App::Prop_None, "Template file name."); // Width and Height properties shouldn't be set by the user Height.setStatus(App::Property::ReadOnly, true); Width.setStatus(App::Property::ReadOnly, true); Orientation.setStatus(App::Property::ReadOnly, true); std::string svgFilter("Svg files (*.svg *.SVG);;All files (*)"); Template.setFilter(svgFilter); } DrawSVGTemplate::~DrawSVGTemplate() { } PyObject *DrawSVGTemplate::getPyObject() { if (PythonObject.is(Py::_None())) { // ref counter is set to 1 PythonObject = Py::Object(new DrawSVGTemplatePy(this), true); } return Py::new_reference_to(PythonObject); } void DrawSVGTemplate::onChanged(const App::Property* prop) { if (prop == &Template && !isRestoring()) { //if we are restoring an existing file we just want the properties set as they were save, //but if we are not restoring, we need to replace the embedded file and extract the new //EditableTexts. //We could try to find matching field names are preserve the values from //the old template, but there is no guarantee that the same fields will be present. replaceFileIncluded(Template.getValue()); EditableTexts.setValues(getEditableTextsFromTemplate()); } else if (prop == &EditableTexts) { //handled by ViewProvider } TechDraw::DrawTemplate::onChanged(prop); } //parse the Svg code, inserting current EditableTexts values, and return the result as a QString. //While parsing, note the Orientation, Width and Height values in the Svg code. QString DrawSVGTemplate::processTemplate() { // Base::Console().Message("DSVGT::processTemplate() - isRestoring: %d\n", isRestoring()); if (isRestoring()) { //until everything is fully restored, the embedded file is not available, so we //can't do anything return QString(); } QFile templateFile(Base::Tools::fromStdString(PageResult.getValue())); if (!templateFile.open(QIODevice::ReadOnly)) { Base::Console().Error("DrawSVGTemplate::processTemplate can't read embedded template %s!\n", PageResult.getValue()); return QString(); } QDomDocument templateDocument; if (!templateDocument.setContent(&templateFile)) { Base::Console().Error("DrawSVGTemplate::processTemplate - failed to parse file: %s\n", PageResult.getValue()); return QString(); } QXmlQuery query(QXmlQuery::XQuery10); QDomNodeModel model(query.namePool(), templateDocument); query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement()))); // XPath query to select all nodes whose parent // has "freecad:editable" attribute query.setQuery(QString::fromUtf8( "declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " "//text[@freecad:editable]/tspan")); QXmlResultItems queryResult; query.evaluateTo(&queryResult); std::map substitutions = EditableTexts.getValues(); while (!queryResult.next().isNull()) { QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); // Replace the editable text spans with new nodes holding actual values QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable")); std::map::iterator item = substitutions.find(std::string(editableName.toUtf8().constData())); if (item != substitutions.end()) { // Keep all spaces in the text node tspan.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve")); // Remove all child nodes and append text node with editable replacement as the only descendant while (!tspan.lastChild().isNull()) { tspan.removeChild(tspan.lastChild()); } tspan.appendChild(templateDocument.createTextNode(QString::fromUtf8(item->second.c_str()))); } } // Calculate the dimensions of the page and store for retrieval // Obtain the size of the SVG document by reading the document attributes QDomElement docElement = templateDocument.documentElement(); Base::Quantity quantity; // Obtain the width QString str = docElement.attribute(QString::fromLatin1("width")); quantity = Base::Quantity::parse(str); quantity.setUnit(Base::Unit::Length); Width.setValue(quantity.getValue()); str = docElement.attribute(QString::fromLatin1("height")); quantity = Base::Quantity::parse(str); quantity.setUnit(Base::Unit::Length); Height.setValue(quantity.getValue()); bool isLandscape = getWidth() / getHeight() >= 1.; Orientation.setValue(isLandscape ? 1 : 0); //all Qt holds on files should be released on exit #4085 return templateDocument.toString(); } double DrawSVGTemplate::getWidth() const { return Width.getValue(); } double DrawSVGTemplate::getHeight() const { return Height.getValue(); } void DrawSVGTemplate::replaceFileIncluded(std::string newTemplateFileName) { // Base::Console().Message("DSVGT::replaceFileIncluded(%s)\n", newTemplateFileName.c_str()); if (newTemplateFileName.empty()) { return; } Base::FileInfo tfi(newTemplateFileName); if (tfi.isReadable()) { PageResult.setValue(newTemplateFileName.c_str()); } else { throw Base::RuntimeError("Could not read the new template file"); } } std::map DrawSVGTemplate::getEditableTextsFromTemplate() { // Base::Console().Message("DSVGT::getEditableTextsFromTemplate()\n"); std::map editables; std::string templateFilename = Template.getValue(); if (templateFilename.empty()) { return editables; } Base::FileInfo tfi(templateFilename); if (!tfi.isReadable()) { // if there is a old absolute template file set use a redirect tfi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + tfi.fileName()); // try the redirect if (!tfi.isReadable()) { Base::Console().Log("DrawSVGTemplate::getEditableTextsFromTemplate() not able to open %s!\n", Template.getValue()); return editables; } } QFile templateFile(QString::fromUtf8(tfi.filePath().c_str())); if (!templateFile.open(QIODevice::ReadOnly)) { Base::Console().Log("DrawSVGTemplate::getEditableTextsFromTemplate() can't read template %s!\n", Template.getValue()); return editables; } QDomDocument templateDocument; if (!templateDocument.setContent(&templateFile)) { Base::Console().Message("DrawSVGTemplate::getEditableTextsFromTemplate() - failed to parse file: %s\n", Template.getValue()); return editables; } QXmlQuery query(QXmlQuery::XQuery10); QDomNodeModel model(query.namePool(), templateDocument, true); query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement()))); // XPath query to select all nodes whose parent // has "freecad:editable" attribute query.setQuery(QString::fromUtf8( "declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " "//text[@freecad:editable]/tspan")); QXmlResultItems queryResult; query.evaluateTo(&queryResult); while (!queryResult.next().isNull()) { QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable")); QString editableValue = tspan.firstChild().nodeValue(); editables[std::string(editableName.toUtf8().constData())] = std::string(editableValue.toUtf8().constData()); } return editables; } // Python Template feature --------------------------------------------------------- namespace App { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawSVGTemplatePython, TechDraw::DrawSVGTemplate) template<> const char* TechDraw::DrawSVGTemplatePython::getViewProviderName() const { return "TechDrawGui::ViewProviderPython"; } /// @endcond // explicit template instantiation template class TechDrawExport FeaturePythonT; }