diff --git a/src/backend/core/Project.cpp b/src/backend/core/Project.cpp index afeb37f0c..1f4f8e5d7 100644 --- a/src/backend/core/Project.cpp +++ b/src/backend/core/Project.cpp @@ -1,457 +1,457 @@ /*************************************************************************** File : Project.cpp Project : LabPlot Description : Represents a LabPlot project. -------------------------------------------------------------------- Copyright : (C) 2011-2014 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 Knut Franke (knut.franke@gmx.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datasources/LiveDataSource.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYEquationCurve.h" #include "backend/worksheet/plots/cartesian/XYDataReductionCurve.h" #include "backend/worksheet/plots/cartesian/XYDifferentiationCurve.h" #include "backend/worksheet/plots/cartesian/XYIntegrationCurve.h" #include "backend/worksheet/plots/cartesian/XYInterpolationCurve.h" #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h" #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "backend/worksheet/plots/cartesian/XYFourierFilterCurve.h" #include "backend/worksheet/plots/cartesian/XYFourierTransformCurve.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/datapicker/DatapickerCurve.h" #include #include #include #include #include #include #include #include #include #include /** * \class Project * \brief Represents a project. * \ingroup core * Project represents the root node of all objects created during the runtime of the program. * Manages also the undo stack. */ /** * \enum Project::MdiWindowVisibility * \brief MDI subwindow visibility setting */ /** * \var Project::folderOnly * \brief only show MDI windows corresponding to Parts in the current folder */ /** * \var Project::foldAndSubfolders * \brief show MDI windows corresponding to Parts in the current folder and its subfolders */ /** * \var Project::allMdiWindows * \brief show MDI windows for all Parts in the project simultaneously */ class Project::Private { public: Private() : mdiWindowVisibility(Project::folderOnly), scriptingEngine(0), version(LVERSION), author(QString(qgetenv("USER"))), modificationTime(QDateTime::currentDateTime()), changed(false) { } QUndoStack undo_stack; MdiWindowVisibility mdiWindowVisibility; AbstractScriptingEngine* scriptingEngine; QString fileName; QString version; QString author; QDateTime modificationTime; bool changed; }; Project::Project() : Folder(i18n("Project")), d(new Private()) { //load default values for name, comment and author from config KConfig config; KConfigGroup group = config.group("Project"); d->author = group.readEntry("Author", QString()); //we don't have direct access to the members name and comment //->temporaly disable the undo stack and call the setters setUndoAware(false); setIsLoading(true); setName(group.readEntry("Name", i18n("Project"))); setComment(group.readEntry("Comment", QString())); setUndoAware(true); setIsLoading(false); d->changed = false; // TODO: intelligent engine choosing // Q_ASSERT(ScriptingEngineManager::instance()->engineNames().size() > 0); // QString engine_name = ScriptingEngineManager::instance()->engineNames()[0]; // d->scriptingEngine = ScriptingEngineManager::instance()->engine(engine_name); connect(this, &Project::aspectDescriptionChanged,this, &Project::descriptionChanged); } Project::~Project() { //if the project is being closed and the live data sources still continue reading the data, //the dependend objects (columns, etc.), which are already deleted maybe here, are still being notified about the changes. //->stop reading the live data sources prior to deleting all objects. for (auto* lds : children()) lds->pauseReading(); //if the project is being closed, in Worksheet the scene items are being removed and the selection in the view can change. //don't react on these changes since this can lead crashes (worksheet object is already in the destructor). //->notify all worksheets about the project being closed. for (auto* w : children()) w->setIsClosing(); d->undo_stack.clear(); delete d; } QUndoStack* Project::undoStack() const { return &d->undo_stack; } QMenu* Project::createContextMenu() { QMenu* menu = new QMenu(); // no remove action from AbstractAspect in the project context menu emit requestProjectContextMenu(menu); return menu; } QMenu* Project::createFolderContextMenu(const Folder* folder) { QMenu* menu = const_cast(folder)->AbstractAspect::createContextMenu(); emit requestFolderContextMenu(folder, menu); return menu; } void Project::setMdiWindowVisibility(MdiWindowVisibility visibility) { d->mdiWindowVisibility = visibility; emit mdiWindowVisibilityChanged(); } Project::MdiWindowVisibility Project::mdiWindowVisibility() const { return d->mdiWindowVisibility; } AbstractScriptingEngine* Project::scriptingEngine() const { return d->scriptingEngine; } CLASS_D_ACCESSOR_IMPL(Project, QString, fileName, FileName, fileName) BASIC_D_ACCESSOR_IMPL(Project, QString, version, Version, version) CLASS_D_ACCESSOR_IMPL(Project, QString, author, Author, author) CLASS_D_ACCESSOR_IMPL(Project, QDateTime, modificationTime, ModificationTime, modificationTime) void Project::setChanged(const bool value) { if (isLoading()) return; if (value) emit changed(); d->changed = value; } bool Project ::hasChanged() const { return d->changed ; } void Project::descriptionChanged(const AbstractAspect* aspect) { if (isLoading()) return; if (this != aspect) return; d->changed = true; emit changed(); } void Project::navigateTo(const QString& path) { - requestNavigateTo(path); + emit requestNavigateTo(path); } bool Project::isLabPlotProject(const QString& fileName) { return fileName.endsWith(".lml", Qt::CaseInsensitive) || fileName.endsWith(".lml.gz", Qt::CaseInsensitive) || fileName.endsWith(".lml.bz2", Qt::CaseInsensitive) || fileName.endsWith(".lml.xz", Qt::CaseInsensitive); } QString Project::supportedExtensions() { static const QString extensions = "*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ"; return extensions; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /** * \brief Save as XML */ void Project::save(QXmlStreamWriter* writer) const { //set the version and the modification time to the current values d->version = LVERSION; d->modificationTime = QDateTime::currentDateTime(); writer->setAutoFormatting(true); writer->writeStartDocument(); writer->writeDTD(""); writer->writeStartElement("project"); writer->writeAttribute("version", version()); writer->writeAttribute("fileName", fileName()); writer->writeAttribute("modificationTime", modificationTime().toString("yyyy-dd-MM hh:mm:ss:zzz")); writer->writeAttribute("author", author()); writeBasicAttributes(writer); writeCommentElement(writer); //save all children for (auto* child : children(IncludeHidden)) { writer->writeStartElement("child_aspect"); child->save(writer); writer->writeEndElement(); } //save the state of the views (visible, maximized/minimized/geometry) //and the state of the project explorer (expanded items, currently selected item) emit requestSaveState(writer); writer->writeEndElement(); writer->writeEndDocument(); } bool Project::load(const QString& filename, bool preview) { QIODevice *file; // first try gzip compression, because projects can be gzipped and end with .lml if (filename.endsWith(QLatin1String(".lml"), Qt::CaseInsensitive)) file = new KCompressionDevice(filename,KFilterDev::compressionTypeForMimeType("application/x-gzip")); else // opens filename using file ending file = new KFilterDev(filename); if (file == 0) file = new QFile(filename); if (!file->open(QIODevice::ReadOnly)) { KMessageBox::error(0, i18n("Sorry. Could not open file for reading.")); return false; } char c; bool rc = file->getChar(&c); if (!rc) { KMessageBox::error(0, i18n("The project file is empty."), i18n("Error opening project")); file->close(); delete file; return false; } file->seek(0); //parse XML XmlStreamReader reader(file); setIsLoading(true); rc = this->load(&reader, preview); setIsLoading(false); if (rc == false) { RESET_CURSOR; QString msg_text = reader.errorString(); KMessageBox::error(0, msg_text, i18n("Error when opening the project")); return false; } if (reader.hasWarnings()) { QString msg = i18n("The following problems occurred when loading the project file:\n"); const QStringList& warnings = reader.warningStrings(); for (const auto& str : warnings) msg += str + '\n'; qWarning() << msg; //TODO: show warnings in a kind of "log window" but not in message box // KMessageBox::error(this, msg, i18n("Project loading partly failed")); } file->close(); delete file; return true; } /** * \brief Load from XML */ bool Project::load(XmlStreamReader* reader, bool preview) { while (!(reader->isStartDocument() || reader->atEnd())) reader->readNext(); if(!(reader->atEnd())) { if (!reader->skipToNextTag()) return false; if (reader->name() == "project") { QString version = reader->attributes().value("version").toString(); if(version.isEmpty()) reader->raiseWarning(i18n("Attribute 'version' is missing.")); else d->version = version; if (!readBasicAttributes(reader)) return false; if (!readProjectAttributes(reader)) return false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement()) break; if (reader->isStartElement()) { if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if(reader->name() == "child_aspect") { if (!readChildAspectElement(reader, preview)) return false; } else if(reader->name() == "state") { //load the state of the views (visible, maximized/minimized/geometry) //and the state of the project explorer (expanded items, currently selected item) emit requestLoadState(reader); } else { reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } } //wait until all columns are decoded from base64-encoded data QThreadPool::globalInstance()->waitForDone(); //everything is read now. //restore the pointer to the data sets (columns) in xy-curves etc. QVector curves = children(AbstractAspect::Recursive); QVector axes = children(AbstractAspect::Recursive); QVector dataPickerCurves = children(AbstractAspect::Recursive); if (!curves.isEmpty() || !axes.isEmpty()) { QVector columns = children(AbstractAspect::Recursive); //XY-curves for (auto* curve : curves) { if (!curve) continue; curve->suppressRetransform(true); XYEquationCurve* equationCurve = dynamic_cast(curve); XYAnalysisCurve* analysisCurve = dynamic_cast(curve); if (equationCurve) { //curves defined by a mathematical equations recalculate their own columns on load again. if (!preview) equationCurve->recalculate(); } else if (analysisCurve) { RESTORE_COLUMN_POINTER(analysisCurve, xDataColumn, XDataColumn); RESTORE_COLUMN_POINTER(analysisCurve, yDataColumn, YDataColumn); XYFitCurve* fitCurve = dynamic_cast(curve); if (fitCurve) { RESTORE_COLUMN_POINTER(fitCurve, xErrorColumn, XErrorColumn); RESTORE_COLUMN_POINTER(fitCurve, yErrorColumn, YErrorColumn); } } else { RESTORE_COLUMN_POINTER(curve, xColumn, XColumn); RESTORE_COLUMN_POINTER(curve, yColumn, YColumn); RESTORE_COLUMN_POINTER(curve, valuesColumn, ValuesColumn); RESTORE_COLUMN_POINTER(curve, xErrorPlusColumn, XErrorPlusColumn); RESTORE_COLUMN_POINTER(curve, xErrorMinusColumn, XErrorMinusColumn); RESTORE_COLUMN_POINTER(curve, yErrorPlusColumn, YErrorPlusColumn); RESTORE_COLUMN_POINTER(curve, yErrorMinusColumn, YErrorMinusColumn); } if (dynamic_cast(curve)) RESTORE_POINTER(dynamic_cast(curve), dataSourceCurve, DataSourceCurve, XYCurve, curves); curve->suppressRetransform(false); } //Axes for (auto* axis : axes) { if (!axis) continue; RESTORE_COLUMN_POINTER(axis, majorTicksColumn, MajorTicksColumn); RESTORE_COLUMN_POINTER(axis, minorTicksColumn, MinorTicksColumn); } for (auto* dataPickerCurve : dataPickerCurves) { if (!dataPickerCurve) continue; RESTORE_COLUMN_POINTER(dataPickerCurve, posXColumn, PosXColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, posYColumn, PosYColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaXColumn, PlusDeltaXColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaXColumn, MinusDeltaXColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaYColumn, PlusDeltaYColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaYColumn, MinusDeltaYColumn); } } } else // no project element reader->raiseError(i18n("no project element found")); } else // no start document reader->raiseError(i18n("no valid XML document found")); if (!preview) { for (auto* plot : children(AbstractAspect::Recursive)) { plot->setIsLoading(false); plot->retransform(); } } emit loaded(); return !reader->hasError(); } bool Project::readProjectAttributes(XmlStreamReader* reader) { QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value(reader->namespaceUri().toString(), "fileName").toString(); if(str.isEmpty()) { reader->raiseError(i18n("Project file name missing.")); return false; } d->fileName = str; str = attribs.value(reader->namespaceUri().toString(), "modificationTime").toString(); QDateTime modificationTime = QDateTime::fromString(str, "yyyy-dd-MM hh:mm:ss:zzz"); if(str.isEmpty() || !modificationTime.isValid()) { reader->raiseWarning(i18n("Invalid project modification time. Using current time.")); d->modificationTime = QDateTime::currentDateTime(); } else d->modificationTime = modificationTime; str = attribs.value(reader->namespaceUri().toString(), "author").toString(); d->author = str; return true; } diff --git a/src/backend/datasources/projects/OriginProjectParser.cpp b/src/backend/datasources/projects/OriginProjectParser.cpp index 26248e3c3..5231cdfa0 100644 --- a/src/backend/datasources/projects/OriginProjectParser.cpp +++ b/src/backend/datasources/projects/OriginProjectParser.cpp @@ -1,1947 +1,1946 @@ /*************************************************************************** File : OriginProjectParser.h Project : LabPlot Description : parser for Origin projects -------------------------------------------------------------------- Copyright : (C) 2017-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/projects/OriginProjectParser.h" #include "backend/core/column/Column.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/Project.h" #include "backend/core/Workbook.h" #include "backend/matrix/Matrix.h" #include "backend/note/Note.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYEquationCurve.h" #include "backend/worksheet/TextLabel.h" #include #include #include #include #include /*! \class OriginProjectParser \brief parser for Origin projects. \ingroup datasources */ OriginProjectParser::OriginProjectParser() : ProjectParser(), m_originFile(nullptr), m_importUnusedObjects(false) { m_topLevelClasses << "Folder" << "Workbook" << "Spreadsheet" << "Matrix" << "Worksheet" << "Note"; } bool OriginProjectParser::isOriginProject(const QString& fileName) { //TODO add opju later when liborigin supports it return fileName.endsWith(".opj", Qt::CaseInsensitive); } void OriginProjectParser::setImportUnusedObjects(bool importUnusedObjects) { m_importUnusedObjects = importUnusedObjects; } bool OriginProjectParser::hasUnusedObjects() { if (m_originFile) delete m_originFile; m_originFile = new OriginFile((const char*)m_projectFileName.toLocal8Bit()); if (!m_originFile->parse()) return false; for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { const Origin::SpreadSheet& spread = m_originFile->spread(i); if (spread.objectID < 0) return true; } for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { const Origin::Excel& excel = m_originFile->excel(i); if (excel.objectID < 0) return true; } for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { const Origin::Matrix& originMatrix = m_originFile->matrix(i); if (originMatrix.objectID < 0) return true; } return false; } QString OriginProjectParser::supportedExtensions() { //TODO add opju later when liborigin supports it static const QString extensions = "*.opj *.OPJ"; return extensions; } unsigned int OriginProjectParser::findSpreadByName(QString name) { for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { const Origin::SpreadSheet& spread = m_originFile->spread(i); if (spread.name == name.toStdString()) { m_spreadNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findMatrixByName(QString name) { for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { const Origin::Matrix& originMatrix = m_originFile->matrix(i); if (originMatrix.name == name.toStdString()) { m_matrixNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findExcelByName(QString name) { for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { const Origin::Excel& excel = m_originFile->excel(i); if (excel.name == name.toStdString()) { m_excelNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findGraphByName(QString name) { for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { const Origin::Graph& graph = m_originFile->graph(i); if (graph.name == name.toStdString()) { m_graphNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findNoteByName(QString name) { for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { const Origin::Note& originNote = m_originFile->note(i); if (originNote.name == name.toStdString()) { m_noteNameList << name; return i; } } return 0; } //############################################################################## //############## Deserialization from Origin's project tree #################### //############################################################################## bool OriginProjectParser::load(Project* project, bool preview) { DEBUG("OriginProjectParser::load()"); //read and parse the m_originFile-file if (m_originFile) delete m_originFile; m_originFile = new OriginFile((const char*)m_projectFileName.toLocal8Bit()); if (!m_originFile->parse()) return false; //Origin project tree and the iterator pointing to the root node const tree* projectTree = m_originFile->project(); tree::iterator projectIt = projectTree->begin(projectTree->begin()); m_spreadNameList.clear(); m_excelNameList.clear(); m_matrixNameList.clear(); m_graphNameList.clear(); m_noteNameList.clear(); //convert the project tree from liborigin's representation to LabPlot's project object if (projectIt.node) { // only opj files from version >= 6.0 do have project tree QString name(QString::fromLatin1(projectIt->name.c_str())); project->setName(name); project->setCreationTime(creationTime(projectIt)); loadFolder(project, projectIt, preview); } else { // for lower versions put all windows on rootfolder int pos = m_projectFileName.lastIndexOf(QDir::separator()) + 1; project->setName((const char*)m_projectFileName.mid(pos).toLocal8Bit()); } // imports all loose windows (like prior version 6 which has no project tree) handleLooseWindows(project, preview); //restore column pointers: //1. extend the pathes to contain the parent structures first //2. restore the pointers from the pathes const QVector columns = project->children(AbstractAspect::Recursive); const QVector spreadsheets = project->children(AbstractAspect::Recursive); for (auto* curve : project->children(AbstractAspect::Recursive)) { curve->suppressRetransform(true); //x-column QString spreadsheetName = curve->xColumnPath().left(curve->xColumnPath().indexOf(QLatin1Char('/'))); for (const auto* spreadsheet : spreadsheets) { if (spreadsheet->name() == spreadsheetName) { const QString& newPath = spreadsheet->parentAspect()->path() + "/" + curve->xColumnPath(); curve->setXColumnPath(newPath); for (auto* column : columns) { if (!column) continue; if (column->path() == newPath) { curve->setXColumn(column); break; } } break; } } //x-column spreadsheetName = curve->yColumnPath().left(curve->yColumnPath().indexOf(QLatin1Char('/'))); for (const auto* spreadsheet : spreadsheets) { if (spreadsheet->name() == spreadsheetName) { const QString& newPath = spreadsheet->parentAspect()->path() + "/" + curve->yColumnPath(); curve->setYColumnPath(newPath); for (auto* column : columns) { if (!column) continue; if (column->path() == newPath) { curve->setYColumn(column); break; } } break; } } //TODO: error columns curve->suppressRetransform(false); } if (!preview) { for (auto* plot : project->children(AbstractAspect::Recursive)) { plot->setIsLoading(false); plot->retransform(); } } emit project->loaded(); return true; } bool OriginProjectParser::loadFolder(Folder* folder, const tree::iterator& baseIt, bool preview) { DEBUG("OriginProjectParser::loadFolder()\n--------------------------------"); const tree* projectTree = m_originFile->project(); //load folder's children: logic for reading the selected objects only is similar to Folder::readChildAspectElement for (tree::sibling_iterator it = projectTree->begin(baseIt); it != projectTree->end(baseIt); ++it) { QString name(QString::fromLatin1(it->name.c_str())); //name of the current child DEBUG(" * folder item name = " << name.toStdString()); //check whether we need to skip the loading of the current child if (!folder->pathesToLoad().isEmpty()) { //child's path is not available yet (child not added yet) -> construct the path manually const QString childPath = folder->path() + '/' + name; //skip the current child aspect it is not in the list of aspects to be loaded if (folder->pathesToLoad().indexOf(childPath) == -1) continue; } //load top-level children AbstractAspect* aspect = nullptr; switch (it->type) { case Origin::ProjectNode::Folder: { DEBUG(" top level folder"); Folder* f = new Folder(name); if (!folder->pathesToLoad().isEmpty()) { //a child folder to be read -> provide the list of aspects to be loaded to the child folder, too. //since the child folder and all its children are not added yet (path() returns empty string), //we need to remove the path of the current child folder from the full pathes provided in pathesToLoad. //E.g. we want to import the path "Project/Folder/Spreadsheet" in the following project // Project // \Spreadsheet // \Folder // \Spreadsheet // //Here, we remove the part "Project/Folder/" and proceed for this child folder with "Spreadsheet" only. //With this the logic above where it is determined whether to import the child aspect or not works out. //manually construct the path of the child folder to be read const QString& curFolderPath = folder->path() + '/' + name; //remove the path of the current child folder QStringList pathesToLoadNew; - for (auto path : folder->pathesToLoad()) { + for (const auto& path : folder->pathesToLoad()) { if (path.startsWith(curFolderPath)) pathesToLoadNew << path.right(path.length() - curFolderPath.length()); } f->setPathesToLoad(pathesToLoadNew); } loadFolder(f, it, preview); aspect = f; break; } case Origin::ProjectNode::SpreadSheet: { DEBUG(" top level spreadsheet"); Spreadsheet* spreadsheet = new Spreadsheet(0, name); loadSpreadsheet(spreadsheet, preview, name); aspect = spreadsheet; break; } case Origin::ProjectNode::Graph: { DEBUG(" top level graph"); Worksheet* worksheet = new Worksheet(0, name); worksheet->setIsLoading(true); loadWorksheet(worksheet, preview); aspect = worksheet; break; } case Origin::ProjectNode::Matrix: { DEBUG(" top level matrix"); const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(name)); DEBUG(" matrix name = " << originMatrix.name); DEBUG(" number of sheets = " << originMatrix.sheets.size()); if (originMatrix.sheets.size() == 1) { // single sheet -> load into a matrix Matrix* matrix = new Matrix(0, name); loadMatrix(matrix, preview); aspect = matrix; } else { // multiple sheets -> load into a workbook Workbook* workbook = new Workbook(0, name); loadMatrixWorkbook(workbook, preview); aspect = workbook; } break; } case Origin::ProjectNode::Excel: { DEBUG(" top level excel"); Workbook* workbook = new Workbook(0, name); loadWorkbook(workbook, preview); aspect = workbook; break; } case Origin::ProjectNode::Note: { DEBUG("top level note"); Note* note = new Note(name); loadNote(note, preview); aspect = note; break; } case Origin::ProjectNode::Graph3D: default: //TODO: add UnsupportedAspect break; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(creationTime(it)); aspect->setIsLoading(false); } } // ResultsLog QString resultsLog = QString::fromLatin1(m_originFile->resultsLogString().c_str()); if (resultsLog.length() > 0) { DEBUG("Results log:\t\tyes"); Note* note = new Note("ResultsLog"); if (preview) folder->addChildFast(note); else { //only import the log if it is in the list of aspects to be loaded const QString childPath = folder->path() + '/' + note->name(); if (folder->pathesToLoad().indexOf(childPath) != -1) { note->setText(resultsLog); folder->addChildFast(note); } } } else DEBUG("Results log:\t\tno"); return folder; } void OriginProjectParser::handleLooseWindows(Folder* folder, bool preview) { DEBUG("OriginProjectParser::handleLooseWindows()"); QDEBUG("pathes to load:" << folder->pathesToLoad()); m_spreadNameList.removeDuplicates(); m_excelNameList.removeDuplicates(); m_matrixNameList.removeDuplicates(); m_graphNameList.removeDuplicates(); m_noteNameList.removeDuplicates(); QDEBUG(" spreads =" << m_spreadNameList); QDEBUG(" excels =" << m_excelNameList); QDEBUG(" matrices =" << m_matrixNameList); QDEBUG(" graphs =" << m_graphNameList); QDEBUG(" notes =" << m_noteNameList); DEBUG("Number of spreads loaded:\t" << m_spreadNameList.size() << ", in file: " << m_originFile->spreadCount()); DEBUG("Number of excels loaded:\t" << m_excelNameList.size() << ", in file: " << m_originFile->excelCount()); DEBUG("Number of matrices loaded:\t" << m_matrixNameList.size() << ", in file: " << m_originFile->matrixCount()); DEBUG("Number of graphs loaded:\t" << m_graphNameList.size() << ", in file: " << m_originFile->graphCount()); DEBUG("Number of notes loaded:\t\t" << m_noteNameList.size() << ", in file: " << m_originFile->noteCount()); // loop over all spreads to find loose ones for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::SpreadSheet& spread = m_originFile->spread(i); QString name = QString::fromStdString(spread.name); DEBUG(" spread.objectId = " << spread.objectID); // skip unused spreads if selected if (spread.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose spread: " << name.toStdString()); continue; } const QString childPath = folder->path() + '/' + name; // we could also use spread.loose if (!m_spreadNameList.contains(name) && (preview || (!preview && folder->pathesToLoad().indexOf(childPath) != -1))) { DEBUG(" Adding loose spread: " << name.toStdString()); Spreadsheet* spreadsheet = new Spreadsheet(0, name); loadSpreadsheet(spreadsheet, preview, name); aspect = spreadsheet; } if (aspect) { folder->addChildFast(aspect); DEBUG(" creation time as reported by liborigin: " << spread.creationDate); aspect->setCreationTime(QDateTime::fromTime_t(spread.creationDate)); } } // loop over all excels to find loose ones for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Excel& excel = m_originFile->excel(i); QString name = QString::fromStdString(excel.name); DEBUG(" excel.objectId = " << excel.objectID); // skip unused data sets if selected if (excel.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose excel: " << name.toStdString()); continue; } const QString childPath = folder->path() + '/' + name; // we could also use excel.loose if (!m_excelNameList.contains(name) && (preview || (!preview && folder->pathesToLoad().indexOf(childPath) != -1))) { DEBUG(" Adding loose excel: " << name.toStdString()); DEBUG(" containing number of sheets = " << excel.sheets.size()); Workbook* workbook = new Workbook(0, name); loadWorkbook(workbook, preview); aspect = workbook; } if (aspect) { folder->addChildFast(aspect); DEBUG(" creation time as reported by liborigin: " << excel.creationDate); aspect->setCreationTime(QDateTime::fromTime_t(excel.creationDate)); } } // loop over all matrices to find loose ones for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Matrix& originMatrix = m_originFile->matrix(i); QString name = QString::fromStdString(originMatrix.name); DEBUG(" originMatrix.objectId = " << originMatrix.objectID); // skip unused data sets if selected if (originMatrix.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose matrix: " << name.toStdString()); continue; } const QString childPath = folder->path() + '/' + name; if (!m_matrixNameList.contains(name) && (preview || (!preview && folder->pathesToLoad().indexOf(childPath) != -1))) { DEBUG(" Adding loose matrix: " << name.toStdString()); DEBUG(" containing number of sheets = " << originMatrix.sheets.size()); if (originMatrix.sheets.size() == 1) { // single sheet -> load into a matrix Matrix* matrix = new Matrix(0, name); loadMatrix(matrix, preview); aspect = matrix; } else { // multiple sheets -> load into a workbook Workbook* workbook = new Workbook(0, name); loadMatrixWorkbook(workbook, preview); aspect = workbook; } } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(originMatrix.creationDate)); } } // handle loose graphs (is this even possible?) for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Graph& graph = m_originFile->graph(i); QString name = QString::fromStdString(graph.name); DEBUG(" graph.objectId = " << graph.objectID); // skip unused graph if selected if (graph.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose graph: " << name.toStdString()); continue; } const QString childPath = folder->path() + '/' + name; if (!m_graphNameList.contains(name) && (preview || (!preview && folder->pathesToLoad().indexOf(childPath) != -1))) { DEBUG(" Adding loose graph: " << name.toStdString()); Worksheet* worksheet = new Worksheet(0, name); loadWorksheet(worksheet, preview); aspect = worksheet; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(graph.creationDate)); } } // handle loose notes (is this even possible?) for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Note& originNote = m_originFile->note(i); QString name = QString::fromStdString(originNote.name); DEBUG(" originNote.objectId = " << originNote.objectID); // skip unused notes if selected if (originNote.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose note: " << name.toStdString()); continue; } const QString childPath = folder->path() + '/' + name; if (!m_noteNameList.contains(name) && (preview || (!preview && folder->pathesToLoad().indexOf(childPath) != -1))) { DEBUG(" Adding loose note: " << name.toStdString()); Note* note = new Note(name); loadNote(note, preview); aspect = note; } if (aspect) { folder->addChildFast(aspect); - QDateTime dt = QDateTime::fromTime_t(originNote.creationDate); aspect->setCreationTime(QDateTime::fromTime_t(originNote.creationDate)); } } } bool OriginProjectParser::loadWorkbook(Workbook* workbook, bool preview) { DEBUG("loadWorkbook()"); //load workbook sheets const Origin::Excel& excel = m_originFile->excel(findExcelByName(workbook->name())); DEBUG(" excel name = " << excel.name); DEBUG(" number of sheets = " << excel.sheets.size()); for (unsigned int s = 0; s < excel.sheets.size(); ++s) { Spreadsheet* spreadsheet = new Spreadsheet(0, QString::fromLatin1(excel.sheets[s].name.c_str())); loadSpreadsheet(spreadsheet, preview, workbook->name(), s); workbook->addChildFast(spreadsheet); } return true; } // load spreadsheet from spread (sheetIndex == -1) or from excel (only sheet sheetIndex) bool OriginProjectParser::loadSpreadsheet(Spreadsheet* spreadsheet, bool preview, const QString& name, int sheetIndex) { DEBUG("loadSpreadsheet() sheetIndex = " << sheetIndex); //load spreadsheet data Origin::SpreadSheet spread; Origin::Excel excel; if (sheetIndex == -1) // spread spread = m_originFile->spread(findSpreadByName(name)); else { excel = m_originFile->excel(findExcelByName(name)); spread = excel.sheets[sheetIndex]; } const size_t cols = spread.columns.size(); int rows = 0; for (size_t j = 0; j < cols; ++j) rows = std::max((int)spread.columns[j].data.size(), rows); // alternative: int rows = excel.maxRows; DEBUG("loadSpreadsheet() cols/maxRows = " << cols << "/" << rows); if (rows < 0 || (int)cols < 0) return false; //TODO QLocale locale = mw->locale(); spreadsheet->setRowCount(rows); spreadsheet->setColumnCount((int)cols); if (sheetIndex == -1) spreadsheet->setComment(QString::fromLatin1(spread.label.c_str())); else spreadsheet->setComment(QString::fromLatin1(excel.label.c_str())); //in Origin column width is measured in characters, we need to convert to pixels //TODO: determine the font used in Origin in order to get the same column width as in Origin QFont font; QFontMetrics fm(font); const int scaling_factor = fm.maxWidth(); for (size_t j = 0; j < cols; ++j) { Origin::SpreadColumn column = spread.columns[j]; Column* col = spreadsheet->column((int)j); QString name(column.name.c_str()); col->setName(name.replace(QRegExp(".*_"),"")); if (preview) continue; //TODO: we don't support any formulas for cells yet. // if (column.command.size() > 0) // col->setFormula(Interval(0, rows), QString(column.command.c_str())); col->setComment(QString::fromLatin1(column.comment.c_str())); col->setWidth((int)column.width * scaling_factor); //plot designation switch (column.type) { case Origin::SpreadColumn::X: col->setPlotDesignation(AbstractColumn::X); break; case Origin::SpreadColumn::Y: col->setPlotDesignation(AbstractColumn::Y); break; case Origin::SpreadColumn::Z: col->setPlotDesignation(AbstractColumn::Z); break; case Origin::SpreadColumn::XErr: col->setPlotDesignation(AbstractColumn::XError); break; case Origin::SpreadColumn::YErr: col->setPlotDesignation(AbstractColumn::YError); break; case Origin::SpreadColumn::Label: case Origin::SpreadColumn::NONE: default: col->setPlotDesignation(AbstractColumn::NoDesignation); } QString format; switch(column.valueType) { case Origin::Numeric: { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const double value = column.data.at(i).as_double(); if (value != _ONAN) col->setValueAt(i, value); } loadColumnNumericFormat(column, col); break; } case Origin::TextNumeric: { //A TextNumeric column can contain numeric and string values, there is no equivalent column mode in LabPlot. // -> Set the column mode as 'Numeric' or 'Text' depending on the type of first non-empty element in column. for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const Origin::variant value(column.data.at(i)); if (value.type() == Origin::Variant::V_DOUBLE) { if (value.as_double() != _ONAN) break; } else { if (value.as_string() != NULL) { col->setColumnMode(AbstractColumn::Text); break; } } } if (col->columnMode() == AbstractColumn::Numeric) { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const double value = column.data.at(i).as_double(); if (column.data.at(i).type() == Origin::Variant::V_DOUBLE && value != _ONAN) col->setValueAt(i, value); } loadColumnNumericFormat(column, col); } else { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const Origin::variant value(column.data.at(i)); if (value.type() == Origin::Variant::V_STRING) { if (value.as_string() != NULL) col->setTextAt(i, value.as_string()); } else { if (value.as_double() != _ONAN) col->setTextAt(i, QString::number(value.as_double())); } } } break; } case Origin::Text: col->setColumnMode(AbstractColumn::Text); for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setTextAt(i, column.data[i].as_string()); break; case Origin::Time: { switch (column.valueTypeSpecification + 128) { case Origin::TIME_HH_MM: format="hh:mm"; break; case Origin::TIME_HH: format="hh"; break; case Origin::TIME_HH_MM_SS: format="hh:mm:ss"; break; case Origin::TIME_HH_MM_SS_ZZ: format="hh:mm:ss.zzz"; break; case Origin::TIME_HH_AP: format="hh ap"; break; case Origin::TIME_HH_MM_AP: format="hh:mm ap"; break; case Origin::TIME_MM_SS: format="mm:ss"; break; case Origin::TIME_MM_SS_ZZ: format="mm:ss.zzz"; break; case Origin::TIME_HHMM: format="hhmm"; break; case Origin::TIME_HHMMSS: format="hhmmss"; break; case Origin::TIME_HH_MM_SS_ZZZ: format="hh:mm:ss.zzz"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Date: { switch (column.valueTypeSpecification) { case Origin::DATE_DD_MM_YYYY: format="dd/MM/yyyy"; break; case Origin::DATE_DD_MM_YYYY_HH_MM: format="dd/MM/yyyy HH:mm"; break; case Origin::DATE_DD_MM_YYYY_HH_MM_SS: format="dd/MM/yyyy HH:mm:ss"; break; case Origin::DATE_DDMMYYYY: case Origin::DATE_DDMMYYYY_HH_MM: case Origin::DATE_DDMMYYYY_HH_MM_SS: format="dd.MM.yyyy"; break; case Origin::DATE_MMM_D: format="MMM d"; break; case Origin::DATE_M_D: format="M/d"; break; case Origin::DATE_D: format="d"; break; case Origin::DATE_DDD: case Origin::DATE_DAY_LETTER: format="ddd"; break; case Origin::DATE_YYYY: format="yyyy"; break; case Origin::DATE_YY: format="yy"; break; case Origin::DATE_YYMMDD: case Origin::DATE_YYMMDD_HH_MM: case Origin::DATE_YYMMDD_HH_MM_SS: case Origin::DATE_YYMMDD_HHMM: case Origin::DATE_YYMMDD_HHMMSS: format="yyMMdd"; break; case Origin::DATE_MMM: case Origin::DATE_MONTH_LETTER: format="MMM"; break; case Origin::DATE_M_D_YYYY: format="M-d-yyyy"; break; default: format="dd.MM.yyyy"; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Month: { switch (column.valueTypeSpecification) { case Origin::MONTH_MMM: format = "MMM"; break; case Origin::MONTH_MMMM: format = "MMMM"; break; case Origin::MONTH_LETTER: format = "M"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::Month); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Day: { switch (column.valueTypeSpecification) { case Origin::DAY_DDD: format = "ddd"; break; case Origin::DAY_DDDD: format = "dddd"; break; case Origin::DAY_LETTER: format = "d"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::Day); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::ColumnHeading: case Origin::TickIndexedDataset: case Origin::Categorical: break; } } //TODO: "hidden" not supporrted yet // if (spread.hidden || spread.loose) // mw->hideWindow(spreadsheet); return true; } void OriginProjectParser::loadColumnNumericFormat(const Origin::SpreadColumn& originColumn, Column* column) const { if (originColumn.numericDisplayType != 0) { int fi = 0; switch (originColumn.valueTypeSpecification) { case Origin::Decimal: fi = 1; break; case Origin::Scientific: fi = 2; break; case Origin::Engineering: case Origin::DecimalWithMarks: break; } Double2StringFilter* filter = static_cast(column->outputFilter()); filter->setNumericFormat(fi); filter->setNumDigits(originColumn.decimalPlaces); } } bool OriginProjectParser::loadMatrixWorkbook(Workbook* workbook, bool preview) { DEBUG("loadMatrixWorkbook()"); //load matrix workbook sheets const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(workbook->name())); for (size_t s = 0; s < originMatrix.sheets.size(); ++s) { Matrix* matrix = new Matrix(0, QString::fromLatin1(originMatrix.sheets[s].name.c_str())); loadMatrix(matrix, preview, s, workbook->name()); workbook->addChildFast(matrix); } return true; } bool OriginProjectParser::loadMatrix(Matrix* matrix, bool preview, size_t sheetIndex, const QString& mwbName) { DEBUG("loadMatrix()"); //import matrix data const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(mwbName)); if (preview) return true; //in Origin column width is measured in characters, we need to convert to pixels //TODO: determine the font used in Origin in order to get the same column width as in Origin QFont font; QFontMetrics fm(font); const int scaling_factor = fm.maxWidth(); const Origin::MatrixSheet& layer = originMatrix.sheets[sheetIndex]; const int colCount = layer.columnCount; const int rowCount = layer.rowCount; matrix->setRowCount(rowCount); matrix->setColumnCount(colCount); matrix->setFormula(layer.command.c_str()); //TODO: how to handle different widths for different columns? for (int j = 0; j < colCount; j++) matrix->setColumnWidth(j, layer.width * scaling_factor); //TODO: check colum major vs. row major to improve the performance here for (int i = 0; i < rowCount; i++) { for (int j = 0; j < colCount; j++) matrix->setCell(i, j, layer.data[j + i*colCount]); } char format = 'g'; //TODO: prec not support by Matrix //int prec = 6; switch (layer.valueTypeSpecification) { case 0: //Decimal 1000 format='f'; // prec = layer.decimalPlaces; break; case 1: //Scientific format='e'; // prec = layer.decimalPlaces; break; case 2: //Engineering case 3: //Decimal 1,000 format='g'; // prec = layer.significantDigits; break; } matrix->setNumericFormat(format); return true; } bool OriginProjectParser::loadWorksheet(Worksheet* worksheet, bool preview) { DEBUG("OriginProjectParser::loadWorksheet()"); //load worksheet data const Origin::Graph& graph = m_originFile->graph(findGraphByName(worksheet->name())); DEBUG(" graph name = " << graph.name); worksheet->setComment(graph.label.c_str()); //TODO: width, height, view mode (print view, page view, window view, draft view) //Origin allows to freely resize the window and ajusts the size of the plot (layer) automatically //by keeping a certain width-to-height ratio. It's not clear what the actual size of the plot/layer is and how to handle this. //For now we simply create a new wokrsheet here with it's default size and make it using the whole view size. //Later we can decide to use one of the following properties: // 1) Window.frameRect gives Rect-corner coordinates (in pixels) of the Window object // 2) GraphLayer.clientRect gives Rect-corner coordinates (pixels) of the Layer inside the (printer?) page. // 3) Graph.width, Graph.height give the (printer?) page size in pixels. worksheet->setUseViewSize(true); // worksheet background color const Origin::ColorGradientDirection bckgColorGradient = graph.windowBackgroundColorGradient; const Origin::Color bckgBaseColor = graph.windowBackgroundColorBase; const Origin::Color bckgEndColor = graph.windowBackgroundColorEnd; worksheet->setBackgroundColorStyle(backgroundColorStyle(bckgColorGradient)); switch (bckgColorGradient) { case Origin::ColorGradientDirection::NoGradient: case Origin::ColorGradientDirection::TopLeft: case Origin::ColorGradientDirection::Left: case Origin::ColorGradientDirection::BottomLeft: case Origin::ColorGradientDirection::Top: worksheet->setBackgroundFirstColor(color(bckgEndColor)); worksheet->setBackgroundSecondColor(color(bckgBaseColor)); break; case Origin::ColorGradientDirection::Center: break; case Origin::ColorGradientDirection::Bottom: case Origin::ColorGradientDirection::TopRight: case Origin::ColorGradientDirection::Right: case Origin::ColorGradientDirection::BottomRight: worksheet->setBackgroundFirstColor(color(bckgBaseColor)); worksheet->setBackgroundSecondColor(color(bckgEndColor)); } //TODO: do we need changes on the worksheet layout? //add plots int index = 1; for (const auto& layer: graph.layers) { if (!layer.is3D()) { CartesianPlot* plot = new CartesianPlot(i18n("Plot") + QString::number(index)); plot->setIsLoading(true); //TODO: width, height //background color const Origin::Color& regColor = layer.backgroundColor; if (regColor.type == Origin::Color::None) plot->plotArea()->setBackgroundOpacity(0); else plot->plotArea()->setBackgroundFirstColor(color(regColor)); //border if (layer.borderType == Origin::BorderType::None) plot->plotArea()->setBorderPen(QPen(Qt::NoPen)); else plot->plotArea()->setBorderPen(QPen(Qt::SolidLine)); //ranges plot->setAutoScaleX(false); plot->setAutoScaleY(false); const Origin::GraphAxis& originXAxis = layer.xAxis; const Origin::GraphAxis& originYAxis = layer.yAxis; plot->setXMin(originXAxis.min); plot->setXMax(originXAxis.max); plot->setYMin(originYAxis.min); plot->setYMax(originYAxis.max); //scales switch(originXAxis.scale) { case Origin::GraphAxis::Linear: plot->setXScale(CartesianPlot::ScaleLinear); break; case Origin::GraphAxis::Log10: plot->setXScale(CartesianPlot::ScaleLog10); break; case Origin::GraphAxis::Ln: plot->setXScale(CartesianPlot::ScaleLn); break; case Origin::GraphAxis::Log2: plot->setXScale(CartesianPlot::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: plot->setXScale(CartesianPlot::ScaleLinear); break; } switch(originYAxis.scale) { case Origin::GraphAxis::Linear: plot->setYScale(CartesianPlot::ScaleLinear); break; case Origin::GraphAxis::Log10: plot->setYScale(CartesianPlot::ScaleLog10); break; case Origin::GraphAxis::Ln: plot->setYScale(CartesianPlot::ScaleLn); break; case Origin::GraphAxis::Log2: plot->setYScale(CartesianPlot::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: plot->setYScale(CartesianPlot::ScaleLinear); break; } //axes //x bottom if (layer.curves.size()){ Origin::GraphCurve originCurve = layer.curves[0]; if (!originXAxis.formatAxis[0].hidden) { Axis* axis = new Axis("x", Axis::AxisHorizontal); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisBottom); plot->addChild(axis); loadAxis(originXAxis, axis, 0, QString::fromLatin1(originCurve.xColumnName.c_str())); axis->setSuppressRetransform(false); } //x top if (!originXAxis.formatAxis[1].hidden) { Axis* axis = new Axis("x top", Axis::AxisHorizontal); axis->setPosition(Axis::AxisTop); axis->setSuppressRetransform(true); plot->addChild(axis); loadAxis(originXAxis, axis, 1, QString::fromLatin1(originCurve.xColumnName.c_str())); axis->setSuppressRetransform(false); } //y left if (!originYAxis.formatAxis[0].hidden) { Axis* axis = new Axis("y", Axis::AxisVertical); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisLeft); plot->addChild(axis); loadAxis(originYAxis, axis, 0, QString::fromLatin1(originCurve.yColumnName.c_str())); axis->setSuppressRetransform(false); } //y right if (!originYAxis.formatAxis[1].hidden) { Axis* axis = new Axis("y right", Axis::AxisVertical); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisRight); plot->addChild(axis); loadAxis(originYAxis, axis, 1, QString::fromLatin1(originCurve.yColumnName.c_str())); axis->setSuppressRetransform(false); } } else { //TODO: ? } //range breaks //TODO //add legend if available const Origin::TextBox& originLegend = layer.legend; const QString& legendText = QString::fromLatin1(originLegend.text.c_str()); DEBUG(" legend text = " << legendText.toStdString()); if (!originLegend.text.empty()) { CartesianPlotLegend* legend = new CartesianPlotLegend(plot, i18n("legend")); //Origin's legend uses "\l(...)" or "\L(...)" string to format the legend symbol // and "%(...) to format the legend text for each curve //s. a. https://www.originlab.com/doc/Origin-Help/Legend-ManualControl //the text before these formatting tags, if available, is interpreted as the legend title QString legendTitle; //search for the first occurance of the legend symbol substring int index = legendText.indexOf(QLatin1String("\\l("), 0, Qt::CaseInsensitive); if (index != -1) legendTitle = legendText.left(index); else { //check legend text index = legendText.indexOf(QLatin1String("%(")); if (index != -1) legendTitle = legendText.left(index); } legendTitle = legendTitle.trimmed(); if (!legendTitle.isEmpty()) legendTitle = parseOriginText(legendTitle); DEBUG(" legend title = " << legendTitle.toStdString()); legend->title()->setText(legendTitle); //TODO: text color //const Origin::Color& originColor = originLegend.color; //position //TODO: for the first release with OPJ support we put the leget to the bottom left corner, //in the next release we'll evaluate originLegend.clientRect giving the position inside of the whole page in Origin. //In Origin the legend can be placed outside of the plot which is not possible in LabPlot. //To achieve this we'll need to increase padding area in the plot and to place the legend outside of the plot area. CartesianPlotLegend::PositionWrapper position; position.horizontalPosition = CartesianPlotLegend::hPositionRight; position.verticalPosition = CartesianPlotLegend::vPositionBottom; legend->setPosition(position); //TODO: rotation //legend->setRotationAngle(originLegend.rotation); //border line if (originLegend.borderType == Origin::BorderType::None) legend->setBorderPen(QPen(Qt::NoPen)); else legend->setBorderPen(QPen(Qt::SolidLine)); //background color, determine it with the help of the border type if (originLegend.borderType == Origin::BorderType::DarkMarble) legend->setBackgroundFirstColor(Qt::darkGray); else if (originLegend.borderType == Origin::BorderType::BlackOut) legend->setBackgroundFirstColor(Qt::black); else legend->setBackgroundFirstColor(Qt::white); plot->addChild(legend); } //add texts // TODO: not supported yet... /* for (const auto &s: layer.texts) { DEBUG("EXTRA TEXT =" << s.text.c_str()); // plot->newLegend(parseOriginText(QString::fromLocal8Bit(s.text.c_str()))); } */ //curves int curveIndex = 1; for (const auto& originCurve: layer.curves) { QString data(originCurve.dataName.c_str()); switch(data[0].toAscii()) { case 'T': case 'E': { if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::Scatter || originCurve.type == Origin::GraphCurve::LineSymbol || originCurve.type == Origin::GraphCurve::ErrorBar || originCurve.type == Origin::GraphCurve::XErrorBar) { // parse and use legend text // find substring between %c{curveIndex} and %c{curveIndex+1} int pos1 = legendText.indexOf(QString("\\c{%1}").arg(curveIndex)) + 5; int pos2 = legendText.indexOf(QString("\\c{%1}").arg(curveIndex+1)); QString curveText = legendText.mid(pos1, pos2 - pos1); // replace %(1), %(2), etc. with curve name curveText.replace(QString("%(%1)").arg(curveIndex), QString::fromLatin1(originCurve.yColumnName.c_str())); curveText = curveText.trimmed(); DEBUG(" curve " << curveIndex << " text = " << curveText.toStdString()); //XYCurve* xyCurve = new XYCurve(i18n("Curve") + QString::number(curveIndex)); //TODO: curve (legend) does not support HTML text yet. //XYCurve* xyCurve = new XYCurve(curveText); XYCurve* curve = new XYCurve(QString::fromLatin1(originCurve.yColumnName.c_str())); const QString& tableName = data.right(data.length() - 2); curve->setXColumnPath(tableName + "/" + originCurve.xColumnName.c_str()); curve->setYColumnPath(tableName + "/" + originCurve.yColumnName.c_str()); curve->suppressRetransform(true); if (!preview) loadCurve(originCurve, curve); plot->addChild(curve); curve->suppressRetransform(false); } else if (originCurve.type == Origin::GraphCurve::Column) { //vertical bars } else if (originCurve.type == Origin::GraphCurve::Bar) { //horizontal bars } else if (originCurve.type == Origin::GraphCurve::Histogram) { } } break; case 'F': { Origin::Function function; const vector::size_type funcIndex = m_originFile->functionIndex(data.right(data.length()-2).toStdString().c_str()); function = m_originFile->function(funcIndex); XYEquationCurve* xyEqCurve = new XYEquationCurve(function.name.c_str()); XYEquationCurve::EquationData eqData; eqData.count = function.totalPoints; eqData.expression1 = QString(function.formula.c_str()); if(function.type == Origin::Function::Polar) { eqData.type = XYEquationCurve::Polar; //replace 'x' by 'phi' eqData.expression1 = eqData.expression1.replace('x', "phi"); //convert from degrees to radians eqData.min = QString::number(function.begin/180) + QLatin1String("*pi"); eqData.max = QString::number(function.end/180) + QLatin1String("*pi"); } else { eqData.expression1 = QString(function.formula.c_str()); eqData.min = QString::number(function.begin); eqData.max = QString::number(function.end); } xyEqCurve->suppressRetransform(true); xyEqCurve->setEquationData(eqData); if (!preview) loadCurve(originCurve, xyEqCurve); plot->addChild(xyEqCurve); xyEqCurve->suppressRetransform(false); } } ++curveIndex; } worksheet->addChildFast(plot); } else { //no support for 3D plots yet //TODO: add an "UnsupportedAspect" here } ++index; } if (!preview) worksheet->updateLayout(); return true; } /* * sets the axis properties (format and ticks) as defined in \c originAxis in \c axis, * \c index being 0 or 1 for "top" and "bottom" or "left" and "right" for horizontal or vertical axes, respectively. */ void OriginProjectParser::loadAxis(const Origin::GraphAxis& originAxis, Axis* axis, int index, const QString& axisTitle) const { // int axisPosition; // possible values: // 0: Axis is at default position // 1: Axis is at (axisPositionValue)% from standard position // 2: Axis is at (axisPositionValue) position of ortogonal axis // double axisPositionValue; // bool zeroLine; // bool oppositeLine; //ranges axis->setStart(originAxis.min); axis->setEnd(originAxis.max); //ticks axis->setMajorTicksType(Axis::TicksIncrement); axis->setMajorTicksIncrement(originAxis.step); axis->setMinorTicksType(Axis::TicksTotalNumber); axis->setMinorTicksNumber(originAxis.minorTicks); //scale switch(originAxis.scale) { case Origin::GraphAxis::Linear: axis->setScale(Axis::ScaleLinear); break; case Origin::GraphAxis::Log10: axis->setScale(Axis::ScaleLog10); break; case Origin::GraphAxis::Ln: axis->setScale(Axis::ScaleLn); break; case Origin::GraphAxis::Log2: axis->setScale(Axis::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: axis->setScale(Axis::ScaleLinear); break; } //major grid const Origin::GraphGrid& majorGrid = originAxis.majorGrid; QPen gridPen = axis->majorGridPen(); Qt::PenStyle penStyle(Qt::NoPen); if (!majorGrid.hidden) { switch (majorGrid.style) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } } gridPen.setStyle(penStyle); Origin::Color gridColor; gridColor.type = Origin::Color::ColorType::Regular; gridColor.regular = majorGrid.color; gridPen.setColor(OriginProjectParser::color(gridColor)); gridPen.setWidthF(Worksheet::convertToSceneUnits(majorGrid.width, Worksheet::Point)); axis->setMajorGridPen(gridPen); //minor grid const Origin::GraphGrid& minorGrid = originAxis.minorGrid; gridPen = axis->minorGridPen(); penStyle = Qt::NoPen; if (!minorGrid.hidden) { switch (minorGrid.style) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } } gridPen.setStyle(penStyle); gridColor.regular = minorGrid.color; gridPen.setColor(OriginProjectParser::color(gridColor)); gridPen.setWidthF(Worksheet::convertToSceneUnits(minorGrid.width, Worksheet::Point)); axis->setMinorGridPen(gridPen); //process Origin::GraphAxisFormat const Origin::GraphAxisFormat& axisFormat = originAxis.formatAxis[index]; QPen pen; Origin::Color color; color.type = Origin::Color::ColorType::Regular; color.regular = axisFormat.color; pen.setColor(OriginProjectParser::color(color)); pen.setWidthF(Worksheet::convertToSceneUnits(axisFormat.thickness, Worksheet::Point)); axis->setLinePen(pen); axis->setMajorTicksLength( Worksheet::convertToSceneUnits(axisFormat.majorTickLength, Worksheet::Point) ); axis->setMajorTicksDirection( (Axis::TicksFlags) axisFormat.majorTicksType); axis->setMajorTicksPen(pen); axis->setMinorTicksLength( axis->majorTicksLength()/2); // minorTicksLength is half of majorTicksLength axis->setMinorTicksDirection( (Axis::TicksFlags) axisFormat.minorTicksType); axis->setMinorTicksPen(pen); QString titleText = parseOriginText(QString::fromLocal8Bit(axisFormat.label.text.c_str())); DEBUG(" axis title text = " << titleText.toStdString()); //TODO: parseOriginText() returns html formatted string. What is axisFormat.color used for? //TODO: use axisFormat.fontSize to override the global font size for the hmtl string? DEBUG(" curve name = " << axisTitle.toStdString()); titleText.replace("%(?X)", axisTitle); titleText.replace("%(?Y)", axisTitle); DEBUG(" axis title = " << titleText.toStdString()); axis->title()->setText(titleText); axis->title()->setRotationAngle(axisFormat.label.rotation); axis->setLabelsPrefix(axisFormat.prefix.c_str()); axis->setLabelsSuffix(axisFormat.suffix.c_str()); //TODO: handle string factor member in GraphAxisFormat //process Origin::GraphAxisTick const Origin::GraphAxisTick& tickAxis = originAxis.tickAxis[index]; if (tickAxis.showMajorLabels) { color.type = Origin::Color::ColorType::Regular; color.regular = tickAxis.color; axis->setLabelsColor(OriginProjectParser::color(color)); //TODO: how to set labels position (top vs. bottom)? } else { axis->setLabelsPosition(Axis::LabelsPosition::NoLabels); } //TODO: handle ValueType valueType member in GraphAxisTick //TODO: handle int valueTypeSpecification in GraphAxisTick axis->setLabelsPrecision(tickAxis.decimalPlaces); QFont font; //TODO: font family? font.setPixelSize( Worksheet::convertToSceneUnits(tickAxis.fontSize, Worksheet::Point) ); font.setBold(tickAxis.fontBold); axis->setLabelsFont(font); //TODO: handle string dataName member in GraphAxisTick //TODO: handle string columnName member in GraphAxisTick axis->setLabelsRotationAngle(tickAxis.rotation); } void OriginProjectParser::loadCurve(const Origin::GraphCurve& originCurve, XYCurve* curve) const { //line properties QPen pen = curve->linePen(); Qt::PenStyle penStyle(Qt::NoPen); if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::LineSymbol) { switch(originCurve.lineConnect) { case Origin::GraphCurve::NoLine: curve->setLineType(XYCurve::NoLine); break; case Origin::GraphCurve::Straight: curve->setLineType(XYCurve::Line); break; case Origin::GraphCurve::TwoPointSegment: curve->setLineType(XYCurve::Segments2); break; case Origin::GraphCurve::ThreePointSegment: curve->setLineType(XYCurve::Segments3); break; case Origin::GraphCurve::BSpline: case Origin::GraphCurve::Bezier: case Origin::GraphCurve::Spline: curve->setLineType(XYCurve::SplineCubicNatural); break; case Origin::GraphCurve::StepHorizontal: curve->setLineType(XYCurve::StartHorizontal); break; case Origin::GraphCurve::StepVertical: curve->setLineType(XYCurve::StartVertical); break; case Origin::GraphCurve::StepHCenter: curve->setLineType(XYCurve::MidpointHorizontal); break; case Origin::GraphCurve::StepVCenter: curve->setLineType(XYCurve::MidpointVertical); break; } switch (originCurve.lineStyle) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } pen.setStyle(penStyle); pen.setWidthF( Worksheet::convertToSceneUnits(originCurve.lineWidth, Worksheet::Point) ); pen.setColor(color(originCurve.lineColor)); curve->setLineOpacity(1 - originCurve.lineTransparency/255); //TODO: handle unsigned char boxWidth of Origin::GraphCurve } pen.setStyle(penStyle); curve->setLinePen(pen); //symbol properties if (originCurve.type == Origin::GraphCurve::Scatter || originCurve.type == Origin::GraphCurve::LineSymbol) { //try to map the different symbols, mapping is not exact curve->setSymbolsRotationAngle(0); switch(originCurve.symbolShape) { case 0: //NoSymbol curve->setSymbolsStyle(Symbol::NoSymbols); break; case 1: //Rect curve->setSymbolsStyle(Symbol::Square); break; case 2: //Ellipse case 20://Sphere curve->setSymbolsStyle(Symbol::Circle); break; case 3: //UTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 4: //DTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 5: //Diamond curve->setSymbolsStyle(Symbol::Diamond); break; case 6: //Cross + curve->setSymbolsStyle(Symbol::Cross); break; case 7: //Cross x curve->setSymbolsStyle(Symbol::Cross); break; case 8: //Snow curve->setSymbolsStyle(Symbol::Star4); break; case 9: //Horizontal - curve->setSymbolsStyle(Symbol::Line); curve->setSymbolsRotationAngle(90); break; case 10: //Vertical | curve->setSymbolsStyle(Symbol::Line); break; case 15: //LTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 16: //RTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 17: //Hexagon case 19: //Pentagon curve->setSymbolsStyle(Symbol::Square); break; case 18: //Star curve->setSymbolsStyle(Symbol::Star5); break; default: curve->setSymbolsStyle(Symbol::NoSymbols); } //Color symbolColor; //Color symbolFillColor; //TODO: doesn't work QBrush brush = curve->symbolsBrush(); brush.setColor(color(originCurve.symbolColor)); curve->setSymbolsBrush(brush); curve->setSymbolsSize(Worksheet::convertToSceneUnits(originCurve.symbolSize, Worksheet::Point)); QPen pen = curve->symbolsPen(); pen.setWidthF(Worksheet::convertToSceneUnits(originCurve.symbolThickness, Worksheet::Point)); //handle unsigned char pointOffset member //handle bool connectSymbols member } else { curve->setSymbolsStyle(Symbol::NoSymbols); } //filling properties if(originCurve.fillArea) { //TODO: handle unsigned char fillAreaType; //with 'fillAreaType'=0x10 the area between the curve and the x-axis is filled //with 'fillAreaType'=0x14 the area included inside the curve is filled. First and last curve points are joined by a line to close the otherwise open area. //with 'fillAreaType'=0x12 the area excluded outside the curve is filled. The inverse of fillAreaType=0x14 is filled. //At the moment we only support the first type, so set it to XYCurve::FillingBelow curve->setFillingPosition(XYCurve::FillingBelow); if (originCurve.fillAreaPattern == 0) { curve->setFillingType(PlotArea::Color); } else { curve->setFillingType(PlotArea::Pattern); //map different patterns in originCurve.fillAreaPattern (has the values of Origin::FillPattern) to Qt::BrushStyle; switch(originCurve.fillAreaPattern) { case 0: curve->setFillingBrushStyle(Qt::NoBrush); break; case 1: case 2: case 3: curve->setFillingBrushStyle(Qt::BDiagPattern); break; case 4: case 5: case 6: curve->setFillingBrushStyle(Qt::FDiagPattern); break; case 7: case 8: case 9: curve->setFillingBrushStyle(Qt::DiagCrossPattern); break; case 10: case 11: case 12: curve->setFillingBrushStyle(Qt::HorPattern); break; case 13: case 14: case 15: curve->setFillingBrushStyle(Qt::VerPattern); break; case 16: case 17: case 18: curve->setFillingBrushStyle(Qt::CrossPattern); break; } } curve->setFillingFirstColor(color(originCurve.fillAreaColor)); curve->setFillingOpacity(1 - originCurve.fillAreaTransparency/255); //Color fillAreaPatternColor - color for the pattern lines, not supported //double fillAreaPatternWidth - width of the pattern lines, not supported //bool fillAreaWithLineTransparency - transparency of the pattern lines indepetendent of the area transparency, not supported //TODO: //unsigned char fillAreaPatternBorderStyle; //Color fillAreaPatternBorderColor; //double fillAreaPatternBorderWidth; //The Border properties are used only in "Column/Bar" (histogram) plots. Those properties are: //fillAreaPatternBorderStyle for the line style (use enum Origin::LineStyle here) //fillAreaPatternBorderColor for the line color //fillAreaPatternBorderWidth for the line width } else curve->setFillingPosition(XYCurve::NoFilling); } bool OriginProjectParser::loadNote(Note* note, bool preview) { DEBUG("OriginProjectParser::loadNote()"); //load note data const Origin::Note& originNote = m_originFile->note(findNoteByName(note->name())); if (preview) return true; note->setComment(originNote.label.c_str()); note->setNote(QString::fromLatin1(originNote.text.c_str())); return true; } //############################################################################## //########################### Helper functions ################################ //############################################################################## QDateTime OriginProjectParser::creationTime(const tree::iterator& it) const { //this logic seems to be correct only for the first node (project node). For other nodes the current time is returned. char time_str[21]; strftime(time_str, sizeof(time_str), "%F %T", gmtime(&(*it).creationDate)); return QDateTime::fromString(QString(time_str), Qt::ISODate); } QString OriginProjectParser::parseOriginText(const QString &str) const { DEBUG("parseOriginText()"); QStringList lines = str.split("\n"); QString text = ""; for (int i = 0; i < lines.size(); ++i) { if (i > 0) text.append("\n"); text.append(parseOriginTags(lines[i])); } return text; } QColor OriginProjectParser::color(const Origin::Color& color) const { switch (color.type) { case Origin::Color::ColorType::Regular: switch (color.regular) { case Origin::Color::Black: return QColor(Qt::black); case Origin::Color::Red: return QColor(Qt::red); case Origin::Color::Green: return QColor(Qt::green); case Origin::Color::Blue: return QColor(Qt::blue); case Origin::Color::Cyan: return QColor(Qt::cyan); case Origin::Color::Magenta: return QColor(Qt::magenta); case Origin::Color::Yellow: return QColor(Qt::yellow); case Origin::Color::DarkYellow: return QColor(Qt::darkYellow); case Origin::Color::Navy: return QColor(0, 0, 128); case Origin::Color::Purple: return QColor(128, 0, 128); case Origin::Color::Wine: return QColor(128, 0, 0); case Origin::Color::Olive: return QColor(0, 128, 0); case Origin::Color::DarkCyan: return QColor(Qt::darkCyan); case Origin::Color::Royal: return QColor(0, 0, 160); case Origin::Color::Orange: return QColor(255, 128, 0); case Origin::Color::Violet: return QColor(128, 0, 255); case Origin::Color::Pink: return QColor(255, 0, 128); case Origin::Color::White: return QColor(Qt::white); case Origin::Color::LightGray: return QColor(Qt::lightGray); case Origin::Color::Gray: return QColor(Qt::gray); case Origin::Color::LTYellow: return QColor(255, 0, 128); case Origin::Color::LTCyan: return QColor(128, 255, 255); case Origin::Color::LTMagenta: return QColor(255, 128, 255); case Origin::Color::DarkGray: return QColor(Qt::darkGray); case Origin::Color::SpecialV7Axis: return QColor(Qt::black); } break; case Origin::Color::ColorType::Custom: return QColor(color.custom[0], color.custom[1], color.custom[2]); case Origin::Color::ColorType::None: case Origin::Color::ColorType::Automatic: case Origin::Color::ColorType::Increment: case Origin::Color::ColorType::Indexing: case Origin::Color::ColorType::RGB: case Origin::Color::ColorType::Mapping: break; } return QColor(Qt::white); } PlotArea::BackgroundColorStyle OriginProjectParser::backgroundColorStyle(const Origin::ColorGradientDirection& colorGradient) const { switch (colorGradient) { case Origin::ColorGradientDirection::NoGradient: return PlotArea::BackgroundColorStyle::SingleColor; case Origin::ColorGradientDirection::TopLeft: return PlotArea::BackgroundColorStyle::TopLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Left: return PlotArea::BackgroundColorStyle::HorizontalLinearGradient; case Origin::ColorGradientDirection::BottomLeft: return PlotArea::BackgroundColorStyle::BottomLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Top: return PlotArea::BackgroundColorStyle::VerticalLinearGradient; case Origin::ColorGradientDirection::Center: return PlotArea::BackgroundColorStyle::RadialGradient; case Origin::ColorGradientDirection::Bottom: return PlotArea::BackgroundColorStyle::VerticalLinearGradient; case Origin::ColorGradientDirection::TopRight: return PlotArea::BackgroundColorStyle::BottomLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Right: return PlotArea::BackgroundColorStyle::HorizontalLinearGradient; case Origin::ColorGradientDirection::BottomRight: return PlotArea::BackgroundColorStyle::TopLeftDiagonalLinearGradient; } return PlotArea::BackgroundColorStyle::SingleColor; } QString strreverse(const QString &str) { //QString reversing QByteArray ba = str.toLocal8Bit(); std::reverse(ba.begin(), ba.end()); return QString(ba); } // taken from SciDAVis QString OriginProjectParser::parseOriginTags(const QString &str) const { DEBUG("parseOriginTags()"); DEBUG(" string: " << str.toStdString()); QString line = str; //replace \l(...) and %(...) tags QRegExp rxline("\\\\\\s*l\\s*\\(\\s*\\d+\\s*\\)"); - QRegExp rxcol("\\%\\(\\d+\\)"); +// QRegExp rxcol("\\%\\(\\d+\\)"); int pos = rxline.indexIn(line); while (pos > -1) { QString value = rxline.cap(0); int len = value.length(); value.replace(QRegExp(" "),""); value = "\\c{" + value.mid(3, value.length()-4) + "}"; line.replace(pos, len, value); pos = rxline.indexIn(line); } //Lookbehind conditions are not supported - so need to reverse string QRegExp rx("\\)[^\\)\\(]*\\((?!\\s*[buig\\+\\-]\\s*\\\\)"); QRegExp rxfont("\\)[^\\)\\(]*\\((?![^\\:]*\\:f\\s*\\\\)"); QString linerev = strreverse(line); QString lBracket = strreverse("&lbracket;"); QString rBracket = strreverse("&rbracket;"); QString ltagBracket = strreverse("<agbracket;"); QString rtagBracket = strreverse("&rtagbracket;"); int pos1 = rx.indexIn(linerev); int pos2 = rxfont.indexIn(linerev); while (pos1 > -1 || pos2 > -1) { if (pos1 == pos2) { QString value = rx.cap(0); int len = value.length(); value = rBracket + value.mid(1, len-2) + lBracket; linerev.replace(pos1, len, value); } else if ((pos1 > pos2 && pos2 != -1) || pos1 == -1) { QString value = rxfont.cap(0); int len = value.length(); value = rtagBracket + value.mid(1, len-2) + ltagBracket; linerev.replace(pos2, len, value); } else if ((pos2 > pos1 && pos1 != -1) || pos2 == -1) { QString value = rx.cap(0); int len = value.length(); value = rtagBracket + value.mid(1, len-2) + ltagBracket; linerev.replace(pos1, len, value); } pos1 = rx.indexIn(linerev); pos2 = rxfont.indexIn(linerev); } linerev.replace(ltagBracket, "("); linerev.replace(rtagBracket, ")"); line = strreverse(linerev); //replace \b(...), \i(...), \u(...), \g(...), \+(...), \-(...), \f:font(...) tags const QString rxstr[] = { "\\\\\\s*b\\s*\\(", "\\\\\\s*i\\s*\\(", "\\\\\\s*u\\s*\\(", "\\\\\\s*g\\s*\\(", "\\\\\\s*\\+\\s*\\(", "\\\\\\s*\\-\\s*\\(", "\\\\\\s*f\\:[^\\(]*\\("}; int postag[] = {0, 0, 0, 0, 0, 0, 0}; QString ltag[] = {"","","","","","",""}; QString rtag[] = {"","","","","","",""}; QRegExp rxtags[7]; for (int i = 0; i < 7; ++i) rxtags[i].setPattern(rxstr[i]+"[^\\(\\)]*\\)"); bool flag = true; while (flag) { for (int i = 0; i < 7; ++i) { postag[i] = rxtags[i].indexIn(line); while (postag[i] > -1) { QString value = rxtags[i].cap(0); int len = value.length(); pos2 = value.indexOf("("); if (i < 6) value = ltag[i] + value.mid(pos2+1, len-pos2-2) + rtag[i]; else { int posfont = value.indexOf("f:"); value = ltag[i].arg(value.mid(posfont+2, pos2-posfont-2)) + value.mid(pos2+1, len-pos2-2) + rtag[i]; } line.replace(postag[i], len, value); postag[i] = rxtags[i].indexIn(line); } } flag = false; for (int i = 0; i < 7; ++i) { if (rxtags[i].indexIn(line) > -1) { flag = true; break; } } } //replace unclosed tags for (int i = 0; i < 6; ++i) line.replace(QRegExp(rxstr[i]), ltag[i]); rxfont.setPattern(rxstr[6]); pos = rxfont.indexIn(line); while (pos > -1) { QString value = rxfont.cap(0); int len = value.length(); int posfont = value.indexOf("f:"); value = ltag[6].arg(value.mid(posfont+2, len-posfont-3)); line.replace(pos, len, value); pos = rxfont.indexIn(line); } line.replace("&lbracket;", "("); line.replace("&rbracket;", ")"); DEBUG(" result: " << line.toStdString()); return line; } diff --git a/src/backend/datasources/projects/ProjectParser.cpp b/src/backend/datasources/projects/ProjectParser.cpp index b0306558f..68b5d16f2 100644 --- a/src/backend/datasources/projects/ProjectParser.cpp +++ b/src/backend/datasources/projects/ProjectParser.cpp @@ -1,164 +1,164 @@ /*************************************************************************** File : ProjectParser.h Project : LabPlot Description : base class for project parsers -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ProjectParser.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/lib/trace.h" #include /*! \class ProjectParser \brief base class for project parsers \ingroup datasources */ ProjectParser::ProjectParser() : QObject(), m_project(nullptr) { } ProjectParser::~ProjectParser() { if (m_project != nullptr) delete m_project; } void ProjectParser::setProjectFileName(const QString& name) { m_projectFileName = name; //delete the previous project object if (m_project) { delete m_project; m_project = nullptr; } } const QString& ProjectParser::projectFileName() const { return m_projectFileName; } QList ProjectParser::topLevelClasses() const { return m_topLevelClasses; } QAbstractItemModel* ProjectParser::model() { WAIT_CURSOR; PERFTRACE("project model for preview created"); if (m_project == nullptr) m_project = new Project(); AspectTreeModel* model = nullptr; bool rc = load(m_project, true); if (rc) { model = new AspectTreeModel(m_project); model->setReadOnly(true); } RESET_CURSOR; return model; } void ProjectParser::importTo(Folder* targetFolder, const QStringList& selectedPathes) { QDEBUG("Starting the import of " + m_projectFileName); //import the selected objects into a temporary project Project* project = new Project(); project->setPathesToLoad(selectedPathes); load(project, false); //determine the first child of the last top level child in the list of the imported objects //we want to navigate to in the project explorer after the import AbstractAspect* lastTopLevelChild = project->child(project->childCount()-1); AbstractAspect* childToNavigate = nullptr; if (lastTopLevelChild->childCount() > 0) { childToNavigate = lastTopLevelChild->child(0); //we don't want to select columns, select rather their parent spreadsheet if (dynamic_cast(childToNavigate)) childToNavigate = lastTopLevelChild; } else { childToNavigate = lastTopLevelChild; } //move all children from the temp project to the target folder - targetFolder->beginMacro(i18n("%1: Import from %2").arg(targetFolder->name()).arg(m_projectFileName)); + targetFolder->beginMacro(i18n("%1: Import from %2").arg(targetFolder->name(), m_projectFileName)); for (auto* child : project->children()) { Folder* folder = dynamic_cast(child); if (folder) { moveFolder(targetFolder, folder); } else { project->removeChild(child); //remove the object to be imported in the target folder if it's already existing AbstractAspect* targetChild = targetFolder->child(child->name()); if (targetChild) targetFolder->removeChild(targetChild); targetFolder->addChild(child); } } targetFolder->endMacro(); delete project; targetFolder->project()->navigateTo(childToNavigate->path()); QDEBUG("Import of " + m_projectFileName + " done."); } /* * moved \c sourceChildFolderToMove from its parten folder to \c targetParentFolder * keeping (not overwriting ) the sub-folder structure. */ void ProjectParser::moveFolder(Folder* targetParentFolder, Folder* sourceChildFolderToMove) const { Folder* targetChildFolder = targetParentFolder->child(sourceChildFolderToMove->name()); if (targetChildFolder) { //folder exists already in the target parent folder, //-> recursively move its children from source into target parent folder for (auto* child : sourceChildFolderToMove->children()) { Folder* folder = dynamic_cast(child); if (folder) { moveFolder(targetChildFolder, folder); } else { sourceChildFolderToMove->removeChild(child); //remove the object to be imported in the target folder if it's already existing AbstractAspect* targetChild = targetChildFolder->child(child->name()); if (targetChild) targetChildFolder->removeChild(targetChild); targetChildFolder->addChild(child); } } } else { //folder doesn't exist yet in the target parent folder -> simply move it Folder* sourceParentFolder = dynamic_cast(sourceChildFolderToMove->parentAspect()); sourceParentFolder->removeChild(sourceChildFolderToMove); targetParentFolder->addChild(sourceChildFolderToMove); } } diff --git a/src/backend/note/Note.cpp b/src/backend/note/Note.cpp index 849cf33bc..5a71542a1 100644 --- a/src/backend/note/Note.cpp +++ b/src/backend/note/Note.cpp @@ -1,180 +1,180 @@ /*************************************************************************** File : Notes.cpp Project : LabPlot Description : Notes Widget for taking notes -------------------------------------------------------------------- Copyright : (C) 2009-2015 Garvit Khatri (garvitdelhi@gmail.com) Copyright : (C) 2016 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "Note.h" #include "commonfrontend/note/NoteView.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include #include #include #include #include #include #include Note::Note(const QString& name) : AbstractPart(name), m_view(nullptr) { KConfig config; KConfigGroup group = config.group("Notes"); m_backgroundColor = group.readEntry("BackgroundColor", QColor(Qt::yellow)); m_textColor = group.readEntry("TextColor", QColor(Qt::black)); m_textFont = group.readEntry("TextFont", QFont()); } QIcon Note::icon() const { return QIcon::fromTheme("document-new"); } bool Note::printView() { QPrinter printer; QPrintDialog* dlg = new QPrintDialog(&printer, m_view); dlg->setWindowTitle(i18n("Print Worksheet")); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool Note::printPreview() const { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &NoteView::print); return dlg->exec(); } bool Note::exportView() const { return false; } void Note::setNote(const QString& note) { m_note = note; } const QString& Note::note() const { return m_note; } void Note::setBackgroundColor(const QColor& color) { m_backgroundColor = color; - emit (backgroundColorChanged(color)); + emit backgroundColorChanged(color); } const QColor& Note::backgroundColor() const { return m_backgroundColor; } void Note::setTextColor(const QColor& color) { m_textColor = color; - emit (textColorChanged(color)); + emit textColorChanged(color); } const QColor& Note::textColor() const{ return m_textColor; } void Note::setTextFont(const QFont& font) { m_textFont = font; - emit (textFontChanged(font)); + emit textFontChanged(font); } const QFont& Note::textFont() const { return m_textFont; } QWidget* Note::view() const { if (!m_partView) { m_view = new NoteView(const_cast(this)); m_partView = m_view; } return m_partView; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Note::save(QXmlStreamWriter* writer) const { writer->writeStartElement("note"); writeBasicAttributes(writer); writeCommentElement(writer); writer->writeStartElement("background"); WRITE_QCOLOR(m_backgroundColor); writer->writeEndElement(); writer->writeStartElement("text"); WRITE_QCOLOR(m_textColor); WRITE_QFONT(m_textFont); writer->writeAttribute("text", m_note); writer->writeEndElement(); writer->writeEndElement(); // close "note" section } bool Note::load(XmlStreamReader* reader, bool preview) { if (!reader->isStartElement() || reader->name() != "note") { reader->raiseError(i18n("no note element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "note") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); READ_QCOLOR(m_backgroundColor); } else if (!preview && reader->name() == "text") { attribs = reader->attributes(); READ_QCOLOR(m_textColor); READ_QFONT(m_textFont); m_note = attribs.value("text").toString(); } } return true; } diff --git a/src/backend/worksheet/TextLabel.cpp b/src/backend/worksheet/TextLabel.cpp index 9454d057e..b40207504 100644 --- a/src/backend/worksheet/TextLabel.cpp +++ b/src/backend/worksheet/TextLabel.cpp @@ -1,872 +1,872 @@ /*************************************************************************** File : TextLabel.cpp Project : LabPlot Description : Text label supporting reach text and latex formatting -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "TextLabel.h" #include "Worksheet.h" #include "TextLabelPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * \class TextLabel * \brief A label supporting rendering of html- and tex-formated textes. * * The label is aligned relative to the specified position. * The position can be either specified by providing the x- and y- coordinates * in parent's coordinate system, or by specifying one of the predefined position * flags (\ca HorizontalPosition, \ca VerticalPosition). */ TextLabel::TextLabel(const QString& name, Type type):WorksheetElement(name), d_ptr(new TextLabelPrivate(this)), m_type(type), visibilityAction(nullptr) { init(); } TextLabel::TextLabel(const QString &name, TextLabelPrivate *dd, Type type):WorksheetElement(name), d_ptr(dd), m_type(type), visibilityAction(nullptr) { init(); } TextLabel::Type TextLabel::type() const { return m_type; } void TextLabel::init() { Q_D(TextLabel); KConfig config; KConfigGroup group; if (m_type == AxisTitle) group = config.group("AxisTitle"); else if (m_type == PlotTitle) group = config.group("PlotTitle"); else if (m_type == PlotLegendTitle) group = config.group("PlotLegendTitle"); else group = config.group("TextLabel"); //properties common to all types d->textWrapper.teXUsed = group.readEntry("TeXUsed", false); d->teXFont.setFamily(group.readEntry("TeXFontFamily", "Computer Modern")); d->teXFont.setPointSize(group.readEntry("TeXFontSize", 12)); d->teXFontColor = group.readEntry("TeXFontColor", QColor(Qt::black)); d->teXBackgroundColor = group.readEntry("TeXBackgroundColor", QColor(Qt::white)); d->rotationAngle = group.readEntry("Rotation", 0.0); d->staticText.setTextFormat(Qt::RichText); // explicitly set no wrap mode for text label to avoid unnecessary line breaks QTextOption textOption; textOption.setWrapMode(QTextOption::NoWrap); d->staticText.setTextOption(textOption); //position and alignment relevant properties, dependent on the actual type if (m_type == PlotTitle || m_type == PlotLegendTitle) { d->position.horizontalPosition = (HorizontalPosition) group.readEntry("PositionX", (int)TextLabel::hPositionCenter); d->position.verticalPosition = (VerticalPosition) group.readEntry("PositionY", (int) TextLabel::vPositionTop); d->position.point.setX( group.readEntry("PositionXValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->position.point.setY( group.readEntry("PositionYValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->horizontalAlignment= (TextLabel::HorizontalAlignment) group.readEntry("HorizontalAlignment", (int)TextLabel::hAlignCenter); d->verticalAlignment= (TextLabel::VerticalAlignment) group.readEntry("VerticalAlignment", (int)TextLabel::vAlignBottom); } else { d->position.horizontalPosition = (HorizontalPosition) group.readEntry("PositionX", (int)TextLabel::hPositionCustom); d->position.verticalPosition = (VerticalPosition) group.readEntry("PositionY", (int) TextLabel::vPositionCustom); d->position.point.setX( group.readEntry("PositionXValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->position.point.setY( group.readEntry("PositionYValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->horizontalAlignment= (TextLabel::HorizontalAlignment) group.readEntry("HorizontalAlignment", (int)TextLabel::hAlignCenter); d->verticalAlignment= (TextLabel::VerticalAlignment) group.readEntry("VerticalAlignment", (int)TextLabel::vAlignCenter); } //scaling: //we need to scale from the font size specified in points to scene units. //furhermore, we create the tex-image in a higher resolution then usual desktop resolution // -> take this into account d->scaleFactor = Worksheet::convertToSceneUnits(1, Worksheet::Point); d->teXImageResolution = QApplication::desktop()->physicalDpiX(); d->teXImageScaleFactor = Worksheet::convertToSceneUnits(2.54/QApplication::desktop()->physicalDpiX(), Worksheet::Centimeter); connect(&d->teXImageFutureWatcher, &QFutureWatcher::finished, this, &TextLabel::updateTeXImage); } TextLabel::~TextLabel() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } QGraphicsItem* TextLabel::graphicsItem() const { return d_ptr; } void TextLabel::setParentGraphicsItem(QGraphicsItem* item) { Q_D(TextLabel); d->setParentItem(item); d->updatePosition(); } void TextLabel::retransform() { Q_D(TextLabel); d->retransform(); } void TextLabel::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("TextLabel::handleResize()"); Q_UNUSED(pageResize); Q_D(TextLabel); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); d->teXFont.setPointSizeF(d->teXFont.pointSizeF() * ratio); d->updateText(); //TODO: doesn't seem to work QTextDocument doc; doc.setHtml(d->textWrapper.text); QTextCursor cursor(&doc); cursor.select(QTextCursor::Document); QTextCharFormat fmt = cursor.charFormat(); QFont font = fmt.font(); font.setPointSizeF(font.pointSizeF() * ratio); fmt.setFont(font); cursor.setCharFormat(fmt); } /*! Returns an icon to be used in the project explorer. */ QIcon TextLabel::icon() const{ return QIcon::fromTheme("draw-text"); } QMenu* TextLabel::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" if (!visibilityAction) { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &TextLabel::visibilityChanged); } visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertSeparator(firstAction); return menu; } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::TextWrapper, text, textWrapper) CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, teXFontColor, teXFontColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, teXBackgroundColor, teXBackgroundColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QFont, teXFont, teXFont); CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::PositionWrapper, position, position); BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::HorizontalAlignment, horizontalAlignment, horizontalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::VerticalAlignment, verticalAlignment, verticalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, qreal, rotationAngle, rotationAngle); /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(TextLabel, SetText, TextLabel::TextWrapper, textWrapper, updateText); void TextLabel::setText(const TextWrapper &textWrapper) { Q_D(TextLabel); if ( (textWrapper.text != d->textWrapper.text) || (textWrapper.teXUsed != d->textWrapper.teXUsed) ) exec(new TextLabelSetTextCmd(d, textWrapper, i18n("%1: set label text"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFont, QFont, teXFont, updateText); void TextLabel::setTeXFont(const QFont& font) { Q_D(TextLabel); if (font != d->teXFont) exec(new TextLabelSetTeXFontCmd(d, font, i18n("%1: set TeX main font"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFontColor, QColor, teXFontColor, updateText); void TextLabel::setTeXFontColor(const QColor color) { Q_D(TextLabel); if (color != d->teXFontColor) exec(new TextLabelSetTeXFontColorCmd(d, color, i18n("%1: set TeX font color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXBackgroundColor, QColor, teXBackgroundColor, updateText); void TextLabel::setTeXBackgroundColor(const QColor color) { Q_D(TextLabel); if (color != d->teXBackgroundColor) exec(new TextLabelSetTeXBackgroundColorCmd(d, color, i18n("%1: set TeX background color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetPosition, TextLabel::PositionWrapper, position, retransform); void TextLabel::setPosition(const PositionWrapper& pos) { Q_D(TextLabel); if (pos.point!=d->position.point || pos.horizontalPosition!=d->position.horizontalPosition || pos.verticalPosition!=d->position.verticalPosition) exec(new TextLabelSetPositionCmd(d, pos, i18n("%1: set position"))); } /*! sets the position without undo/redo-stuff */ void TextLabel::setPosition(const QPointF& point) { Q_D(TextLabel); if (point != d->position.point) { d->position.point = point; retransform(); } } /*! * position is set to invalid if the parent item is not drawn on the scene * (e.g. axis is not drawn because it's outside plot ranges -> don't draw axis' title label) */ void TextLabel::setPositionInvalid(bool invalid) { Q_D(TextLabel); if (invalid != d->positionInvalid) { d->positionInvalid = invalid; } } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetRotationAngle, qreal, rotationAngle, recalcShapeAndBoundingRect); void TextLabel::setRotationAngle(qreal angle) { Q_D(TextLabel); if (angle != d->rotationAngle) exec(new TextLabelSetRotationAngleCmd(d, angle, i18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetHorizontalAlignment, TextLabel::HorizontalAlignment, horizontalAlignment, retransform); void TextLabel::setHorizontalAlignment(const TextLabel::HorizontalAlignment hAlign) { Q_D(TextLabel); if (hAlign != d->horizontalAlignment) exec(new TextLabelSetHorizontalAlignmentCmd(d, hAlign, i18n("%1: set horizontal alignment"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetVerticalAlignment, TextLabel::VerticalAlignment, verticalAlignment, retransform); void TextLabel::setVerticalAlignment(const TextLabel::VerticalAlignment vAlign) { Q_D(TextLabel); if (vAlign != d->verticalAlignment) exec(new TextLabelSetVerticalAlignmentCmd(d, vAlign, i18n("%1: set vertical alignment"))); } STD_SWAP_METHOD_SETTER_CMD_IMPL_F(TextLabel, SetVisible, bool, swapVisible, retransform); void TextLabel::setVisible(bool on) { Q_D(TextLabel); exec(new TextLabelSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); } bool TextLabel::isVisible() const { Q_D(const TextLabel); return d->isVisible(); } void TextLabel::setPrinting(bool on) { Q_D(TextLabel); d->m_printing = on; } void TextLabel::updateTeXImage() { Q_D(TextLabel); d->updateTeXImage(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void TextLabel::visibilityChanged() { Q_D(const TextLabel); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## TextLabelPrivate::TextLabelPrivate(TextLabel* owner) : teXRenderSuccessful(false), positionInvalid(false), suppressItemChangeEvent(false), suppressRetransform(false), m_printing(false), m_hovered(false), q(owner) { setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setAcceptHoverEvents(true); } QString TextLabelPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the label. Called on geometry or text changes. */ void TextLabelPrivate::retransform() { if (suppressRetransform) return; if (position.horizontalPosition != TextLabel::hPositionCustom || position.verticalPosition != TextLabel::vPositionCustom) updatePosition(); float x = position.point.x(); float y = position.point.y(); //determine the size of the label in scene units. float w, h; if (textWrapper.teXUsed) { //image size is in pixel, convert to scene units w = teXImage.width()*teXImageScaleFactor; h = teXImage.height()*teXImageScaleFactor; } else { //size is in points, convert to scene units w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new GraphicsItem's position in parent's coordinate system QPointF itemPos; switch (horizontalAlignment) { case TextLabel::hAlignLeft: itemPos.setX(x - w/2); break; case TextLabel::hAlignCenter: itemPos.setX(x); break; case TextLabel::hAlignRight: itemPos.setX(x + w/2); break; } switch (verticalAlignment) { case TextLabel::vAlignTop: itemPos.setY(y - h/2); break; case TextLabel::vAlignCenter: itemPos.setY(y); break; case TextLabel::vAlignBottom: itemPos.setY(y + h/2); break; } suppressItemChangeEvent = true; setPos(itemPos); suppressItemChangeEvent = false; boundingRectangle.setX(-w/2); boundingRectangle.setY(-h/2); boundingRectangle.setWidth(w); boundingRectangle.setHeight(h); recalcShapeAndBoundingRect(); } /*! calculates the position of the label, when the position relative to the parent was specified (left, right, etc.) */ void TextLabelPrivate::updatePosition() { //determine the parent item QRectF parentRect; QGraphicsItem* parent = parentItem(); if (parent) { parentRect = parent->boundingRect(); } else { if (!scene()) return; parentRect = scene()->sceneRect(); } if (position.horizontalPosition != TextLabel::hPositionCustom) { if (position.horizontalPosition == TextLabel::hPositionLeft) position.point.setX( parentRect.x() ); else if (position.horizontalPosition == TextLabel::hPositionCenter) position.point.setX( parentRect.x() + parentRect.width()/2 ); else if (position.horizontalPosition == TextLabel::hPositionRight) position.point.setX( parentRect.x() + parentRect.width() ); } if (position.verticalPosition != TextLabel::vPositionCustom) { if (position.verticalPosition == TextLabel::vPositionTop) position.point.setY( parentRect.y() ); else if (position.verticalPosition == TextLabel::vPositionCenter) position.point.setY( parentRect.y() + parentRect.height()/2 ); else if (position.verticalPosition == TextLabel::vPositionBottom) position.point.setY( parentRect.y() + parentRect.height() ); } emit q->positionChanged(position); } /*! updates the static text. */ void TextLabelPrivate::updateText() { if (suppressRetransform) return; if (textWrapper.teXUsed) { TeXRenderer::Formatting format; format.fontColor = teXFontColor; format.backgroundColor = teXBackgroundColor; format.fontSize = teXFont.pointSize(); format.fontFamily = teXFont.family(); format.dpi = teXImageResolution; QFuture future = QtConcurrent::run(TeXRenderer::renderImageLaTeX, textWrapper.text, &teXRenderSuccessful, format); teXImageFutureWatcher.setFuture(future); //don't need to call retransorm() here since it is done in updateTeXImage //when the asynchronous rendering of the image is finished. } else { staticText.setText(textWrapper.text); //the size of the label was most probably changed. //call retransform() to recalculate the position and the bounding box of the label retransform(); } } void TextLabelPrivate::updateTeXImage() { teXImage = teXImageFutureWatcher.result(); retransform(); DEBUG("teXRenderSuccessful =" << teXRenderSuccessful); emit q->teXImageUpdated(teXRenderSuccessful); } bool TextLabelPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF TextLabelPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath TextLabelPrivate::shape() const { return labelShape; } /*! recalculates the outer bounds and the shape of the label. */ void TextLabelPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); QMatrix matrix; matrix.rotate(-rotationAngle); transformedBoundingRectangle = matrix.mapRect(boundingRectangle); labelShape = QPainterPath(); labelShape.addRect(boundingRectangle); labelShape = matrix.map(labelShape); - emit(q->changed()); + emit q->changed(); } void TextLabelPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (positionInvalid) return; if (textWrapper.text.isEmpty()) return; painter->save(); painter->rotate(-rotationAngle); if (textWrapper.teXUsed) { if (boundingRect().width() != 0.0 && boundingRect().height() != 0.0) painter->drawImage(boundingRect(), teXImage); } else { painter->scale(scaleFactor, scaleFactor); float w = staticText.size().width(); float h = staticText.size().height(); painter->drawStaticText(QPoint(-w/2,-h/2), staticText); } painter->restore(); if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(labelShape); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(labelShape); } } QVariant TextLabelPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates TextLabel::PositionWrapper tempPosition; tempPosition.point = positionFromItemPosition(value.toPointF()); tempPosition.horizontalPosition = TextLabel::hPositionCustom; tempPosition.verticalPosition = TextLabel::vPositionCustom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void TextLabelPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = positionFromItemPosition(pos()); if (qAbs(point.x()-position.point.x())>20 && qAbs(point.y()-position.point.y())>20 ) { //position was changed -> set the position related member variables suppressRetransform = true; TextLabel::PositionWrapper tempPosition; tempPosition.point = point; tempPosition.horizontalPosition = TextLabel::hPositionCustom; tempPosition.verticalPosition = TextLabel::vPositionCustom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } /*! * converts label's position to GraphicsItem's position. */ QPointF TextLabelPrivate::positionFromItemPosition(const QPointF& itemPos) { float x = itemPos.x(); float y = itemPos.y(); float w, h; QPointF tmpPosition; if (textWrapper.teXUsed) { w = teXImage.width()*scaleFactor; h = teXImage.height()*scaleFactor; } else { w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new position switch (horizontalAlignment) { case TextLabel::hAlignLeft: tmpPosition.setX(x + w/2); break; case TextLabel::hAlignCenter: tmpPosition.setX(x); break; case TextLabel::hAlignRight: tmpPosition.setX(x - w/2); break; } switch (verticalAlignment) { case TextLabel::vAlignTop: tmpPosition.setY(y + h/2); break; case TextLabel::vAlignCenter: tmpPosition.setY(y); break; case TextLabel::vAlignBottom: tmpPosition.setY(y - h/2); break; } return tmpPosition; } void TextLabelPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void TextLabelPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; - q->hovered(); + emit q->hovered(); update(); } } void TextLabelPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; - q->unhovered(); + emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void TextLabel::save(QXmlStreamWriter* writer) const { Q_D(const TextLabel); writer->writeStartElement( "textLabel" ); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); writer->writeAttribute( "horizontalAlignment", QString::number(d->horizontalAlignment) ); writer->writeAttribute( "verticalAlignment", QString::number(d->verticalAlignment) ); writer->writeAttribute( "rotationAngle", QString::number(d->rotationAngle) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); writer->writeStartElement( "text" ); writer->writeCharacters( d->textWrapper.text ); writer->writeEndElement(); writer->writeStartElement( "format" ); writer->writeAttribute( "teXUsed", QString::number(d->textWrapper.teXUsed) ); WRITE_QFONT(d->teXFont); writer->writeAttribute( "teXFontColor_r", QString::number(d->teXFontColor.red()) ); writer->writeAttribute( "teXFontColor_g", QString::number(d->teXFontColor.green()) ); writer->writeAttribute( "teXFontColor_b", QString::number(d->teXFontColor.blue()) ); writer->writeEndElement(); if (d->textWrapper.teXUsed) { writer->writeStartElement("teXImage"); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); d->teXImage.save(&buffer, "PNG"); writer->writeCharacters(ba.toBase64()); writer->writeEndElement(); } writer->writeEndElement(); // close "textLabel" section } //! Load from XML bool TextLabel::load(XmlStreamReader* reader, bool preview) { if(!reader->isStartElement() || reader->name() != "textLabel") { reader->raiseError(i18n("no textLabel element found")); return false; } if (!readBasicAttributes(reader)) return false; Q_D(TextLabel); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool teXImageFound = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "textLabel") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.point.setY(str.toDouble()); str = attribs.value("horizontalPosition").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPosition'")); else d->position.horizontalPosition = (TextLabel::HorizontalPosition)str.toInt(); str = attribs.value("verticalPosition").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPosition'")); else d->position.verticalPosition = (TextLabel::VerticalPosition)str.toInt(); str = attribs.value("horizontalAlignment").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalAlignment'")); else d->horizontalAlignment = (TextLabel::HorizontalAlignment)str.toInt(); str = attribs.value("verticalAlignment").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalAlignment'")); else d->verticalAlignment = (TextLabel::VerticalAlignment)str.toInt(); str = attribs.value("rotationAngle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotationAngle'")); else d->rotationAngle = str.toInt(); str = attribs.value("visible").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "text") { d->textWrapper.text = reader->readElementText(); } else if (!preview && reader->name() == "format") { attribs = reader->attributes(); str = attribs.value("teXUsed").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXUsed'")); else d->textWrapper.teXUsed = str.toInt(); READ_QFONT(d->teXFont); str = attribs.value("teXFontColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXFontColor_r'")); else d->teXFontColor.setRed( str.toInt() ); str = attribs.value("teXFontColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXFontColor_g'")); else d->teXFontColor.setGreen( str.toInt() ); str = attribs.value("teXFontColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXFontColor_b'")); else d->teXFontColor.setBlue( str.toInt() ); } else if (!preview && reader->name() == "teXImage") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray ba = QByteArray::fromBase64(content.toAscii()); teXImageFound = d->teXImage.loadFromData(ba); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; //in case we use latex and the image was stored (older versions of LabPlot didn't save the image)and loaded, //we just need to retransform. //otherwise, we set the static text and retransform in updateText() if ( !(d->textWrapper.teXUsed && teXImageFound) ) d->updateText(); else retransform(); return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void TextLabel::loadThemeConfig(const KConfig& config) { Q_D(TextLabel); KConfigGroup group = config.group("Label"); const QColor fontColor = group.readEntry("FontColor", QColor(Qt::white)); const QColor backgroundColor = group.readEntry("BackgroundColor", QColor(Qt::black)); d->suppressRetransform = true; if (!d->textWrapper.teXUsed && !d->textWrapper.text.isEmpty()) { //replace colors in the html-formatted string QTextDocument doc; doc.setHtml(d->textWrapper.text); QTextCharFormat fmt; fmt.setForeground(QBrush(fontColor)); fmt.setBackground(QBrush(backgroundColor)); QTextCursor cursor(&doc); cursor.select(QTextCursor::Document); cursor.setCharFormat(fmt); TextLabel::TextWrapper wrapper(doc.toHtml(), d->textWrapper.teXUsed); this->setText(wrapper); } else { //replace colors in the TeX-string this->setTeXFontColor(fontColor); this->setTeXBackgroundColor(backgroundColor); } d->suppressRetransform = false; d->updateText(); } void TextLabel::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Label"); //TODO // group.writeEntry("TeXFontColor", (QColor) this->teXFontColor()); } diff --git a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp index 28a043d53..4419ea89b 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp @@ -1,681 +1,679 @@ /*************************************************************************** File : CartesianCoordinateSystem.cpp Project : LabPlot Description : Cartesian coordinate system for plots. -------------------------------------------------------------------- Copyright : (C) 2012-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" /* ============================================================================ */ /* =================================== scales ================================= */ /* ============================================================================ */ /** * \class CartesianScale * \brief Base class for cartesian coordinate system scales. */ CartesianScale::CartesianScale(ScaleType type, const Interval &interval, double a, double b, double c) : m_type(type), m_interval(interval), m_a(a), m_b(b), m_c(c) { } CartesianScale::~CartesianScale() {} void CartesianScale::getProperties(ScaleType *type, Interval *interval, double *a, double *b, double *c) const { if (type) *type = m_type; if (interval) *interval = m_interval; if (a) *a = m_a; if (b) *b = m_b; if (c) *c = m_c; } double CartesianScale::start() const { return m_interval.start(); } double CartesianScale::end() const { return m_interval.end(); } bool CartesianScale::contains(double value) const { return m_interval.contains(value); } /** * \class CartesianCoordinateSystem::LinearScale * \brief implementation of the linear scale for cartesian coordinate system. */ class LinearScale : public CartesianScale { public: LinearScale(const Interval &interval, double offset, double gradient) : CartesianScale(ScaleLinear, interval, offset, gradient, 0) { Q_ASSERT(gradient != 0.0); } ~LinearScale() override {} bool map(double *value) const override { *value = *value * m_b + m_a; return true; } bool inverseMap(double *value) const override { if (m_b == 0.0) return false; *value = (*value - m_a) / m_b; return true; } int direction() const override { return m_b < 0 ? -1 : 1; } }; /** * \class CartesianCoordinateSystem::LinearScale * \brief implementation of the linear scale for cartesian coordinate system. */ class LogScale : public CartesianScale { public: LogScale(const Interval &interval, double offset, double scaleFactor, double base) : CartesianScale(ScaleLog, interval, offset, scaleFactor, base) { Q_ASSERT(scaleFactor != 0.0); Q_ASSERT(base > 0.0); } ~LogScale() override {} bool map(double *value) const override { if (*value > 0.0) *value = log(*value)/log(m_c) * m_b + m_a; else return false; return true; } bool inverseMap(double *value) const override { if (m_a == 0.0) return false; if (m_c <= 0.0) return false; *value = pow(m_c, (*value - m_a) / m_b); return true; } int direction() const override { return m_b < 0 ? -1 : 1; } }; /* ============================================================================ */ /* ========================= coordinate system ================================ */ /* ============================================================================ */ class CartesianCoordinateSystemPrivate { public: CartesianCoordinateSystemPrivate(CartesianCoordinateSystem *owner); ~CartesianCoordinateSystemPrivate(); CartesianCoordinateSystem* const q; CartesianPlot* plot; QVector xScales; QVector yScales; }; /** * \class CartesianCoordinateSystem * \brief Cartesian coordinate system for plots. */ CartesianCoordinateSystem::CartesianCoordinateSystem(CartesianPlot* plot) : AbstractCoordinateSystem(plot), d(new CartesianCoordinateSystemPrivate(this)) { d->plot=plot; // TODO: set some standard scales } CartesianCoordinateSystem::~CartesianCoordinateSystem() { delete d; } CartesianScale *CartesianScale::createScale(ScaleType type, const Interval &interval, double a, double b, double c) { switch (type) { case ScaleLinear: return new LinearScale(interval, a, b); case ScaleLog: return new LogScale(interval, a, b, c); default: return NULL; } } CartesianScale *CartesianScale::createLinearScale(const Interval &interval, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) { DEBUG("CartesianScale::createLinearScale() scene start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd); double lDiff = logicalEnd - logicalStart; if (lDiff == 0.0) return NULL; double b = (sceneEnd - sceneStart) / lDiff; double a = sceneStart - b * logicalStart; return new LinearScale(interval, a, b); } CartesianScale *CartesianScale::createLogScale(const Interval &interval, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd, double base) { if (base < 0.0 || base == 0.0) return NULL; if (logicalStart < 0.0 || logicalStart == 0.0) return NULL; if (logicalEnd < 0.0 || logicalEnd == 0.0) return NULL; double lDiff = (log(logicalEnd) - log(logicalStart)) / log(base); if (lDiff == 0.0) return NULL; double b = (sceneEnd - sceneStart) / lDiff; double a = sceneStart - b * log(logicalStart)/log(base); return new LogScale(interval, a, b, base); } //############################################################################## //######################### logical to scene mappers ########################### //############################################################################## QVector CartesianCoordinateSystem::mapLogicalToScene(const QVector &points, const MappingFlags &flags) const { const QRectF pageRect = d->plot->dataRect(); QVector result; bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); for (const auto* xScale : d->xScales) { if (!xScale) continue; for (const auto* yScale : d->yScales) { if (!yScale) continue; for (const auto& point : points) { double x = point.x(); double y = point.y(); if (!xScale->contains(x)) continue; if (!yScale->contains(y)) continue; if (!xScale->map(&x)) continue; if (!yScale->map(&y)) continue; const QPointF mappedPoint(x, y); if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) result.append(mappedPoint); } } } return result; } /*! Maps the points in logical coordinates from \c points and fills the \c visiblePoints with the points in logical coordinates restricted to the current intervals. \param logicalPoints List of points in logical coordinates \param scenePoints List for the points in scene coordinates \param visiblePoints List for the logical coordinates restricted to the current region of the coordinate system \param flags */ void CartesianCoordinateSystem::mapLogicalToScene(const QVector& logicalPoints, QVector& scenePoints, std::vector& visiblePoints, const MappingFlags& flags) const{ const QRectF pageRect = d->plot->dataRect(); - QVector result; const bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); for (const auto* xScale : d->xScales) { if (!xScale) continue; for (const auto* yScale : d->yScales) { if (!yScale) continue; for (int i=0; icontains(x)) continue; if (!xScale->map(&x)) continue; double y = point.y(); if (!yScale->contains(y)) continue; if (!yScale->map(&y)) continue; const QPointF mappedPoint(x, y); if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) { scenePoints.append(mappedPoint); visiblePoints[i].flip(); } } } } } QPointF CartesianCoordinateSystem::mapLogicalToScene(const QPointF& logicalPoint, const MappingFlags& flags) const{ const QRectF pageRect = d->plot->dataRect(); - QVector result; bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); double x = logicalPoint.x(); double y = logicalPoint.y(); for (const auto* xScale : d->xScales) { if (!xScale) continue; for (const auto* yScale : d->yScales) { if (!yScale) continue; if (!xScale->contains(x)) continue; if (!xScale->map(&x)) continue; if (!yScale->contains(y)) continue; if (!yScale->map(&y)) continue; QPointF mappedPoint(x, y); if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) return mappedPoint; } } return QPointF(); } QVector CartesianCoordinateSystem::mapLogicalToScene(const QVector &lines, const MappingFlags &flags) const{ QRectF pageRect = d->plot->dataRect(); QVector result; const bool doPageClipping = !pageRect.isNull() && !(flags & SuppressPageClipping); double xGapBefore = NAN; double xGapAfter = NAN; double yGapBefore = NAN; double yGapAfter = NAN; QVectorIterator xIterator(d->xScales); while (xIterator.hasNext()) { const CartesianScale* xScale = xIterator.next(); if (!xScale) continue; xGapBefore = xGapAfter; if (xIterator.hasNext()) { const CartesianScale* nextXScale = xIterator.peekNext(); if (!nextXScale) continue; Interval nextXInterval; nextXScale->getProperties(NULL, &nextXInterval); double x1 = xScale->end(); double x2 = nextXScale->start(); bool valid = xScale->map(&x1); if (valid) valid = nextXScale->map(&x2); if (valid) xGapAfter = x2 - x1; else xGapAfter = NAN; } else xGapAfter = NAN; QVectorIterator yIterator(d->yScales); while (yIterator.hasNext()) { const CartesianScale* yScale = yIterator.next(); if (!yScale) continue; yGapBefore = yGapAfter; if (yIterator.hasNext()) { const CartesianScale* nextYScale = yIterator.peekNext(); if (!nextYScale) continue; double y1 = yScale->end(); double y2 = nextYScale->start(); bool valid = yScale->map(&y1); if (valid) valid = nextYScale->map(&y2); if (valid) yGapAfter = y2 - y1; else yGapAfter = NAN; } else yGapAfter = NAN; const QRectF scaleRect = QRectF(xScale->start(), yScale->start(), xScale->end() - xScale->start(), yScale->end() - yScale->start()).normalized(); for (auto line : lines) { LineClipResult clipResult; if (!AbstractCoordinateSystem::clipLineToRect(&line, scaleRect, &clipResult)) continue; double x1 = line.x1(); if (!xScale->map(&x1)) continue; double x2 = line.x2(); if (!xScale->map(&x2)) continue; double y1 = line.y1(); if (!yScale->map(&y1)) continue; double y2 = line.y2(); if (!yScale->map(&y2)) continue; if (flags & MarkGaps) { //mark the end of the gap if (!std::isnan(xGapBefore)) { if (clipResult.xClippedLeft[0]) { QLineF gapMarker(x1 + xGapBefore/4, y1 - xGapBefore/2, x1 - xGapBefore/4, y1 + xGapBefore/2); // if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } if (clipResult.xClippedLeft[1]) { QLineF gapMarker(x2 + xGapBefore/4, y2 - xGapBefore/2, x2 - xGapBefore/4, y2 + xGapBefore/2); // if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } } //mark the beginning of the gap if (!std::isnan(xGapAfter)) { if (clipResult.xClippedRight[0]) { QLineF gapMarker(x1 + xGapAfter/4, y1 - xGapAfter/2, x1 - xGapAfter/4, y1 + xGapAfter/2); // if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } if (clipResult.xClippedRight[1]) { QLineF gapMarker(x2 + xGapAfter/4, y2 - xGapAfter/2, x2 - xGapAfter/4, y2 + xGapAfter/2); // if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } } if (!std::isnan(yGapBefore)) { if (clipResult.yClippedTop[0]) { QLineF gapMarker(x1 + yGapBefore/2, y1 - yGapBefore/4, x1 - yGapBefore/2, y1 + yGapBefore/4); // if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } if (clipResult.yClippedTop[1]) { QLineF gapMarker(x2 + yGapBefore/2, y2 - yGapBefore/4, x2 - yGapBefore/2, y2 + yGapBefore/4); // if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } } if (!std::isnan(yGapAfter)) { if (clipResult.yClippedBottom[0]) { QLineF gapMarker(QPointF(x1 + yGapAfter / 2, y1 - yGapAfter / 4), QPointF(x1 - yGapAfter / 2, y1 + yGapAfter / 4)); if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } if (clipResult.yClippedBottom[1]) { QLineF gapMarker(QPointF(x2 + yGapAfter / 2, y2 - yGapAfter / 4), QPointF(x2 - yGapAfter / 2, y2 + yGapAfter / 4)); if (AbstractCoordinateSystem::clipLineToRect(&gapMarker, pageRect)) result.append(gapMarker); } } } QLineF mappedLine(QPointF(x1, y1), QPointF(x2, y2)); if (doPageClipping) { if (!AbstractCoordinateSystem::clipLineToRect(&mappedLine, pageRect)) continue; } result.append(mappedLine); } } } return result; } //############################################################################## //######################### scene to logical mappers ########################### //############################################################################## QVector CartesianCoordinateSystem::mapSceneToLogical(const QVector &points, const MappingFlags &flags) const{ QRectF pageRect = d->plot->dataRect(); QVector result; bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); for (const auto& point : points) { if (noPageClipping || pageRect.contains(point)) { bool found = false; double x = point.x(); double y = point.y(); for (const auto* xScale : d->xScales) { if (found) break; if (!xScale) continue; for (const auto* yScale : d->yScales) { if (found) break; if (!yScale) continue; if (!xScale->inverseMap(&x)) { x = point.x(); continue; } if (!yScale->inverseMap(&y)) { y = point.y(); continue; } if (!xScale->contains(x)) { x = point.x(); continue; } if (!yScale->contains(y)) { y = point.y(); continue; } result.append(QPointF(x, y)); found = true; } } } } return result; } QPointF CartesianCoordinateSystem::mapSceneToLogical(const QPointF& logicalPoint, const MappingFlags& flags) const { QRectF pageRect = d->plot->dataRect(); QPointF result; bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); if (noPageClipping || pageRect.contains(logicalPoint)) { double x = logicalPoint.x(); double y = logicalPoint.y(); for (const auto* xScale : d->xScales) { if (!xScale) continue; for (const auto* yScale : d->yScales) { if (!yScale) continue; if (!xScale->inverseMap(&x)) continue; if (!yScale->inverseMap(&y)) continue; if (!xScale->contains(x)) continue; if (!yScale->contains(y)) continue; result.setX(x); result.setY(y); return result; } } } return result; } /** * \brief Determine the horizontal direction relative to the page. * * This function is needed for untransformed lengths such as axis tick length. * \return 1 or -1 */ int CartesianCoordinateSystem::xDirection() const { if (d->xScales.isEmpty()) return 1; return d->xScales.at(0)->direction(); } /** * \brief Determine the vertical direction relative to the page. * * This function is needed for untransformed lengths such as axis tick length. * \return 1 or -1 */ int CartesianCoordinateSystem::yDirection() const{ if (d->yScales.isEmpty()) return 1; return d->yScales.at(0)->direction(); } // TODO: design elegant, flexible and undo-aware API for changing scales bool CartesianCoordinateSystem::setXScales(const QVector &scales) { while (!d->xScales.isEmpty()) delete d->xScales.takeFirst(); d->xScales = scales; return true; // TODO: check scales validity } QVector CartesianCoordinateSystem::xScales() const { return d->xScales; // TODO: should rather return a copy of the scales here } bool CartesianCoordinateSystem::setYScales(const QVector &scales) { while (!d->yScales.isEmpty()) delete d->yScales.takeFirst(); d->yScales = scales; return true; // TODO: check scales validity } QVector CartesianCoordinateSystem::yScales() const { return d->yScales; // TODO: should rather return a copy of the scales here } /*! * Adjusted the function QRectF::contains(QPointF) from Qt 4.8.4 to handle the * comparison of float numbers correctly. * TODO: check whether the newer versions of Qt do the comparison correctly. */ bool CartesianCoordinateSystem::rectContainsPoint(const QRectF& rect, const QPointF& point) const{ qreal l = rect.x(); qreal r = rect.x(); qreal w = rect.width(); qreal h = rect.height(); if (w < 0) l += w; else r += w; if ( AbstractCoordinateSystem::essentiallyEqual(l, r)) // null rect return false; if ( AbstractCoordinateSystem::definitelyLessThan(point.x(), l) || AbstractCoordinateSystem::definitelyGreaterThan(point.x(), r) ) return false; qreal t = rect.y(); qreal b = rect.y(); if (h < 0) t += h; else b += h; if ( AbstractCoordinateSystem::essentiallyEqual(t, b) ) // null rect return false; if ( AbstractCoordinateSystem::definitelyLessThan(point.y(), t) || AbstractCoordinateSystem::definitelyGreaterThan(point.y(), b) ) return false; return true; } //############################################################################## //######################### Private implementation ############################# //############################################################################## CartesianCoordinateSystemPrivate::CartesianCoordinateSystemPrivate(CartesianCoordinateSystem *owner) :q(owner), plot(0) { } CartesianCoordinateSystemPrivate::~CartesianCoordinateSystemPrivate() { while (!xScales.isEmpty()) delete xScales.takeFirst(); while (!yScales.isEmpty()) delete yScales.takeFirst(); } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp index 0662a8116..1f895abd4 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp @@ -1,1148 +1,1148 @@ /*************************************************************************** File : CartesianPlotLegend.cpp Project : LabPlot Description : Legend for the cartesian plot -------------------------------------------------------------------- Copyright : (C) 2013-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class CartesianPlotLegend \brief Legend for the cartesian plot. \ingroup kdefrontend */ #include "CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegendPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include #include #include #include #include #include #include CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name) : WorksheetElement(name), d_ptr(new CartesianPlotLegendPrivate(this)), m_plot(plot) { init(); } CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name, CartesianPlotLegendPrivate *dd) : WorksheetElement(name), d_ptr(dd), m_plot(plot) { init(); } CartesianPlotLegend::~CartesianPlotLegend() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void CartesianPlotLegend::init() { Q_D(CartesianPlotLegend); KConfig config; KConfigGroup group = config.group( "CartesianPlotLegend" ); d->labelFont = group.readEntry("LabelsFont", QFont()); d->labelFont.setPixelSize( Worksheet::convertToSceneUnits( 10, Worksheet::Point ) ); d->labelColor = Qt::black; d->labelColumnMajor = true; d->lineSymbolWidth = group.readEntry("LineSymbolWidth", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)); d->rowCount = 0; d->columnCount = 0; d->position.horizontalPosition = CartesianPlotLegend::hPositionRight; d->position.verticalPosition = CartesianPlotLegend::vPositionBottom; //Title d->title = new TextLabel(this->name(), TextLabel::PlotLegendTitle); addChild(d->title); d->title->setHidden(true); d->title->setParentGraphicsItem(graphicsItem()); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); connect(d->title, &TextLabel::changed, this, &CartesianPlotLegend::retransform); //Background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int) PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //Border d->borderPen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)), (Qt::PenStyle) group.readEntry("BorderStyle", (int)Qt::SolidLine)); d->borderCornerRadius = group.readEntry("BorderCornerRadius", 0.0); d->borderOpacity = group.readEntry("BorderOpacity", 1.0); //Layout d->layoutTopMargin = group.readEntry("LayoutTopMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutRightMargin = group.readEntry("LayoutRightMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Centimeter)); d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Centimeter)); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 1); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges); this->initActions(); } void CartesianPlotLegend::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlotLegend::visibilityChanged); } QMenu* CartesianPlotLegend::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlotLegend::icon() const{ return QIcon::fromTheme("text-field"); } STD_SWAP_METHOD_SETTER_CMD_IMPL(CartesianPlotLegend, SetVisible, bool, swapVisible) void CartesianPlotLegend::setVisible(bool on) { Q_D(CartesianPlotLegend); exec(new CartesianPlotLegendSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); } bool CartesianPlotLegend::isVisible() const{ Q_D(const CartesianPlotLegend); return d->isVisible(); } void CartesianPlotLegend::setPrinting(bool on) { Q_D(CartesianPlotLegend); d->m_printing = on; } QGraphicsItem *CartesianPlotLegend::graphicsItem() const{ return d_ptr; } void CartesianPlotLegend::retransform() { d_ptr->retransform(); } void CartesianPlotLegend::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); //TODO // Q_D(const CartesianPlotLegend); } //############################################################################## //################################ getter methods ############################ //############################################################################## CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QFont, labelFont, labelFont) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, labelColor, labelColor) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, bool, labelColumnMajor, labelColumnMajor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, CartesianPlotLegend::PositionWrapper, position, position) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, lineSymbolWidth, lineSymbolWidth) //Title TextLabel* CartesianPlotLegend::title() { return d_ptr->title; } //Background BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QString, backgroundFileName, backgroundFileName) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, backgroundOpacity, backgroundOpacity) //Border CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderCornerRadius, borderCornerRadius) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderOpacity, borderOpacity) //Layout BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutTopMargin, layoutTopMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutBottomMargin, layoutBottomMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutLeftMargin, layoutLeftMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutRightMargin, layoutRightMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, int, layoutColumnCount, layoutColumnCount) //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelFont, QFont, labelFont, retransform) void CartesianPlotLegend::setLabelFont(const QFont& font) { Q_D(CartesianPlotLegend); if (font!= d->labelFont) exec(new CartesianPlotLegendSetLabelFontCmd(d, font, i18n("%1: set font"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColor, QColor, labelColor, update) void CartesianPlotLegend::setLabelColor(const QColor& color) { Q_D(CartesianPlotLegend); if (color!= d->labelColor) exec(new CartesianPlotLegendSetLabelColorCmd(d, color, i18n("%1: set font color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColumnMajor, bool, labelColumnMajor, retransform) void CartesianPlotLegend::setLabelColumnMajor(bool columnMajor) { Q_D(CartesianPlotLegend); if (columnMajor != d->labelColumnMajor) exec(new CartesianPlotLegendSetLabelColumnMajorCmd(d, columnMajor, i18n("%1: change column order"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLineSymbolWidth, float, lineSymbolWidth, retransform) void CartesianPlotLegend::setLineSymbolWidth(float width) { Q_D(CartesianPlotLegend); if (width != d->lineSymbolWidth) exec(new CartesianPlotLegendSetLineSymbolWidthCmd(d, width, i18n("%1: change line+symbol width"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetPosition, CartesianPlotLegend::PositionWrapper, position, updatePosition); void CartesianPlotLegend::setPosition(const PositionWrapper& pos) { Q_D(CartesianPlotLegend); if (pos.point!=d->position.point || pos.horizontalPosition!=d->position.horizontalPosition || pos.verticalPosition!=d->position.verticalPosition) exec(new CartesianPlotLegendSetPositionCmd(d, pos, i18n("%1: set position"))); } //Background STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void CartesianPlotLegend::setBackgroundType(PlotArea::BackgroundType type) { Q_D(CartesianPlotLegend); if (type != d->backgroundType) exec(new CartesianPlotLegendSetBackgroundTypeCmd(d, type, i18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void CartesianPlotLegend::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundColorStyle) exec(new CartesianPlotLegendSetBackgroundColorStyleCmd(d, style, i18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void CartesianPlotLegend::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundImageStyle) exec(new CartesianPlotLegendSetBackgroundImageStyleCmd(d, style, i18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void CartesianPlotLegend::setBackgroundBrushStyle(Qt::BrushStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundBrushStyle) exec(new CartesianPlotLegendSetBackgroundBrushStyleCmd(d, style, i18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void CartesianPlotLegend::setBackgroundFirstColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundFirstColor) exec(new CartesianPlotLegendSetBackgroundFirstColorCmd(d, color, i18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void CartesianPlotLegend::setBackgroundSecondColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundSecondColor) exec(new CartesianPlotLegendSetBackgroundSecondColorCmd(d, color, i18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFileName, QString, backgroundFileName, update) void CartesianPlotLegend::setBackgroundFileName(const QString& fileName) { Q_D(CartesianPlotLegend); if (fileName!= d->backgroundFileName) exec(new CartesianPlotLegendSetBackgroundFileNameCmd(d, fileName, i18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundOpacity, float, backgroundOpacity, update) void CartesianPlotLegend::setBackgroundOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->backgroundOpacity) exec(new CartesianPlotLegendSetBackgroundOpacityCmd(d, opacity, i18n("%1: set opacity"))); } //Border STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderPen, QPen, borderPen, update) void CartesianPlotLegend::setBorderPen(const QPen &pen) { Q_D(CartesianPlotLegend); if (pen != d->borderPen) exec(new CartesianPlotLegendSetBorderPenCmd(d, pen, i18n("%1: set border style"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderCornerRadius, qreal, borderCornerRadius, update) void CartesianPlotLegend::setBorderCornerRadius(float radius) { Q_D(CartesianPlotLegend); if (radius != d->borderCornerRadius) exec(new CartesianPlotLegendSetBorderCornerRadiusCmd(d, radius, i18n("%1: set border corner radius"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderOpacity, qreal, borderOpacity, update) void CartesianPlotLegend::setBorderOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->borderOpacity) exec(new CartesianPlotLegendSetBorderOpacityCmd(d, opacity, i18n("%1: set border opacity"))); } //Layout STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutTopMargin, float, layoutTopMargin, retransform) void CartesianPlotLegend::setLayoutTopMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutTopMargin) exec(new CartesianPlotLegendSetLayoutTopMarginCmd(d, margin, i18n("%1: set layout top margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutBottomMargin, float, layoutBottomMargin, retransform) void CartesianPlotLegend::setLayoutBottomMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutBottomMargin) exec(new CartesianPlotLegendSetLayoutBottomMarginCmd(d, margin, i18n("%1: set layout bottom margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutLeftMargin, float, layoutLeftMargin, retransform) void CartesianPlotLegend::setLayoutLeftMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutLeftMargin) exec(new CartesianPlotLegendSetLayoutLeftMarginCmd(d, margin, i18n("%1: set layout left margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutRightMargin, float, layoutRightMargin, retransform) void CartesianPlotLegend::setLayoutRightMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutRightMargin) exec(new CartesianPlotLegendSetLayoutRightMarginCmd(d, margin, i18n("%1: set layout right margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, retransform) void CartesianPlotLegend::setLayoutVerticalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutVerticalSpacing) exec(new CartesianPlotLegendSetLayoutVerticalSpacingCmd(d, spacing, i18n("%1: set layout vertical spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, retransform) void CartesianPlotLegend::setLayoutHorizontalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutHorizontalSpacing) exec(new CartesianPlotLegendSetLayoutHorizontalSpacingCmd(d, spacing, i18n("%1: set layout horizontal spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutColumnCount, int, layoutColumnCount, retransform) void CartesianPlotLegend::setLayoutColumnCount(int count) { Q_D(CartesianPlotLegend); if (count != d->layoutColumnCount) exec(new CartesianPlotLegendSetLayoutColumnCountCmd(d, count, i18n("%1: set layout column count"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlotLegend::visibilityChangedSlot() { Q_D(const CartesianPlotLegend); this->setVisible(!d->isVisible()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## CartesianPlotLegendPrivate::CartesianPlotLegendPrivate(CartesianPlotLegend *owner):q(owner), suppressItemChangeEvent(false), suppressRetransform(false), m_printing(false), m_hovered(false) { setAcceptHoverEvents(true); } QString CartesianPlotLegendPrivate::name() const { return q->name(); } QRectF CartesianPlotLegendPrivate::boundingRect() const { return rect; } void CartesianPlotLegendPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } /*! Returns the shape of the CartesianPlotLegend as a QPainterPath in local coordinates */ QPainterPath CartesianPlotLegendPrivate::shape() const { QPainterPath path; if ( qFuzzyIsNull(borderCornerRadius) ) path.addRect(rect); else path.addRoundedRect(rect, borderCornerRadius, borderCornerRadius); return path; } bool CartesianPlotLegendPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the rectangular of the legend. */ void CartesianPlotLegendPrivate::retransform() { if (suppressRetransform) return; prepareGeometryChange(); curvesList.clear(); for (auto* curve : q->m_plot->children()) { if (curve && curve->isVisible()) curvesList.push_back(curve); } int curveCount = curvesList.size(); columnCount = (curveCount= curveCount ) break; curve = curvesList.at(index); if (curve) { if (!curve->isVisible()) continue; w = fm.width(curve->name()); if (w>maxTextWidth) maxTextWidth = w; } } maxColumnTextWidths.append(maxTextWidth); legendWidth += maxTextWidth; } legendWidth += layoutLeftMargin + layoutRightMargin; //margins legendWidth += columnCount*lineSymbolWidth + layoutHorizontalSpacing; //width of the columns without the text legendWidth += (columnCount-1)*2*layoutHorizontalSpacing; //spacings between the columns if (title->isVisible() && !title->text().text.isEmpty()) { float titleWidth = title->graphicsItem()->boundingRect().width(); if (titleWidth > legendWidth) legendWidth = titleWidth; } //determine the height of the legend float legendHeight = layoutTopMargin + layoutBottomMargin; //margins legendHeight += rowCount*h; //height of the rows legendHeight += (rowCount-1)*layoutVerticalSpacing; //spacing between the rows if (title->isVisible() && !title->text().text.isEmpty()) legendHeight += title->graphicsItem()->boundingRect().height(); //legend title rect.setX(-legendWidth/2); rect.setY(-legendHeight/2); rect.setWidth(legendWidth); rect.setHeight(legendHeight); updatePosition(); } /*! calculates the position of the legend, when the position relative to the parent was specified (left, right, etc.) */ void CartesianPlotLegendPrivate::updatePosition() { //position the legend relative to the actual plot size minus small offset //TODO: make the offset dependent on the size of axis ticks. const QRectF parentRect = q->m_plot->dataRect(); float hOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); float vOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); if (position.horizontalPosition != CartesianPlotLegend::hPositionCustom) { if (position.horizontalPosition == CartesianPlotLegend::hPositionLeft) position.point.setX(parentRect.x() + rect.width()/2 + hOffset); else if (position.horizontalPosition == CartesianPlotLegend::hPositionCenter) position.point.setX(parentRect.x() + parentRect.width()/2); else if (position.horizontalPosition == CartesianPlotLegend::hPositionRight) position.point.setX(parentRect.x() + parentRect.width() - rect.width()/2 - hOffset); } if (position.verticalPosition != CartesianPlotLegend::vPositionCustom) { if (position.verticalPosition == CartesianPlotLegend::vPositionTop) position.point.setY(parentRect.y() + rect.height()/2 + vOffset); else if (position.verticalPosition == CartesianPlotLegend::vPositionCenter) position.point.setY(parentRect.y() + parentRect.height()/2); else if (position.verticalPosition == CartesianPlotLegend::vPositionBottom) position.point.setY(parentRect.y() + parentRect.height() - rect.height()/2 -vOffset); } suppressItemChangeEvent=true; setPos(position.point); suppressItemChangeEvent=false; emit q->positionChanged(position); suppressRetransform = true; title->retransform(); suppressRetransform = false; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the legend. \sa QGraphicsItem::paint(). */ void CartesianPlotLegendPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->save(); //draw the area painter->setOpacity(backgroundOpacity); painter->setPen(Qt::NoPen); if (backgroundType == PlotArea::Color) { switch (backgroundColorStyle) { case PlotArea::SingleColor:{ painter->setBrush(QBrush(backgroundFirstColor)); break; } case PlotArea::HorizontalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient:{ QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, backgroundFirstColor); radialGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (backgroundType == PlotArea::Image) { if ( !backgroundFileName.trimmed().isEmpty() ) { QPixmap pix(backgroundFileName); switch (backgroundImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Centered: painter->drawPixmap(QPointF(rect.center().x()-pix.size().width()/2,rect.center().y()-pix.size().height()/2),pix); break; case PlotArea::Tiled: painter->drawTiledPixmap(rect,pix); break; case PlotArea::CenterTiled: painter->drawTiledPixmap(rect,pix,QPoint(rect.size().width()/2,rect.size().height()/2)); } } } else if (backgroundType == PlotArea::Pattern) { painter->setBrush(QBrush(backgroundFirstColor,backgroundBrushStyle)); } if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); //draw the border if (borderPen.style() != Qt::NoPen) { painter->setPen(borderPen); painter->setBrush(Qt::NoBrush); painter->setOpacity(borderOpacity); if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } //draw curve's line+symbol and the names int curveCount = curvesList.size(); QFontMetrics fm(labelFont); float h=fm.ascent(); XYCurve* curve; painter->setFont(labelFont); //translate to left upper conner of the bounding rect plus the layout offset and the height of the title painter->translate(-rect.width()/2+layoutLeftMargin, -rect.height()/2+layoutTopMargin); if (title->isVisible() && !title->text().text.isEmpty()) painter->translate(0, title->graphicsItem()->boundingRect().height()); painter->save(); int index; for (int c=0; c= curveCount ) break; curve = curvesList.at(index); //curve's line (painted at the half of the ascent size) if (curve->lineType() != XYCurve::NoLine) { painter->setPen(curve->linePen()); painter->setOpacity(curve->lineOpacity()); painter->drawLine(0, h/2, lineSymbolWidth, h/2); } //error bars if ( (curve->xErrorType() != XYCurve::NoError && curve->xErrorPlusColumn()) || (curve->yErrorType() != XYCurve::NoError && curve->yErrorPlusColumn()) ) { painter->setOpacity(curve->errorBarsOpacity()); painter->setPen(curve->errorBarsPen()); //curve's error bars for x float errorBarsSize = Worksheet::convertToSceneUnits(10, Worksheet::Point); if (curve->symbolsStyle()!=Symbol::NoSymbols && errorBarsSizesymbolsSize()*1.4) errorBarsSize = curve->symbolsSize()*1.4; switch (curve->errorBarsType()) { case XYCurve::ErrorBarsSimple: //horiz. line if (curve->xErrorType() != XYCurve::NoError) painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //vert. line if (curve->yErrorType() != XYCurve::NoError) painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); break; case XYCurve::ErrorBarsWithEnds: //horiz. line if (curve->xErrorType() != XYCurve::NoError) { painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //caps for the horiz. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2-errorBarsSize/2, h/2+errorBarsSize/4); painter->drawLine(lineSymbolWidth/2+errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2+errorBarsSize/2, h/2+errorBarsSize/4); } //vert. line if (curve->yErrorType() != XYCurve::NoError) { painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); //caps for the vert. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2-errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2-errorBarsSize/2); painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2+errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2+errorBarsSize/2); } break; } } //curve's symbol if (curve->symbolsStyle()!=Symbol::NoSymbols) { painter->setOpacity(curve->symbolsOpacity()); painter->setBrush(curve->symbolsBrush()); painter->setPen(curve->symbolsPen()); QPainterPath path = Symbol::pathFromStyle(curve->symbolsStyle()); QTransform trafo; trafo.scale(curve->symbolsSize(), curve->symbolsSize()); path = trafo.map(path); if (curve->symbolsRotationAngle() != 0) { trafo.reset(); trafo.rotate(curve->symbolsRotationAngle()); path = trafo.map(path); } painter->translate(QPointF(lineSymbolWidth/2, h/2)); painter->drawPath(path); painter->translate(-QPointF(lineSymbolWidth/2, h/2)); } //curve's name painter->setPen(QPen(labelColor)); painter->setOpacity(1.0); //TODO: support HTML text? painter->drawText(QPoint(lineSymbolWidth+layoutHorizontalSpacing, h), curve->name()); painter->translate(0,layoutVerticalSpacing+h); } //translate to the beginning of the next column painter->restore(); int deltaX = lineSymbolWidth+layoutHorizontalSpacing+maxColumnTextWidths.at(c); //the width of the current columns deltaX += 2*layoutHorizontalSpacing; //spacing between two columns painter->translate(deltaX,0); painter->save(); } painter->restore(); painter->restore(); if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(shape()); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(shape()); } } QVariant CartesianPlotLegendPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates CartesianPlotLegend::PositionWrapper tempPosition; tempPosition.point = value.toPointF(); tempPosition.horizontalPosition = CartesianPlotLegend::hPositionCustom; tempPosition.verticalPosition = CartesianPlotLegend::vPositionCustom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void CartesianPlotLegendPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = pos(); if (point!=position.point) { //position was changed -> set the position related member variables suppressRetransform = true; CartesianPlotLegend::PositionWrapper tempPosition; tempPosition.point = point; tempPosition.horizontalPosition = CartesianPlotLegend::hPositionCustom; tempPosition.verticalPosition = CartesianPlotLegend::vPositionCustom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } void CartesianPlotLegendPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; - q->hovered(); + emit q->hovered(); update(); } } void CartesianPlotLegendPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; - q->unhovered(); + emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlotLegend::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlotLegend); writer->writeStartElement( "cartesianPlotLegend" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_QCOLOR(d->labelColor); WRITE_QFONT(d->labelFont); writer->writeAttribute( "columnMajor", QString::number(d->labelColumnMajor) ); writer->writeAttribute( "lineSymbolWidth", QString::number(d->lineSymbolWidth) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); writer->writeEndElement(); //title d->title->save(writer); //background writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); //border writer->writeStartElement( "border" ); WRITE_QPEN(d->borderPen); writer->writeAttribute( "borderOpacity", QString::number(d->borderOpacity) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); writer->writeAttribute( "topMargin", QString::number(d->layoutTopMargin) ); writer->writeAttribute( "bottomMargin", QString::number(d->layoutBottomMargin) ); writer->writeAttribute( "leftMargin", QString::number(d->layoutLeftMargin) ); writer->writeAttribute( "rightMargin", QString::number(d->layoutRightMargin) ); writer->writeAttribute( "verticalSpacing", QString::number(d->layoutVerticalSpacing) ); writer->writeAttribute( "horizontalSpacing", QString::number(d->layoutHorizontalSpacing) ); writer->writeAttribute( "columnCount", QString::number(d->layoutColumnCount) ); writer->writeEndElement(); writer->writeEndElement(); // close "cartesianPlotLegend" section } //! Load from XML bool CartesianPlotLegend::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlotLegend); if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlotLegend") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_QCOLOR(d->labelColor); READ_QFONT(d->labelFont); str = attribs.value("columnMajor").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'columnMajor'")); else d->labelColumnMajor = str.toInt(); str = attribs.value("lineSymbolWidth").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'lineSymbolWidth'")); else d->lineSymbolWidth = str.toDouble(); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.point.setY(str.toDouble()); str = attribs.value("horizontalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPosition'")); else d->position.horizontalPosition = (CartesianPlotLegend::HorizontalPosition)str.toInt(); str = attribs.value("verticalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPosition'")); else d->position.verticalPosition = (CartesianPlotLegend::VerticalPosition)str.toInt(); } else if (reader->name() == "textLabel") { if (!d->title->load(reader, preview)) { delete d->title; d->title=0; return false; } } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("type")); else d->backgroundType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("colorStyle")); else d->backgroundColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("imageStyle")); else d->backgroundImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("brushStyle")); else d->backgroundBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("opacity")); else d->backgroundOpacity = str.toDouble(); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_QPEN(d->borderPen); str = attribs.value("borderOpacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("borderOpacity")); else d->borderOpacity = str.toDouble(); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); str = attribs.value("topMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("topMargin")); else d->layoutTopMargin = str.toDouble(); str = attribs.value("bottomMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("bottomMargin")); else d->layoutBottomMargin = str.toDouble(); str = attribs.value("leftMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("leftMargin")); else d->layoutLeftMargin = str.toDouble(); str = attribs.value("rightMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("rightMargin")); else d->layoutRightMargin = str.toDouble(); str = attribs.value("verticalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("verticalSpacing")); else d->layoutVerticalSpacing = str.toDouble(); str = attribs.value("horizontalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("horizontalSpacing")); else d->layoutHorizontalSpacing = str.toDouble(); str = attribs.value("columnCount").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("columnCount")); else d->layoutColumnCount = str.toInt(); } } return true; } void CartesianPlotLegend::loadThemeConfig(const KConfig& config) { KConfigGroup groupLabel = config.group("Label"); this->setLabelColor(groupLabel.readEntry("FontColor", QColor(Qt::white))); const KConfigGroup group = config.group("CartesianPlot"); this->setBackgroundBrushStyle((Qt::BrushStyle)group.readEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle())); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle)(group.readEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()))); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor())); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle)group.readEntry("BackgroundImageStyle",(int) this->backgroundImageStyle())); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", this->backgroundOpacity())); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor())); this->setBackgroundType((PlotArea::BackgroundType)(group.readEntry("BackgroundType",(int) this->backgroundType()))); this->borderPen().setColor(group.readEntry("BorderColor",(QColor) this->borderPen().color())); this->setBorderCornerRadius(group.readEntry("BorderCornerRadius", this->borderCornerRadius())); this->setBorderOpacity(group.readEntry("BorderOpacity", this->borderOpacity())); this->borderPen().setStyle((Qt::PenStyle)(group.readEntry("BorderStyle", (int) this->borderPen().style()))); this->borderPen().setWidthF(group.readEntry("BorderWidth", this->borderPen().widthF())); title()->loadThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/CustomPoint.cpp b/src/backend/worksheet/plots/cartesian/CustomPoint.cpp index 75623f67e..5a6138195 100644 --- a/src/backend/worksheet/plots/cartesian/CustomPoint.cpp +++ b/src/backend/worksheet/plots/cartesian/CustomPoint.cpp @@ -1,493 +1,493 @@ /*************************************************************************** File : CustomPoint.cpp Project : LabPlot Description : Custom user-defined point on the plot -------------------------------------------------------------------- Copyright : (C) 2015 Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CustomPoint.h" #include "CustomPointPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include /** * \class CustomPoint * \brief A customizable point. * * The position can be either specified by mouse events or by providing the * x- and y- coordinates in parent's coordinate system */ CustomPoint::CustomPoint(const CartesianPlot* plot, const QString& name):WorksheetElement(name), d_ptr(new CustomPointPrivate(this,plot)) { init(); } CustomPoint::CustomPoint(const QString& name, CustomPointPrivate* dd):WorksheetElement(name), d_ptr(dd) { init(); } CustomPoint::~CustomPoint() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void CustomPoint::init() { Q_D(CustomPoint); KConfig config; KConfigGroup group; group = config.group("CustomPoint"); d->position.setX( group.readEntry("PositionXValue", d->plot->xMin() + (d->plot->xMax()-d->plot->xMin())/2) ); d->position.setY( group.readEntry("PositionYValue", d->plot->yMin() + (d->plot->yMax()-d->plot->yMin())/2) ); d->symbolStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::Circle); d->symbolSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->symbolRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::red)) ); d->symbolPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); d->symbolPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); this->initActions(); retransform(); } void CustomPoint::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CustomPoint::visibilityChanged); } /*! Returns an icon to be used in the project explorer. */ QIcon CustomPoint::icon() const { return QIcon::fromTheme("draw-cross"); } QMenu* CustomPoint::createContextMenu() { QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } QGraphicsItem* CustomPoint::graphicsItem() const { return d_ptr; } void CustomPoint::retransform() { Q_D(CustomPoint); d->retransform(); } void CustomPoint::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(CustomPoint, QPointF, position, position) //symbols BASIC_SHARED_D_READER_IMPL(CustomPoint, Symbol::Style, symbolStyle, symbolStyle) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolOpacity, symbolOpacity) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolRotationAngle, symbolRotationAngle) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolSize, symbolSize) CLASS_SHARED_D_READER_IMPL(CustomPoint, QBrush, symbolBrush, symbolBrush) CLASS_SHARED_D_READER_IMPL(CustomPoint, QPen, symbolPen, symbolPen) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetPosition, QPointF, position, retransform) void CustomPoint::setPosition(const QPointF& position) { Q_D(CustomPoint); if (position != d->position) exec(new CustomPointSetPositionCmd(d, position, i18n("%1: set position"))); } //Symbol STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolStyle, Symbol::Style, symbolStyle, retransform) void CustomPoint::setSymbolStyle(Symbol::Style style) { Q_D(CustomPoint); if (style != d->symbolStyle) exec(new CustomPointSetSymbolStyleCmd(d, style, i18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolSize, qreal, symbolSize, retransform) void CustomPoint::setSymbolSize(qreal size) { Q_D(CustomPoint); if (!qFuzzyCompare(1 + size, 1 + d->symbolSize)) exec(new CustomPointSetSymbolSizeCmd(d, size, i18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolRotationAngle, qreal, symbolRotationAngle, retransform) void CustomPoint::setSymbolRotationAngle(qreal angle) { Q_D(CustomPoint); if (!qFuzzyCompare(1 + angle, 1 + d->symbolRotationAngle)) exec(new CustomPointSetSymbolRotationAngleCmd(d, angle, i18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolBrush, QBrush, symbolBrush, update) void CustomPoint::setSymbolBrush(const QBrush &brush) { Q_D(CustomPoint); if (brush != d->symbolBrush) exec(new CustomPointSetSymbolBrushCmd(d, brush, i18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolPen, QPen, symbolPen, update) void CustomPoint::setSymbolPen(const QPen &pen) { Q_D(CustomPoint); if (pen != d->symbolPen) exec(new CustomPointSetSymbolPenCmd(d, pen, i18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolOpacity, qreal, symbolOpacity, update) void CustomPoint::setSymbolOpacity(qreal opacity) { Q_D(CustomPoint); if (opacity != d->symbolOpacity) exec(new CustomPointSetSymbolOpacityCmd(d, opacity, i18n("%1: set symbol opacity"))); } STD_SWAP_METHOD_SETTER_CMD_IMPL_F(CustomPoint, SetVisible, bool, swapVisible, retransform); void CustomPoint::setVisible(bool on) { Q_D(CustomPoint); exec(new CustomPointSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); } bool CustomPoint::isVisible() const { Q_D(const CustomPoint); return d->isVisible(); } void CustomPoint::setPrinting(bool on) { Q_D(CustomPoint); d->m_printing = on; } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CustomPoint::visibilityChanged() { Q_D(const CustomPoint); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## CustomPointPrivate::CustomPointPrivate(CustomPoint* owner, const CartesianPlot* p) : plot(p), suppressItemChangeEvent(false), suppressRetransform(false), m_printing(false), m_hovered(false), m_visible(true), q(owner) { setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); } QString CustomPointPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the item/point. Called on geometry or properties changes. */ void CustomPointPrivate::retransform() { if (suppressRetransform) return; //calculate the point in the scene coordinates const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); QVector list, listScene; list<mapLogicalToScene(list, CartesianCoordinateSystem::DefaultMapping); if (!listScene.isEmpty()) { m_visible = true; positionScene = listScene.at(0); suppressItemChangeEvent=true; setPos(positionScene); suppressItemChangeEvent=false; } else { m_visible = false; } recalcShapeAndBoundingRect(); } bool CustomPointPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF CustomPointPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath CustomPointPrivate::shape() const { return pointShape; } /*! recalculates the outer bounds and the shape of the item. */ void CustomPointPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); pointShape = QPainterPath(); if (m_visible && symbolStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolStyle); QTransform trafo; trafo.scale(symbolSize, symbolSize); path = trafo.map(path); trafo.reset(); if (symbolRotationAngle != 0) { trafo.rotate(symbolRotationAngle); path = trafo.map(path); } pointShape = trafo.map(path); transformedBoundingRectangle = pointShape.boundingRect(); } } void CustomPointPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!m_visible) return; if (symbolStyle != Symbol::NoSymbols) { painter->setOpacity(symbolOpacity); painter->setPen(symbolPen); painter->setBrush(symbolBrush); painter->drawPath(pointShape); } if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(pointShape); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(pointShape); } } QVariant CustomPointPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); emit q->positionChanged(cSystem->mapSceneToLogical(value.toPointF())); } return QGraphicsItem::itemChange(change, value); } void CustomPointPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //position was changed -> set the position member variables suppressRetransform = true; const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); emit q->setPosition(cSystem->mapSceneToLogical(pos())); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } void CustomPointPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void CustomPointPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; - q->hovered(); + emit q->hovered(); update(); } } void CustomPointPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; - q->unhovered(); + emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CustomPoint::save(QXmlStreamWriter* writer) const { Q_D(const CustomPoint); writer->writeStartElement("customPoint"); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement("geometry"); writer->writeAttribute( "x", QString::number(d->position.x()) ); writer->writeAttribute( "y", QString::number(d->position.y()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Symbols writer->writeStartElement("symbol"); writer->writeAttribute( "symbolStyle", QString::number(d->symbolStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolSize) ); WRITE_QBRUSH(d->symbolBrush); WRITE_QPEN(d->symbolPen); writer->writeEndElement(); writer->writeEndElement(); // close "CustomPoint" section } //! Load from XML bool CustomPoint::load(XmlStreamReader* reader, bool preview) { Q_D(CustomPoint); if (!reader->isStartElement() || reader->name() != "customPoint") { reader->raiseError(i18n("no custom point element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "customPoint") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.setY(str.toDouble()); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "symbol") { attribs = reader->attributes(); str = attribs.value("symbolStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'symbolStyle'")); else d->symbolStyle = (Symbol::Style)str.toInt(); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->symbolOpacity = str.toDouble(); str = attribs.value("rotation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotation'")); else d->symbolRotationAngle = str.toDouble(); str = attribs.value("size").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'size'")); else d->symbolSize = str.toDouble(); READ_QBRUSH(d->symbolBrush); READ_QPEN(d->symbolPen); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } retransform(); return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp index 7792b51f4..c6dacf547 100644 --- a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp @@ -1,417 +1,417 @@ /*************************************************************************** File : XYDataReductionCurve.cpp Project : LabPlot Description : A xy-curve defined by a data reduction -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYDataReductionCurve \brief A xy-curve defined by a data reduction \ingroup worksheet */ #include "XYDataReductionCurve.h" #include "XYDataReductionCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include XYDataReductionCurve::XYDataReductionCurve(const QString& name) : XYAnalysisCurve(name, new XYDataReductionCurvePrivate(this)) { } XYDataReductionCurve::XYDataReductionCurve(const QString& name, XYDataReductionCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYDataReductionCurve::~XYDataReductionCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYDataReductionCurve::recalculate() { Q_D(XYDataReductionCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYDataReductionCurve::icon() const { return QIcon::fromTheme("labplot-xy-data-reduction-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYDataReductionCurve, XYDataReductionCurve::DataReductionData, dataReductionData, dataReductionData) const XYDataReductionCurve::DataReductionResult& XYDataReductionCurve::dataReductionResult() const { Q_D(const XYDataReductionCurve); return d->dataReductionResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYDataReductionCurve, SetDataReductionData, XYDataReductionCurve::DataReductionData, dataReductionData, recalculate); void XYDataReductionCurve::setDataReductionData(const XYDataReductionCurve::DataReductionData& reductionData) { Q_D(XYDataReductionCurve); exec(new XYDataReductionCurveSetDataReductionDataCmd(d, reductionData, i18n("%1: set options and perform the data reduction"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDataReductionCurvePrivate::XYDataReductionCurvePrivate(XYDataReductionCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYDataReductionCurvePrivate::~XYDataReductionCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYDataReductionCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create dataReduction result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result dataReductionResult = XYDataReductionCurve::DataReductionResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { dataReductionResult.available = true; dataReductionResult.valid = false; dataReductionResult.status = i18n("Number of x and y data points must be equal."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the data reduction to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (dataReductionData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = dataReductionData.xRange.first(); xmax = dataReductionData.xRange.last(); } for (int row = 0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to use const size_t n = (size_t)xdataVector.size(); if (n < 2) { dataReductionResult.available = true; dataReductionResult.valid = false; dataReductionResult.status = i18n("Not enough data points available."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // dataReduction settings const nsl_geom_linesim_type type = dataReductionData.type; const double tol = dataReductionData.tolerance; const double tol2 = dataReductionData.tolerance2; DEBUG("n =" << n); DEBUG("type:" << nsl_geom_linesim_type_name[type]); DEBUG("tolerance/step:" << tol); DEBUG("tolerance2/repeat/maxtol/region:" << tol2); /////////////////////////////////////////////////////////// emit q->completed(10); size_t npoints = 0; double calcTolerance = 0; // calculated tolerance from Douglas-Peucker variant size_t *index = (size_t *) malloc(n*sizeof(size_t)); switch (type) { case nsl_geom_linesim_type_douglas_peucker_variant: // tol used as number of points npoints = tol; calcTolerance = nsl_geom_linesim_douglas_peucker_variant(xdata, ydata, n, npoints, index); break; case nsl_geom_linesim_type_douglas_peucker: npoints = nsl_geom_linesim_douglas_peucker(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_nthpoint: // tol used as step npoints = nsl_geom_linesim_nthpoint(n, (int)tol, index); break; case nsl_geom_linesim_type_raddist: npoints = nsl_geom_linesim_raddist(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_perpdist: // tol2 used as repeat npoints = nsl_geom_linesim_perpdist_repeat(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_interp: npoints = nsl_geom_linesim_interp(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_visvalingam_whyatt: npoints = nsl_geom_linesim_visvalingam_whyatt(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_reumann_witkam: npoints = nsl_geom_linesim_reumann_witkam(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_opheim: npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_lang: // tol2 used as region npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; } DEBUG("npoints =" << npoints); if (type == nsl_geom_linesim_type_douglas_peucker_variant) { DEBUG("calculated tolerance =" << calcTolerance); } else Q_UNUSED(calcTolerance); emit q->completed(80); xVector->resize((int)npoints); yVector->resize((int)npoints); for (int i = 0; i < (int)npoints; i++) { (*xVector)[i] = xdata[index[i]]; (*yVector)[i] = ydata[index[i]]; } emit q->completed(90); const double posError = nsl_geom_linesim_positional_squared_error(xdata, ydata, n, index); const double areaError = nsl_geom_linesim_area_error(xdata, ydata, n, index); free(index); /////////////////////////////////////////////////////////// //write the result dataReductionResult.available = true; dataReductionResult.valid = true; if (npoints > 0) dataReductionResult.status = QString("OK"); else dataReductionResult.status = QString("FAILURE"); dataReductionResult.elapsedTime = timer.elapsed(); dataReductionResult.npoints = npoints; dataReductionResult.posError = posError; dataReductionResult.areaError = areaError; //redraw the curve - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; emit q->completed(100); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYDataReductionCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYDataReductionCurve); writer->writeStartElement("xyDataReductionCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-dataReduction-curve specific information // dataReduction data writer->writeStartElement("dataReductionData"); writer->writeAttribute( "autoRange", QString::number(d->dataReductionData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->dataReductionData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->dataReductionData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->dataReductionData.type) ); writer->writeAttribute( "autoTolerance", QString::number(d->dataReductionData.autoTolerance) ); writer->writeAttribute( "tolerance", QString::number(d->dataReductionData.tolerance) ); writer->writeAttribute( "autoTolerance2", QString::number(d->dataReductionData.autoTolerance2) ); writer->writeAttribute( "tolerance2", QString::number(d->dataReductionData.tolerance2) ); writer->writeEndElement();// dataReductionData // dataReduction results (generated columns) writer->writeStartElement("dataReductionResult"); writer->writeAttribute( "available", QString::number(d->dataReductionResult.available) ); writer->writeAttribute( "valid", QString::number(d->dataReductionResult.valid) ); writer->writeAttribute( "status", d->dataReductionResult.status ); writer->writeAttribute( "time", QString::number(d->dataReductionResult.elapsedTime) ); writer->writeAttribute( "npoints", QString::number(d->dataReductionResult.npoints) ); writer->writeAttribute( "posError", QString::number(d->dataReductionResult.posError) ); writer->writeAttribute( "areaError", QString::number(d->dataReductionResult.areaError) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"dataReductionResult" writer->writeEndElement(); //"xyDataReductionCurve" } //! Load from XML bool XYDataReductionCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYDataReductionCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyDataReductionCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "dataReductionData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", dataReductionData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", dataReductionData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", dataReductionData.xRange.last()); READ_INT_VALUE("type", dataReductionData.type, nsl_geom_linesim_type); READ_INT_VALUE("autoTolerance", dataReductionData.autoTolerance, int); READ_DOUBLE_VALUE("tolerance", dataReductionData.tolerance); READ_INT_VALUE("autoTolerance2", dataReductionData.autoTolerance2, int); READ_DOUBLE_VALUE("tolerance2", dataReductionData.tolerance2); } else if (!preview && reader->name() == "dataReductionResult") { attribs = reader->attributes(); READ_INT_VALUE("available", dataReductionResult.available, int); READ_INT_VALUE("valid", dataReductionResult.valid, int); READ_STRING_VALUE("status", dataReductionResult.status); READ_INT_VALUE("time", dataReductionResult.elapsedTime, int); READ_INT_VALUE("npoints", dataReductionResult.npoints, size_t); READ_DOUBLE_VALUE("posError", dataReductionResult.posError); READ_DOUBLE_VALUE("areaError", dataReductionResult.areaError); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp index 995d155b4..6a24e34d2 100644 --- a/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp @@ -1,231 +1,231 @@ /*************************************************************************** File : XYEquationCurve.cpp Project : LabPlot Description : A xy-curve defined by a mathematical equation -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYEquationCurve \brief A xy-curve defined by a mathematical equation \ingroup worksheet */ #include "XYEquationCurve.h" #include "XYEquationCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/gsl/ExpressionParser.h" #include #include XYEquationCurve::XYEquationCurve(const QString& name) : XYCurve(name, new XYEquationCurvePrivate(this)) { init(); } XYEquationCurve::XYEquationCurve(const QString& name, XYEquationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYEquationCurve::~XYEquationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYEquationCurve::init() { Q_D(XYEquationCurve); d->xColumn->setHidden(true); addChildFast(d->xColumn); d->yColumn->setHidden(true); addChildFast(d->yColumn); //TODO: read from the saved settings for XYEquationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; setUndoAware(false); suppressRetransform(true); setXColumn(d->xColumn); setYColumn(d->yColumn); suppressRetransform(false); setUndoAware(true); } void XYEquationCurve::recalculate() { Q_D(XYEquationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYEquationCurve::icon() const { return QIcon::fromTheme("labplot-xy-equation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYEquationCurve, XYEquationCurve::EquationData, equationData, equationData) //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYEquationCurve, SetEquationData, XYEquationCurve::EquationData, equationData, recalculate); void XYEquationCurve::setEquationData(const XYEquationCurve::EquationData& equationData) { Q_D(XYEquationCurve); if ( (equationData.expression1 != d->equationData.expression1) || (equationData.expression2 != d->equationData.expression2) || (equationData.min != d->equationData.min) || (equationData.max != d->equationData.max) || (equationData.count != d->equationData.count) ) exec(new XYEquationCurveSetEquationDataCmd(d, equationData, i18n("%1: set equation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYEquationCurvePrivate::XYEquationCurvePrivate(XYEquationCurve* owner) : XYCurvePrivate(owner), xColumn(new Column("x", AbstractColumn::Numeric)), yColumn(new Column("y", AbstractColumn::Numeric)), xVector(static_cast* >(xColumn->data())), yVector(static_cast* >(yColumn->data())), q(owner) { } XYEquationCurvePrivate::~XYEquationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYEquationCurvePrivate::recalculate() { //resize the vector if a new number of point to calculate was provided if (equationData.count != xVector->size()) { if (equationData.count >= 1) { xVector->resize(equationData.count); yVector->resize(equationData.count); } else { //invalid number of points provided xVector->clear(); yVector->clear(); - emit (q->dataChanged()); + emit q->dataChanged(); return; } } else { if (equationData.count < 1) return; } ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = false; if (equationData.type == XYEquationCurve::Cartesian) { rc = parser->evaluateCartesian( equationData.expression1, equationData.min, equationData.max, equationData.count, xVector, yVector ); } else if (equationData.type == XYEquationCurve::Polar) { rc = parser->evaluatePolar( equationData.expression1, equationData.min, equationData.max, equationData.count, xVector, yVector ); } else if (equationData.type == XYEquationCurve::Parametric) { rc = parser->evaluateParametric(equationData.expression1, equationData.expression2, equationData.min, equationData.max, equationData.count, xVector, yVector); } if (!rc) { xVector->clear(); yVector->clear(); } - emit (q->dataChanged()); + emit q->dataChanged(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYEquationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYEquationCurve); writer->writeStartElement( "xyEquationCurve" ); //write xy-curve information XYCurve::save(writer); //write xy-equationCurve specific information writer->writeStartElement( "equationData" ); writer->writeAttribute( "type", QString::number(d->equationData.type) ); writer->writeAttribute( "expression1", d->equationData.expression1 ); writer->writeAttribute( "expression2", d->equationData.expression2 ); writer->writeAttribute( "min", d->equationData.min); writer->writeAttribute( "max", d->equationData.max ); writer->writeAttribute( "count", QString::number(d->equationData.count) ); writer->writeEndElement(); writer->writeEndElement(); } //! Load from XML bool XYEquationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYEquationCurve); if (!reader->isStartElement() || reader->name() != "xyEquationCurve") { reader->raiseError(i18n("no xy equation curve element found")); return false; } QString attributeWarning = i18n( "Attribute '%1' missing or empty, default value is used" ); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyEquationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "equationData") { attribs = reader->attributes(); READ_INT_VALUE("type", equationData.type, XYEquationCurve::EquationType); READ_STRING_VALUE("expression1", equationData.expression1); READ_STRING_VALUE("expression2", equationData.expression2); READ_STRING_VALUE("min", equationData.min); READ_STRING_VALUE("max", equationData.max); READ_INT_VALUE("count", equationData.count, int); } } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index 90c5fa1e7..260cf2313 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2260 +1,2260 @@  /*************************************************************************** File : XYFitCurve.cpp Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFitCurve \brief A xy-curve defined by a fit model \ingroup worksheet */ #include "XYFitCurve.h" #include "XYFitCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" #include "backend/gsl/ExpressionParser.h" extern "C" { #include #include #include #include #include #include #include "backend/gsl/parser.h" #include "backend/nsl/nsl_sf_stats.h" #include "backend/nsl/nsl_stats.h" } #include #include #include XYFitCurve::XYFitCurve(const QString& name) : XYAnalysisCurve(name, new XYFitCurvePrivate(this)) { } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYFitCurve::~XYFitCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } void XYFitCurve::initStartValues(const XYCurve* curve) { Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; initStartValues(fitData, curve); } void XYFitCurve::initStartValues(XYFitCurve::FitData& fitData, const XYCurve* curve) { DEBUG("XYFitCurve::initStartValues()"); if (!curve) { DEBUG(" no curve given"); return; } const Column* tmpXDataColumn = dynamic_cast(curve->xColumn()); const Column* tmpYDataColumn = dynamic_cast(curve->yColumn()); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG(" data columns not available"); return; } DEBUG(" x data rows = " << tmpXDataColumn->rowCount()); nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; int degree = fitData.degree; DEBUG(" fit model type = " << modelType << ", degree = " << degree); QVector& paramStartValues = fitData.paramStartValues; //QVector* xVector = static_cast* >(tmpXDataColumn->data()); //double xmean = gsl_stats_mean(xVector->constData(), 1, tmpXDataColumn->rowCount()); double xmin = tmpXDataColumn->minimum(); double xmax = tmpXDataColumn->maximum(); //double ymin = tmpYDataColumn->minimum(); //double ymax = tmpYDataColumn->maximum(); double xrange = xmax-xmin; //double yrange = ymax-ymin; DEBUG(" x min/max = " << xmin << ' ' << xmax); //DEBUG(" y min/max = " << ymin << ' ' << ymax); switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // not needed (works anyway) break; //TODO: handle basic models case nsl_fit_model_power: case nsl_fit_model_exponential: case nsl_fit_model_inverse_exponential: case nsl_fit_model_fourier: break; } break; case nsl_fit_model_peak: // use equidistant mu's and (xmax-xmin)/(10*degree) as sigma for (int d = 0; d < degree; d++) { paramStartValues[3*d+1] = xmin + (d+1.)*xrange/(degree+1.); paramStartValues[3*d] = xrange/(10.*degree); } break; case nsl_fit_model_growth: switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: case nsl_fit_model_sigmoid: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[1] = (xmax+xmin)/2.; paramStartValues[0] = xrange/10.; break; case nsl_fit_model_hill: paramStartValues[0] = xrange/10.; break; case nsl_fit_model_gompertz: //TODO break; } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[1] = (xmin+xmax)/2.; paramStartValues[0] = xrange/10.; break; //TODO: other types default: break; } break; case nsl_fit_model_custom: // not possible break; } } /*! * sets the parameter names for given model category, model type and degree in \c fitData for given action */ void XYFitCurve::initFitData(PlotDataDialog::AnalysisAction action) { if (!action) return; Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; if (action == PlotDataDialog::FitLinear) { //Linear fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = 0; } XYFitCurve::initFitData(fitData); } /*! * sets the model expression and the parameter names for given model category, model type and degree in \c fitData */ void XYFitCurve::initFitData(XYFitCurve::FitData& fitData) { nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; QString& model = fitData.model; QStringList& paramNames = fitData.paramNames; QStringList& paramNamesUtf8 = fitData.paramNamesUtf8; int degree = fitData.degree; QVector& paramStartValues = fitData.paramStartValues; QVector& paramLowerLimits = fitData.paramLowerLimits; QVector& paramUpperLimits = fitData.paramUpperLimits; QVector& paramFixed = fitData.paramFixed; if (modelCategory != nsl_fit_model_custom) { DEBUG("XYFitCurve::initFitData() for model category = " << nsl_fit_model_category_name[modelCategory] << ", model type = " << modelType << ", degree = " << degree); paramNames.clear(); } else { DEBUG("XYFitCurve::initFitData() for model category = nsl_fit_model_custom, model type = " << modelType << ", degree = " << degree); } paramNamesUtf8.clear(); // 10 indices used in multi degree models QStringList indices = { UTF8_QSTRING("₁"), UTF8_QSTRING("₂"), UTF8_QSTRING("₃"), UTF8_QSTRING("₄"), UTF8_QSTRING("₅"), UTF8_QSTRING("₆"), UTF8_QSTRING("₇"), UTF8_QSTRING("₈"), UTF8_QSTRING("₉"), UTF8_QSTRING("₁₀")}; switch (modelCategory) { case nsl_fit_model_basic: model = nsl_fit_model_basic_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_polynomial: paramNames << "c0" << "c1"; paramNamesUtf8 << UTF8_QSTRING("c₀") << UTF8_QSTRING("c₁"); if (degree == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << UTF8_QSTRING("c₂"); } else if (degree > 2) { for (int i = 2; i <= degree; ++i) { QString numStr = QString::number(i); model += "+c" + numStr + "*x^" + numStr; paramNames << "c" + numStr; paramNamesUtf8 << "c" + indices[i-1]; } } break; case nsl_fit_model_power: if (degree == 1) { paramNames << "a" << "b"; } else { paramNames << "a" << "b" << "c"; model = "a + b*x^c"; } break; case nsl_fit_model_exponential: if (degree == 1) { paramNames << "a" << "b"; } else { for (int i = 1; i <= degree; i++) { QString numStr = QString::number(i); if (i == 1) model = "a1*exp(b1*x)"; else model += " + a" + numStr + "*exp(b" + numStr + "*x)"; paramNames << "a" + numStr << "b" + numStr; paramNamesUtf8 << "a" + indices[i-1] << "b" + indices[i-1]; } } break; case nsl_fit_model_inverse_exponential: paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << UTF8_QSTRING("ω") << UTF8_QSTRING("a₀") << UTF8_QSTRING("a₁") << UTF8_QSTRING("b₁"); if (degree > 1) { for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); model += "+ (a" + numStr + "*cos(" + numStr + "*w*x) + b" + numStr + "*sin(" + numStr + "*w*x))"; paramNames << "a" + numStr << "b" + numStr; paramNamesUtf8 << "a" + indices[i-1] << "b" + indices[i-1]; } } break; } break; case nsl_fit_model_peak: model = nsl_fit_model_peak_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_gaussian: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << "A"; break; case 2: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << UTF8_QSTRING("σ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("σ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂"); break; case 3: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2) + a3/s3 * exp(-((x-mu3)/s3)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << UTF8_QSTRING("σ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("σ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂") << UTF8_QSTRING("σ₃") << UTF8_QSTRING("μ₃") << UTF8_QSTRING("A₃"); break; default: model = "1./sqrt(2*pi) * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* exp(-((x-mu" + numStr + ")/s" + numStr + ")^2/2)"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << "A" + indices[i-1]; } model += ")"; } break; case nsl_fit_model_lorentz: switch (degree) { case 1: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("γ") << UTF8_QSTRING("μ") << "A"; break; case 2: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2"; paramNamesUtf8 << UTF8_QSTRING("γ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("γ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂"); break; case 3: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2) + a3 * g3/(g3^2+(x-mu3)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2" << "g3" << "mu3" << "a3"; paramNamesUtf8 << UTF8_QSTRING("γ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("γ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂") << UTF8_QSTRING("γ₃") << UTF8_QSTRING("μ₃") << UTF8_QSTRING("A₃"); break; default: model = "1./pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + " * g" + numStr + "/(g" + numStr + "^2+(x-mu" + numStr + ")^2)"; paramNames << "g" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << UTF8_QSTRING("γ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << "A" + indices[i-1]; } model += ")"; } break; case nsl_fit_model_sech: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << "A"; break; case 2: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << UTF8_QSTRING("σ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("σ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂"); break; case 3: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2) + a3/s3 * sech((x-mu3)/s3))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << UTF8_QSTRING("σ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("σ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂") << UTF8_QSTRING("σ₃") << UTF8_QSTRING("μ₃") << UTF8_QSTRING("A₃"); break; default: model = "1/pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/s" + numStr + ")"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << "A" + indices[i-1]; } model += ")"; } break; case nsl_fit_model_logistic: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << "A"; break; case 2: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << UTF8_QSTRING("σ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("σ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂"); break; case 3: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2 + a3/s3 * sech((x-mu3)/2/s3)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << UTF8_QSTRING("σ₁") << UTF8_QSTRING("μ₁") << UTF8_QSTRING("A₁") << UTF8_QSTRING("σ₂") << UTF8_QSTRING("μ₂") << UTF8_QSTRING("A₂") << UTF8_QSTRING("σ₃") << UTF8_QSTRING("μ₃") << UTF8_QSTRING("A₃"); break; default: model = "1/4 * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/2/s" + numStr + ")**2"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << "A" + indices[i-1]; } model += ")"; } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << "A"; break; case nsl_fit_model_sigmoid: paramNames << "k" << "mu" << "a"; paramNamesUtf8 << "k" << UTF8_QSTRING("μ") << "A"; break; case nsl_fit_model_hill: paramNames << "s" << "n" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << "n" << "A"; break; case nsl_fit_model_gompertz: paramNames << "a" << "b" << "c"; break; } break; case nsl_fit_model_distribution: model = nsl_sf_stats_distribution_equation[fitData.modelType]; switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << "A"; break; case nsl_sf_stats_gaussian_tail: paramNames << "s" << "mu" << "A" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << "A" << "a"; break; case nsl_sf_stats_exponential: paramNames << "l" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("λ") << UTF8_QSTRING("μ") << "A"; break; case nsl_sf_stats_exponential_power: paramNames << "s" << "mu" << "b" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << "b" << "A"; break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("γ") << UTF8_QSTRING("μ") << "A"; break; case nsl_sf_stats_rayleigh: paramNames << "s" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << "A"; break; case nsl_sf_stats_landau: paramNames << "a"; paramNamesUtf8 << "A"; break; case nsl_sf_stats_levy_alpha_stable: // unused distributions case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; case nsl_sf_stats_gamma: paramNames << "t" << "k" << "a"; paramNamesUtf8 << UTF8_QSTRING("θ") << "k" << "A"; break; case nsl_sf_stats_flat: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_chi_squared: paramNames << "n" << "a"; paramNamesUtf8 << "n" << "A"; break; case nsl_sf_stats_fdist: paramNames << "n1" << "n2" << "a"; paramNamesUtf8 << UTF8_QSTRING("ν₁") << UTF8_QSTRING("ν₂") << "A"; break; case nsl_sf_stats_tdist: paramNames << "n" << "a"; paramNamesUtf8 << UTF8_QSTRING("ν") << "A"; break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_weibull: paramNames << "k" << "l" << "mu" << "a"; paramNamesUtf8 << "k" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ") << "A"; break; case nsl_sf_stats_gumbel1: paramNames << "s" << "b" << "mu" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << UTF8_QSTRING("β") << UTF8_QSTRING("μ") << "A"; break; case nsl_sf_stats_gumbel2: paramNames << "a" << "b" << "mu" << "A"; paramNamesUtf8 << "a" << "b" << UTF8_QSTRING("μ") << "A"; break; case nsl_sf_stats_poisson: paramNames << "l" << "a"; paramNamesUtf8 << UTF8_QSTRING("λ") << "A"; break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "p" << "n" << "a"; paramNamesUtf8 << "p" << "n" << "A"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "p" << "a"; paramNamesUtf8 << "p" << "A"; break; case nsl_sf_stats_hypergeometric: paramNames << "n1" << "n2" << "t" << "a"; paramNamesUtf8 << UTF8_QSTRING("n₁") << UTF8_QSTRING("n₂") << "t" << "A"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "s" << "a"; paramNamesUtf8 << UTF8_QSTRING("σ") << "A"; break; case nsl_sf_stats_frechet: paramNames << "g" << "mu" << "s" << "a"; paramNamesUtf8 << UTF8_QSTRING("γ") << UTF8_QSTRING("μ") << UTF8_QSTRING("σ") << "A"; break; } break; case nsl_fit_model_custom: break; } if (paramNamesUtf8.isEmpty()) paramNamesUtf8 << paramNames; //resize the vector for the start values and set the elements to 1.0 //in case a custom model is used, do nothing, we take over the previous values if (modelCategory != nsl_fit_model_custom) { const int np = paramNames.size(); paramStartValues.resize(np); paramFixed.resize(np); paramLowerLimits.resize(np); paramUpperLimits.resize(np); for (int i = 0; i < np; ++i) { paramStartValues[i] = 1.0; paramFixed[i] = false; paramLowerLimits[i] = -std::numeric_limits::max(); paramUpperLimits[i] = std::numeric_limits::max(); } // set some model-dependent start values if (modelCategory == nsl_fit_model_distribution) { nsl_sf_stats_distribution type = (nsl_sf_stats_distribution)modelType; if (type == nsl_sf_stats_flat) paramStartValues[0] = -1.0; else if (type == nsl_sf_stats_frechet || type == nsl_sf_stats_levy || type == nsl_sf_stats_exponential_power) paramStartValues[1] = 0.0; else if (type == nsl_sf_stats_weibull || type == nsl_sf_stats_gumbel2) paramStartValues[2] = 0.0; else if (type == nsl_sf_stats_binomial || type == nsl_sf_stats_negative_binomial || type == nsl_sf_stats_pascal || type == nsl_sf_stats_geometric || type == nsl_sf_stats_logarithmic) paramStartValues[0] = 0.5; } } } /*! Returns an icon to be used in the project explorer. */ QIcon XYFitCurve::icon() const { return QIcon::fromTheme("labplot-xy-fit-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xErrorColumnPath() const { Q_D(const XYFitCurve); return d->xErrorColumnPath; } const QString& XYFitCurve::yErrorColumnPath() const { Q_D(const XYFitCurve); return d->yErrorColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFitCurve, XYFitCurve::FitData, fitData, fitData) const XYFitCurve::FitResult& XYFitCurve::fitResult() const { Q_D(const XYFitCurve); return d->fitResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXErrorColumn, const AbstractColumn*, xErrorColumn) void XYFitCurve::setXErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xErrorColumn) { exec(new XYFitCurveSetXErrorColumnCmd(d, column, i18n("%1: assign x-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYErrorColumn, const AbstractColumn*, yErrorColumn) void XYFitCurve::setYErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yErrorColumn) { exec(new XYFitCurveSetYErrorColumnCmd(d, column, i18n("%1: assign y-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData, recalculate); void XYFitCurve::setFitData(const XYFitCurve::FitData& fitData) { Q_D(XYFitCurve); exec(new XYFitCurveSetFitDataCmd(d, fitData, i18n("%1: set fit options and perform the fit"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYAnalysisCurvePrivate(owner), xErrorColumn(nullptr), yErrorColumn(nullptr), residualsColumn(nullptr), residualsVector(nullptr), q(owner) { } XYFitCurvePrivate::~XYFitCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // data structure to pass parameter to fit functions struct data { size_t n; //number of data points double* x; //pointer to the vector with x-data values double* y; //pointer to the vector with y-data values double* weight; //pointer to the vector with weight values nsl_fit_model_category modelCategory; int modelType; int degree; QString* func; // string containing the definition of the model/function QStringList* paramNames; double* paramMin; // lower parameter limits double* paramMax; // upper parameter limits bool* paramFixed; // parameter fixed? }; /*! * \param paramValues vector containing current values of the fit parameters * \param params * \param f vector with the weighted residuals weight[i]*(Yi - y[i]) */ int func_f(const gsl_vector* paramValues, void* params, gsl_vector* f) { //DEBUG("func_f"); size_t n = ((struct data*)params)->n; double* x = ((struct data*)params)->x; double* y = ((struct data*)params)->y; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; QByteArray funcba = ((struct data*)params)->func->toLatin1(); // a local byte array is needed! const char *func = funcba.constData(); // function to evaluate QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; // set current values of the parameters for (int i = 0; i < paramNames->size(); i++) { double v = gsl_vector_get(paramValues, (size_t)i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(v, min[i], max[i])); QDEBUG("Parameter"<n; double* xVector = ((struct data*)params)->x; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; unsigned int degree = ((struct data*)params)->degree; QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; bool *fixed = ((struct data*)params)->paramFixed; // calculate the Jacobian matrix: // Jacobian matrix J(i,j) = df_i / dx_j // where f_i = w_i*(Y_i - y_i), // Y_i = model and the x_j are the parameters double x; switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // Y(x) = c0 + c1*x + ... + cn*x^n for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < (unsigned int)paramNames->size(); ++j) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_polynomial_param_deriv(x, j, weight[i])); } } break; case nsl_fit_model_power: // Y(x) = a*x^b or Y(x) = a + b*x^c. if (degree == 1) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power1_param_deriv(j, x, a, b, weight[i])); } } } else if (degree == 2) { const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double c = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power2_param_deriv(j, x, b, c, weight[i])); } } } break; case nsl_fit_model_exponential: { // Y(x) = a*exp(b*x) + c*exp(d*x) + ... double *p = new double[2*degree]; for (unsigned int i = 0; i < 2*degree; i++) p[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, i), min[i], max[i]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2*degree; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponentialn_param_deriv(j, x, p, weight[i])); } } delete[] p; break; } case nsl_fit_model_inverse_exponential: { // Y(x) = a*(1-exp(b*x))+c const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_inverse_exponential_param_deriv(j, x, a, b, weight[i])); } } break; } case nsl_fit_model_fourier: { // Y(x) = a0 + (a1*cos(w*x) + b1*sin(w*x)) + ... + (an*cos(n*w*x) + bn*sin(n*w*x) //parameters: w, a0, a1, b1, ... an, bn double* a = new double[degree]; double* b = new double[degree]; double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); a[0] = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); b[0] = 0; for (unsigned int i = 1; i < degree; ++i) { a[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i), min[2*i], max[2*i]); b[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i+1), min[2*i+1], max[2*i+1]); } for (size_t i = 0; i < n; i++) { x = xVector[i]; double wd = 0; //first derivative with respect to the w parameter for (unsigned int j = 1; j < degree; ++j) { wd += -a[j]*j*x*sin(j*w*x) + b[j]*j*x*cos(j*w*x); } gsl_matrix_set(J, i, 0, weight[i]*wd); gsl_matrix_set(J, i, 1, weight[i]); for (unsigned int j = 1; j <= degree; ++j) { gsl_matrix_set(J, (size_t)i, (size_t)(2*j), nsl_fit_model_fourier_param_deriv(0, j, x, w, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(2*j+1), nsl_fit_model_fourier_param_deriv(1, j, x, w, weight[i])); } for (unsigned int j = 0; j <= 2*degree+1; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } delete[] a; delete[] b; break; } } break; case nsl_fit_model_peak: switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: { for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j), min[3*j], max[3*j]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+1), min[3*j+1], max[3*j+1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+2), min[3*j+2], max[3*j+2]); switch (modelType) { case nsl_fit_model_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_gaussian_param_deriv(0, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_gaussian_param_deriv(1, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_gaussian_param_deriv(2, x, s, mu, a, weight[i])); break; case nsl_fit_model_lorentz: // s,t,a gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_lorentz_param_deriv(0, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_lorentz_param_deriv(1, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_lorentz_param_deriv(2, x, s, mu, a, weight[i])); break; case nsl_fit_model_sech: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_sech_param_deriv(0, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_sech_param_deriv(1, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_sech_param_deriv(2, x, s, mu, a, weight[i])); break; case nsl_fit_model_logistic: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_logistic_param_deriv(0, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_logistic_param_deriv(1, x, s, mu, a, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_logistic_param_deriv(2, x, s, mu, a, weight[i])); break; } } for (unsigned int j = 0; j < 3*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; } } break; case nsl_fit_model_growth: switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_sigmoid: // Y(x) = a/(1+exp(-k*(x-mu))) case nsl_fit_model_erf: case nsl_fit_model_hill: case nsl_fit_model_gompertz: // Y(x) = a*exp(-b*exp(-c*x)); case nsl_fit_model_gudermann: { const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_atan) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_atan_param_deriv(j, x, s, mu, a, weight[i])); else if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_tanh) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_tanh_param_deriv(j, x, s, mu, a, weight[i])); else if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_algebraic_sigmoid) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_algebraic_sigmoid_param_deriv(j, x, s, mu, a, weight[i])); else if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_sigmoid) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sigmoid_param_deriv(j, x, s, mu, a, weight[i])); else if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_erf) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_erf_param_deriv(j, x, s, mu, a, weight[i])); else if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_hill) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hill_param_deriv(j, x, s, mu, a, weight[i])); else if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_gompertz) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gompertz_param_deriv(j, x, s, mu, a, weight[i])); else if ((nsl_fit_model_type_growth)modelType == nsl_fit_model_gudermann) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gudermann_param_deriv(j, x, s, mu, a, weight[i])); } } } break; } } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_exponential: case nsl_sf_stats_laplace: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_levy: { const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_param_deriv(j, x, s, mu, a, weight[i])); break; case nsl_sf_stats_exponential: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponential_param_deriv(j, x, s, mu, a, weight[i])); break; case nsl_sf_stats_laplace: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_laplace_param_deriv(j, x, s, mu, a, weight[i])); break; case nsl_sf_stats_cauchy_lorentz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lorentz_param_deriv(j, x, s, mu, a, weight[i])); break; case nsl_sf_stats_rayleigh_tail: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_tail_param_deriv(j, x, s, mu, a, weight[i])); break; case nsl_sf_stats_lognormal: if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lognormal_param_deriv(j, x, s, mu, a, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); break; case nsl_sf_stats_logistic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logistic_param_deriv(j, x, s, mu, a, weight[i])); break; case nsl_sf_stats_sech: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sech_dist_param_deriv(j, x, s, mu, a, weight[i])); break; case nsl_sf_stats_levy: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_levy_param_deriv(j, x, s, mu, a, weight[i])); break; } } } } break; } case nsl_sf_stats_gaussian_tail: { const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_tail_param_deriv(j, x, s, mu, A, a, weight[i])); } } break; } case nsl_sf_stats_exponential_power: { const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exp_pow_param_deriv(j, x, s, mu, b, a, weight[i])); } } break; } case nsl_sf_stats_rayleigh: { const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_param_deriv(j, x, s, a, weight[i])); } } break; } case nsl_sf_stats_gamma: { const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gamma_param_deriv(j, x, t, k, a, weight[i])); } } break; } case nsl_sf_stats_flat: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_flat_param_deriv(j, x, a, b, A, weight[i])); } } break; } case nsl_sf_stats_chi_squared: { const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_chi_square_param_deriv(j, x, nu, a, weight[i])); } } break; } case nsl_sf_stats_tdist: { const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_students_t_param_deriv(j, x, nu, a, weight[i])); } } break; } case nsl_sf_stats_fdist: { const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_fdist_param_deriv(j, x, n1, n2, a, weight[i])); } } break; } case nsl_sf_stats_beta: case nsl_sf_stats_pareto: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_beta: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_beta_param_deriv(j, x, a, b, A, weight[i])); break; case nsl_sf_stats_pareto: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pareto_param_deriv(j, x, a, b, A, weight[i])); break; } } } } break; } case nsl_sf_stats_weibull: { const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_weibull_param_deriv(j, x, k, l, mu, a, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } } } break; } case nsl_sf_stats_gumbel1: { const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel1_param_deriv(j, x, s, b, mu, a, weight[i])); } } break; } case nsl_sf_stats_gumbel2: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel2_param_deriv(j, x, a, b, mu, A, weight[i])); } } break; } case nsl_sf_stats_poisson: { const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_poisson_param_deriv(j, x, l, a, weight[i])); } } break; } case nsl_sf_stats_maxwell_boltzmann: { // Y(x) = a*sqrt(2/pi) * x^2/s^3 * exp(-(x/s)^2/2) const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_maxwell_param_deriv(j, x, s, a, weight[i])); } } break; } case nsl_sf_stats_frechet: { const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_frechet_param_deriv(j, x, g, mu, s, a, weight[i])); } } break; } case nsl_sf_stats_landau: { // const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); for (size_t i = 0; i < n; i++) { x = xVector[i]; if (fixed[0]) gsl_matrix_set(J, (size_t)i, 0, 0.); else gsl_matrix_set(J, (size_t)i, 0, nsl_fit_model_landau_param_deriv(0, x, weight[i])); } break; } case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: { const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double N = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_binomial_param_deriv(j, x, p, N, a, weight[i])); break; case nsl_sf_stats_negative_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_negative_binomial_param_deriv(j, x, p, N, a, weight[i])); break; case nsl_sf_stats_pascal: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pascal_param_deriv(j, x, p, N, a, weight[i])); break; } } } } break; } case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: { const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_geometric: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_geometric_param_deriv(j, x, p, a, weight[i])); break; case nsl_sf_stats_logarithmic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logarithmic_param_deriv(j, x, p, a, weight[i])); break; } } } } break; } case nsl_sf_stats_hypergeometric: { const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hypergeometric_param_deriv(j, x, n1, n2, t, a, weight[i])); } } break; } // unused distributions case nsl_sf_stats_levy_alpha_stable: case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; } break; case nsl_fit_model_custom: QByteArray funcba = ((struct data*)params)->func->toLatin1(); const char* func = funcba.data(); QByteArray nameba; double value; const unsigned int np = paramNames->size(); for (size_t i = 0; i < n; i++) { x = xVector[i]; assign_variable("x", x); for (unsigned int j = 0; j < np; j++) { for (unsigned int k = 0; k < np; k++) { if (k != j) { nameba = paramNames->at(k).toLatin1(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, k), min[k], max[k]); assign_variable(nameba.data(), value); } } nameba = paramNames->at(j).toLatin1(); const char *name = nameba.data(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, j), min[j], max[j]); assign_variable(name, value); const double f_p = parse(func); const double eps = 1.e-9 * std::abs(f_p); // adapt step size to value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // qDebug()<<"evaluate deriv"<* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); residualsVector = static_cast* >(residualsColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->addChild(residualsColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); residualsVector->clear(); } // clear the previous result fitResult = XYFitCurve::FitResult(); //fit settings const unsigned int maxIters = fitData.maxIterations; //maximal number of iterations const double delta = fitData.eps; //fit tolerance const unsigned int np = fitData.paramNames.size(); //number of fit parameters if (np == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Model has no parameters."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source DEBUG(" curve columns as data source"); tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Number of x and y data points must be equal."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (yErrorColumn) { if (yErrorColumn->rowCount() < tmpXDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Not sufficient weight data points provided."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } } //copy all valid data point for the fit to temporary vectors QVector xdataVector; QVector ydataVector; QVector xerrorVector; QVector yerrorVector; double xmin, xmax; if (fitData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = fitData.xRange.first(); xmax = fitData.xRange.last(); } for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { //only copy those data where _all_ values (for x and y and errors, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { if (dataSourceType == XYAnalysisCurve::DataSourceCurve || (!xErrorColumn && !yErrorColumn) || !fitData.useDataErrors) { // x-y xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } else if (!xErrorColumn) { // x-y-dy if (!std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } else { // x-y-dx-dy if (!std::isnan(xErrorColumn->valueAt(row)) && !std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); xerrorVector.append(xErrorColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } } } } //number of data points to fit const size_t n = xdataVector.size(); DEBUG("number of data points: " << n); if (n == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("No data points available."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (n < np) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("The number of data points (%1) must be greater than or equal to the number of parameters (%2).", n, np); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* xerror = xerrorVector.data(); // size may be 0 double* yerror = yerrorVector.data(); // size may be 0 DEBUG("x errors: " << xerrorVector.size()); DEBUG("y errors: " << yerrorVector.size()); double* weight = new double[n]; switch(fitData.xErrorsType) { case nsl_fit_error_no: case nsl_fit_error_direct: break; case nsl_fit_error_inverse: for(int i = 0; i < xerrorVector.size(); i++) xerror[i] = 1./xerror[i]; break; } for (size_t i = 0; i < n; i++) weight[i] = 1.; switch (fitData.yWeightsType) { case nsl_fit_weight_no: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: break; case nsl_fit_weight_instrumental: // yerror are sigmas for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./gsl_pow_2(yerror[i]); break; case nsl_fit_weight_direct: // yerror are weights for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: // yerror are inverse weights for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./yerror[i]; break; case nsl_fit_weight_statistical: for (int i = 0; i < (int)n; i++) weight[i] = 1./ydata[i]; break; case nsl_fit_weight_relative: for (int i = 0; i < (int)n; i++) weight[i] = 1./gsl_pow_2(ydata[i]); break; } /////////////////////// GSL >= 2 has a complete new interface! But the old one is still supported. /////////////////////////// // GSL >= 2 : "the 'fdf' field of gsl_multifit_function_fdf is now deprecated and does not need to be specified for nonlinear least squares problems" unsigned int nf = 0; // number of fixed parameter for (unsigned int i = 0; i < np; i++) { const bool fixed = fitData.paramFixed.data()[i]; if (fixed) nf++; DEBUG("parameter " << i << " fixed: " << fixed); } //function to fit gsl_multifit_function_fdf f; DEBUG("model = " << fitData.model.toStdString()); struct data params = {n, xdata, ydata, weight, fitData.modelCategory, fitData.modelType, fitData.degree, &fitData.model, &fitData.paramNames, fitData.paramLowerLimits.data(), fitData.paramUpperLimits.data(), fitData.paramFixed.data()}; f.f = &func_f; f.df = &func_df; f.fdf = &func_fdf; f.n = n; f.p = np; f.params = ¶ms; DEBUG("initialize the derivative solver (using Levenberg-Marquardt robust solver)"); const gsl_multifit_fdfsolver_type* T = gsl_multifit_fdfsolver_lmsder; gsl_multifit_fdfsolver* s = gsl_multifit_fdfsolver_alloc(T, n, np); DEBUG("set start values"); double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); DEBUG("scale start values if limits are set"); for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_unbound(x_init[i], x_min[i], x_max[i]); DEBUG(" DONE"); gsl_vector_view x = gsl_vector_view_array(x_init, np); DEBUG("Turning off GSL error handler to avoid overflow/underflow"); gsl_set_error_handler_off(); DEBUG("Initialize solver with function f and initial guess x"); gsl_multifit_fdfsolver_set(s, &f, &x.vector); DEBUG("Iterate ..."); int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; // update weights for Y-depending weights (TODO: check correct residuals) if (fitData.yWeightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i } else if (fitData.yWeightsType == nsl_fit_weight_relative_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i^2 } status = gsl_multifit_fdfsolver_iterate(s); writeSolverState(s); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); // second run for x-error fitting if (xerrorVector.size() > 0) { DEBUG("Rerun fit with x errors"); unsigned int iter2 = 0; double chi = 0, chiOld = 0; double *fun = new double[n]; do { iter2++; chiOld = chi; //TODO: Debug printf("iter2 = %d\n", iter2); // calculate df[i] for (size_t i = 0; i < n; i++) fun[i] = gsl_vector_get(s->f, i) * 1./sqrt(weight[i]) + ydata[i]; // calculate weight[i] for (size_t i = 0; i < n; i++) { // calculate df[i] int index = i-1; if (i == 0) index = i; if (i == n-1) index = i-2; double df = (fun[index+1] - fun[index])/(xdata[index+1] - xdata[index]); //TODO: Debug printf("df = %g\n", df); // direct x-error TODO: others double sigmasq = df*df/xerror[i]; if (yerrorVector.size() > 0) { switch (fitData.yWeightsType) { // y-error types case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // direct s^2 = 1/w sigmasq += 1./yerror[i]; break; case nsl_fit_weight_instrumental: // instrumental s^2 = s*s sigmasq += yerror[i]*yerror[i]; break; case nsl_fit_weight_inverse: // inverse s^2 = 1/w sigmasq += yerror[i]; break; case nsl_fit_weight_statistical: case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: // 1/Y_i sigmasq += fun[i]; break; case nsl_fit_weight_relative_fit: // 1/Y_i^2 sigmasq += fun[i]*fun[i]; break; } } //TODO: Debug printf ("sigma[%d] = %g\n", i, sqrt(sigmasq)); weight[i] = 1./sigmasq; } // update weights gsl_multifit_fdfsolver_set(s, &f, &x.vector); do { // fit iter++; writeSolverState(s); status = gsl_multifit_fdfsolver_iterate (s); printf ("status = %s\n", gsl_strerror (status)); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); chi = gsl_blas_dnrm2(s->f); // TODO: Debug printf("chi = %.12g (dchi = %g)\n", chi, fabs(chi-chiOld)); } while (iter2 < maxIters && fabs(chi-chiOld) > fitData.eps); delete[] fun; } delete[] weight; // unscale start parameter for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_bound(x_init[i], x_min[i], x_max[i]); //get the covariance matrix //TODO: scale the Jacobian when limits are used before constructing the covar matrix? gsl_matrix* covar = gsl_matrix_alloc(np, np); #if GSL_MAJOR_VERSION >= 2 // the Jacobian is not part of the solver anymore gsl_matrix *J = gsl_matrix_alloc(s->fdf->n, s->fdf->p); gsl_multifit_fdfsolver_jac(s, J); gsl_multifit_covar(J, 0.0, covar); #else gsl_multifit_covar(s->J, 0.0, covar); #endif //write the result fitResult.available = true; fitResult.valid = true; fitResult.status = gslErrorToString(status); fitResult.iterations = iter; fitResult.dof = n - (np - nf); // samples - (parameter - fixed parameter) //gsl_blas_dnrm2() - computes the Euclidian norm (||r||_2 = \sqrt {\sum r_i^2}) of the vector with the elements weight[i]*(Yi - y[i]) //gsl_blas_dasum() - computes the absolute sum \sum |r_i| of the elements of the vector with the elements weight[i]*(Yi - y[i]) fitResult.sse = gsl_pow_2(gsl_blas_dnrm2(s->f)); if (fitResult.dof != 0) { fitResult.rms = fitResult.sse/fitResult.dof; fitResult.rsd = sqrt(fitResult.rms); } fitResult.mse = fitResult.sse/n; fitResult.rmse = sqrt(fitResult.mse); fitResult.mae = gsl_blas_dasum(s->f)/n; // SST needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); // for a linear model without intercept R-squared is calculated different // see https://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-does-summary_0028_0029-report-strange-results-for-the-R_005e2-estimate-when-I-fit-a-linear-model-with-no-intercept_003f if (fitData.modelCategory == nsl_fit_model_basic && fitData.modelType == nsl_fit_model_polynomial && fitData.degree == 1 && x_init[0] == 0) { DEBUG("Using alternative R^2 for linear model without intercept"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } if (fitResult.sst < fitResult.sse) { DEBUG("Using alternative R^2 since R^2 would be negative (probably custom model without intercept)"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } fitResult.rsquare = nsl_stats_rsquare(fitResult.sse, fitResult.sst); fitResult.rsquareAdj = nsl_stats_rsquareAdj(fitResult.rsquare, np, fitResult.dof, 1); fitResult.chisq_p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); fitResult.fdist_F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms, np, 1); fitResult.fdist_p = nsl_stats_fdist_p(fitResult.fdist_F, np, fitResult.dof); fitResult.logLik = nsl_stats_logLik(fitResult.sse, n); fitResult.aic = nsl_stats_aic(fitResult.sse, n, np, 1); fitResult.bic = nsl_stats_bic(fitResult.sse, n, np, 1); //parameter values // GSL: const double c = GSL_MAX_DBL(1., sqrt(fitResult.rms)); // increase error for poor fit // NIST: const double c = sqrt(fitResult.rms); // increase error for poor fit, decrease for good fit const double c = sqrt(fitResult.rms); fitResult.paramValues.resize(np); fitResult.errorValues.resize(np); fitResult.tdist_tValues.resize(np); fitResult.tdist_pValues.resize(np); fitResult.tdist_marginValues.resize(np); for (unsigned int i = 0; i < np; i++) { // scale resulting values if they are bounded fitResult.paramValues[i] = nsl_fit_map_bound(gsl_vector_get(s->x, i), x_min[i], x_max[i]); // use results as start values if desired if (fitData.useResults) { fitData.paramStartValues.data()[i] = fitResult.paramValues[i]; DEBUG("saving parameter " << i << ": " << fitResult.paramValues[i] << ' ' << fitData.paramStartValues.data()[i]); } fitResult.errorValues[i] = c*sqrt(gsl_matrix_get(covar, i, i)); fitResult.tdist_tValues[i] = nsl_stats_tdist_t(fitResult.paramValues.at(i), fitResult.errorValues.at(i)); fitResult.tdist_pValues[i] = nsl_stats_tdist_p(fitResult.tdist_tValues.at(i), fitResult.dof); fitResult.tdist_marginValues[i] = nsl_stats_tdist_margin(0.05, fitResult.dof, fitResult.errorValues.at(i)); } // fill residuals vector. To get residuals on the correct x values, fill the rest with zeros. residualsVector->resize(tmpXDataColumn->rowCount()); if (fitData.evaluateFullRange) { // evaluate full range of residuals xVector->resize(tmpXDataColumn->rowCount()); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*xVector)[i] = tmpXDataColumn->valueAt(i); ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = parser->evaluateCartesian(fitData.model, xVector, residualsVector, fitData.paramNames, fitResult.paramValues); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*residualsVector)[i] = tmpYDataColumn->valueAt(i) - (*residualsVector)[i]; if (!rc) residualsVector->clear(); } else { // only selected range size_t j = 0; for (int i = 0; i < tmpXDataColumn->rowCount(); i++) { if (tmpXDataColumn->valueAt(i) >= xmin && tmpXDataColumn->valueAt(i) <= xmax) residualsVector->data()[i] = - gsl_vector_get(s->f, j++); else // outside range residualsVector->data()[i] = 0; } } residualsColumn->setChanged(); //free resources gsl_multifit_fdfsolver_free(s); gsl_matrix_free(covar); //calculate the fit function (vectors) ExpressionParser* parser = ExpressionParser::getInstance(); if (fitData.evaluateFullRange) { // evaluate fit on full data range if selected xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } xVector->resize((int)fitData.evaluatedPoints); yVector->resize((int)fitData.evaluatedPoints); bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), (int)fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, fitResult.paramValues); if (!rc) { xVector->clear(); yVector->clear(); } fitResult.elapsedTime = timer.elapsed(); //redraw the curve - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } /*! * writes out the current state of the solver \c s */ void XYFitCurvePrivate::writeSolverState(gsl_multifit_fdfsolver* s) { QString state; //current parameter values, semicolon separated double* min = fitData.paramLowerLimits.data(); double* max = fitData.paramUpperLimits.data(); for (int i = 0; i < fitData.paramNames.size(); ++i) { const double x = gsl_vector_get(s->x, i); // map parameter if bounded state += QString::number(nsl_fit_map_bound(x, min[i], max[i])) + '\t'; } //current value of the chi2-function state += QString::number(gsl_pow_2(gsl_blas_dnrm2(s->f))); state += ';'; fitResult.solverOutput += state; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFitCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFitCurve); writer->writeStartElement("xyFitCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fit-curve specific information //fit data - only save model expression and parameter names for custom model, otherwise they are set in XYFitCurve::initFitData() writer->writeStartElement("fitData"); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("xRangeMin", QString::number(d->fitData.xRange.first(), 'g', 15)); writer->writeAttribute("xRangeMax", QString::number(d->fitData.xRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); writer->writeAttribute("xErrorsType", QString::number(d->fitData.xErrorsType)); writer->writeAttribute("weightsType", QString::number(d->fitData.yWeightsType)); writer->writeAttribute("degree", QString::number(d->fitData.degree)); if (d->fitData.modelCategory == nsl_fit_model_custom) writer->writeAttribute("model", d->fitData.model); writer->writeAttribute("maxIterations", QString::number(d->fitData.maxIterations)); writer->writeAttribute("eps", QString::number(d->fitData.eps, 'g', 15)); writer->writeAttribute("evaluatedPoints", QString::number(d->fitData.evaluatedPoints)); writer->writeAttribute("evaluateFullRange", QString::number(d->fitData.evaluateFullRange)); writer->writeAttribute("useDataErrors", QString::number(d->fitData.useDataErrors)); writer->writeAttribute("useResults", QString::number(d->fitData.useResults)); if (d->fitData.modelCategory == nsl_fit_model_custom) { writer->writeStartElement("paramNames"); foreach (const QString &name, d->fitData.paramNames) writer->writeTextElement("name", name); writer->writeEndElement(); } writer->writeStartElement("paramStartValues"); foreach (const double &value, d->fitData.paramStartValues) writer->writeTextElement("startValue", QString::number(value, 'g', 15)); writer->writeEndElement(); // use 16 digits to handle -DBL_MAX writer->writeStartElement("paramLowerLimits"); foreach (const double &limit, d->fitData.paramLowerLimits) writer->writeTextElement("lowerLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); // use 16 digits to handle DBL_MAX writer->writeStartElement("paramUpperLimits"); foreach (const double &limit, d->fitData.paramUpperLimits) writer->writeTextElement("upperLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); writer->writeStartElement("paramFixed"); foreach (const double &fixed, d->fitData.paramFixed) writer->writeTextElement("fixed", QString::number(fixed)); writer->writeEndElement(); writer->writeEndElement(); //"fitData" //fit results (generated columns and goodness of the fit) writer->writeStartElement("fitResult"); writer->writeAttribute("available", QString::number(d->fitResult.available)); writer->writeAttribute("valid", QString::number(d->fitResult.valid)); writer->writeAttribute("status", d->fitResult.status); writer->writeAttribute("iterations", QString::number(d->fitResult.iterations)); writer->writeAttribute("time", QString::number(d->fitResult.elapsedTime)); writer->writeAttribute("dof", QString::number(d->fitResult.dof)); writer->writeAttribute("sse", QString::number(d->fitResult.sse, 'g', 15)); writer->writeAttribute("sst", QString::number(d->fitResult.sst, 'g', 15)); writer->writeAttribute("rms", QString::number(d->fitResult.rms, 'g', 15)); writer->writeAttribute("rsd", QString::number(d->fitResult.rsd, 'g', 15)); writer->writeAttribute("mse", QString::number(d->fitResult.mse, 'g', 15)); writer->writeAttribute("rmse", QString::number(d->fitResult.rmse, 'g', 15)); writer->writeAttribute("mae", QString::number(d->fitResult.mae, 'g', 15)); writer->writeAttribute("rsquare", QString::number(d->fitResult.rsquare, 'g', 15)); writer->writeAttribute("rsquareAdj", QString::number(d->fitResult.rsquareAdj, 'g', 15)); writer->writeAttribute("chisq_p", QString::number(d->fitResult.chisq_p, 'g', 15)); writer->writeAttribute("fdist_F", QString::number(d->fitResult.fdist_F, 'g', 15)); writer->writeAttribute("fdist_p", QString::number(d->fitResult.fdist_p, 'g', 15)); writer->writeAttribute("aic", QString::number(d->fitResult.aic, 'g', 15)); writer->writeAttribute("bic", QString::number(d->fitResult.bic, 'g', 15)); writer->writeAttribute("solverOutput", d->fitResult.solverOutput); writer->writeStartElement("paramValues"); foreach (const double &value, d->fitResult.paramValues) writer->writeTextElement("value", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("errorValues"); foreach (const double &value, d->fitResult.errorValues) writer->writeTextElement("error", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_tValues"); foreach (const double &value, d->fitResult.tdist_tValues) writer->writeTextElement("tdist_t", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_pValues"); foreach (const double &value, d->fitResult.tdist_pValues) writer->writeTextElement("tdist_p", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_marginValues"); foreach (const double &value, d->fitResult.tdist_marginValues) writer->writeTextElement("tdist_margin", QString::number(value, 'g', 15)); writer->writeEndElement(); //save calculated columns if available if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->save(writer); d->yColumn->save(writer); d->residualsColumn->save(writer); } writer->writeEndElement(); //"fitResult" writer->writeEndElement(); //"xyFitCurve" } //! Load from XML bool XYFitCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFitCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", fitData.xRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); READ_INT_VALUE("xErrorsType", fitData.xErrorsType, nsl_fit_error_type); READ_INT_VALUE("weightsType", fitData.yWeightsType, nsl_fit_weight_type); READ_INT_VALUE("degree", fitData.degree, int); if (d->fitData.modelCategory == nsl_fit_model_custom) { READ_STRING_VALUE("model", fitData.model); DEBUG("read model = " << d->fitData.model.toStdString()); } READ_INT_VALUE("maxIterations", fitData.maxIterations, int); READ_DOUBLE_VALUE("eps", fitData.eps); READ_INT_VALUE("fittedPoints", fitData.evaluatedPoints, size_t); // old name READ_INT_VALUE("evaluatedPoints", fitData.evaluatedPoints, size_t); READ_INT_VALUE("evaluateFullRange", fitData.evaluateFullRange, bool); READ_INT_VALUE("useDataErrors", fitData.useDataErrors, bool); READ_INT_VALUE("useResults", fitData.useResults, bool); //set the model expression and the parameter names (can be derived from the saved values for category, type and degree) XYFitCurve::initFitData(d->fitData); } else if (!preview && reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); } else if (!preview && reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); } else if (!preview && reader->name() == "lowerLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // -DBL_MAX results in conversion error d->fitData.paramLowerLimits << x; else d->fitData.paramLowerLimits << -std::numeric_limits::max(); } else if (!preview && reader->name() == "upperLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // DBL_MAX results in conversion error d->fitData.paramUpperLimits << x; else d->fitData.paramUpperLimits << std::numeric_limits::max(); } else if (!preview && reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_t") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_p") { d->fitResult.tdist_pValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_margin") { d->fitResult.tdist_marginValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fitResult") { attribs = reader->attributes(); READ_INT_VALUE("available", fitResult.available, int); READ_INT_VALUE("valid", fitResult.valid, int); READ_STRING_VALUE("status", fitResult.status); READ_INT_VALUE("iterations", fitResult.iterations, int); READ_INT_VALUE("time", fitResult.elapsedTime, int); READ_DOUBLE_VALUE("dof", fitResult.dof); READ_DOUBLE_VALUE("sse", fitResult.sse); READ_DOUBLE_VALUE("sst", fitResult.sst); READ_DOUBLE_VALUE("rms", fitResult.rms); READ_DOUBLE_VALUE("rsd", fitResult.rsd); READ_DOUBLE_VALUE("mse", fitResult.mse); READ_DOUBLE_VALUE("rmse", fitResult.rmse); READ_DOUBLE_VALUE("mae", fitResult.mae); READ_DOUBLE_VALUE("rsquare", fitResult.rsquare); READ_DOUBLE_VALUE("rsquareAdj", fitResult.rsquareAdj); READ_DOUBLE_VALUE("chisq_p", fitResult.chisq_p); READ_DOUBLE_VALUE("fdist_F", fitResult.fdist_F); READ_DOUBLE_VALUE("fdist_p", fitResult.fdist_p); READ_DOUBLE_VALUE("aic", fitResult.aic); READ_DOUBLE_VALUE("bic", fitResult.bic); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else if (column->name() == "residuals") d->residualsColumn = column; } } if (preview) return true; // new fit model style (reset model type of old projects) if (d->fitData.modelCategory == nsl_fit_model_basic && d->fitData.modelType >= NSL_FIT_MODEL_BASIC_COUNT) { DEBUG("reset old fit model"); d->fitData.modelType = 0; d->fitData.degree = 1; // reset size of fields not touched by initFitData() d->fitData.paramStartValues.resize(2); d->fitData.paramFixed.resize(2); d->fitResult.paramValues.resize(2); d->fitResult.errorValues.resize(2); d->fitResult.tdist_tValues.resize(2); d->fitResult.tdist_pValues.resize(2); d->fitResult.tdist_marginValues.resize(2); } // not present in old projects if (d->fitResult.tdist_tValues.size() == 0) d->fitResult.tdist_tValues.resize(d->fitResult.paramValues.size()); if (d->fitResult.tdist_pValues.size() == 0) d->fitResult.tdist_pValues.resize(d->fitResult.paramValues.size()); if (d->fitResult.tdist_marginValues.size() == 0) d->fitResult.tdist_marginValues.resize(d->fitResult.paramValues.size()); // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); addChild(d->residualsColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); d->residualsVector = static_cast* >(d->residualsColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp index 859a51200..84a955310 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp @@ -1,398 +1,398 @@ /*************************************************************************** File : XYFourierFilterCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier filter -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFourierFilterCurve \brief A xy-curve defined by a Fourier filter \ingroup worksheet */ #include "XYFourierFilterCurve.h" #include "XYFourierFilterCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include #ifdef HAVE_FFTW3 #include #endif #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include // qWarning() XYFourierFilterCurve::XYFourierFilterCurve(const QString& name) : XYAnalysisCurve(name, new XYFourierFilterCurvePrivate(this)) { } XYFourierFilterCurve::XYFourierFilterCurve(const QString& name, XYFourierFilterCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYFourierFilterCurve::~XYFourierFilterCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierFilterCurve::recalculate() { Q_D(XYFourierFilterCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierFilterCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier_filter-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierFilterCurve, XYFourierFilterCurve::FilterData, filterData, filterData) const XYFourierFilterCurve::FilterResult& XYFourierFilterCurve::filterResult() const { Q_D(const XYFourierFilterCurve); return d->filterResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYFourierFilterCurve, SetFilterData, XYFourierFilterCurve::FilterData, filterData, recalculate); void XYFourierFilterCurve::setFilterData(const XYFourierFilterCurve::FilterData& filterData) { Q_D(XYFourierFilterCurve); exec(new XYFourierFilterCurveSetFilterDataCmd(d, filterData, i18n("%1: set filter options and perform the Fourier filter"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierFilterCurvePrivate::XYFourierFilterCurvePrivate(XYFourierFilterCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYFourierFilterCurvePrivate::~XYFourierFilterCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYFourierFilterCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create filter result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result filterResult = XYFourierFilterCurve::FilterResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { filterResult.available = true; filterResult.valid = false; filterResult.status = i18n("Number of x and y data points must be equal."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the differentiation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (filterData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = filterData.xRange.first(); xmax = filterData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to filter const size_t n = (size_t)xdataVector.size(); if (n == 0) { filterResult.available = true; filterResult.valid = false; filterResult.status = i18n("No data points available."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // filter settings const nsl_filter_type type = filterData.type; const nsl_filter_form form = filterData.form; const int order = filterData.order; const double cutoff = filterData.cutoff, cutoff2 = filterData.cutoff2; const nsl_filter_cutoff_unit unit = filterData.unit, unit2 = filterData.unit2; DEBUG("n ="< 0. Giving up."; return; } DEBUG("cut off @" << cutindex << cutindex2); DEBUG("bandwidth =" << bandwidth); // run filter int status = nsl_filter_fourier(ydata, n, type, form, order, cutindex, bandwidth); xVector->resize((int)n); yVector->resize((int)n); memcpy(xVector->data(), xdataVector.data(), n*sizeof(double)); memcpy(yVector->data(), ydata, n*sizeof(double)); /////////////////////////////////////////////////////////// //write the result filterResult.available = true; filterResult.valid = true; filterResult.status = gslErrorToString(status); filterResult.elapsedTime = timer.elapsed(); //redraw the curve - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierFilterCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierFilterCurve); writer->writeStartElement("xyFourierFilterCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fourier_filter-curve specific information //filter data writer->writeStartElement("filterData"); writer->writeAttribute( "autoRange", QString::number(d->filterData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->filterData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->filterData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->filterData.type) ); writer->writeAttribute( "form", QString::number(d->filterData.form) ); writer->writeAttribute( "order", QString::number(d->filterData.order) ); writer->writeAttribute( "cutoff", QString::number(d->filterData.cutoff) ); writer->writeAttribute( "unit", QString::number(d->filterData.unit) ); writer->writeAttribute( "cutoff2", QString::number(d->filterData.cutoff2) ); writer->writeAttribute( "unit2", QString::number(d->filterData.unit2) ); writer->writeEndElement();// filterData //filter results (generated columns) writer->writeStartElement("filterResult"); writer->writeAttribute( "available", QString::number(d->filterResult.available) ); writer->writeAttribute( "valid", QString::number(d->filterResult.valid) ); writer->writeAttribute( "status", d->filterResult.status ); writer->writeAttribute( "time", QString::number(d->filterResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"filterResult" writer->writeEndElement(); //"xyFourierFilterCurve" } //! Load from XML bool XYFourierFilterCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierFilterCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFourierFilterCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "filterData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", filterData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", filterData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", filterData.xRange.last()); READ_INT_VALUE("type", filterData.type, nsl_filter_type); READ_INT_VALUE("form", filterData.form, nsl_filter_form); READ_INT_VALUE("order", filterData.order, int); READ_DOUBLE_VALUE("cutoff", filterData.cutoff); READ_INT_VALUE("unit", filterData.unit, nsl_filter_cutoff_unit); READ_DOUBLE_VALUE("cutoff2", filterData.cutoff2); READ_INT_VALUE("unit2", filterData.unit2, nsl_filter_cutoff_unit); } else if (!preview && reader->name() == "filterResult") { attribs = reader->attributes(); READ_INT_VALUE("available", filterResult.available, int); READ_INT_VALUE("valid", filterResult.valid, int); READ_STRING_VALUE("status", filterResult.status); READ_INT_VALUE("time", filterResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } else qWarning()<<" d->xColumn == NULL!"; return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp index 2fe68d118..2bc840872 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp @@ -1,384 +1,384 @@ /*************************************************************************** File : XYFourierTransformCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier transform -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFourierTransformCurve \brief A xy-curve defined by a Fourier transform \ingroup worksheet */ #include "XYFourierTransformCurve.h" #include "XYFourierTransformCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include #include // qWarning() XYFourierTransformCurve::XYFourierTransformCurve(const QString& name) : XYAnalysisCurve(name, new XYFourierTransformCurvePrivate(this)) { } XYFourierTransformCurve::XYFourierTransformCurve(const QString& name, XYFourierTransformCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYFourierTransformCurve::~XYFourierTransformCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierTransformCurve::recalculate() { Q_D(XYFourierTransformCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierTransformCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier_transform-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierTransformCurve, XYFourierTransformCurve::TransformData, transformData, transformData) const XYFourierTransformCurve::TransformResult& XYFourierTransformCurve::transformResult() const { Q_D(const XYFourierTransformCurve); return d->transformResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYFourierTransformCurve, SetTransformData, XYFourierTransformCurve::TransformData, transformData, recalculate); void XYFourierTransformCurve::setTransformData(const XYFourierTransformCurve::TransformData& transformData) { Q_D(XYFourierTransformCurve); exec(new XYFourierTransformCurveSetTransformDataCmd(d, transformData, i18n("%1: set transform options and perform the Fourier transform"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierTransformCurvePrivate::XYFourierTransformCurvePrivate(XYFourierTransformCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYFourierTransformCurvePrivate::~XYFourierTransformCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYFourierTransformCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create transform result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result transformResult = XYFourierTransformCurve::TransformResult(); if (!xDataColumn || !yDataColumn) { - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (xDataColumn->rowCount()!=yDataColumn->rowCount()) { transformResult.available = true; transformResult.valid = false; transformResult.status = i18n("Number of x and y data points must be equal."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the transform to temporary vectors QVector xdataVector; QVector ydataVector; const double xmin = transformData.xRange.first(); const double xmax = transformData.xRange.last(); for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(xDataColumn->valueAt(row)) && !std::isnan(yDataColumn->valueAt(row)) && !xDataColumn->isMasked(row) && !yDataColumn->isMasked(row)) { // only when inside given range if (xDataColumn->valueAt(row) >= xmin && xDataColumn->valueAt(row) <= xmax) { xdataVector.append(xDataColumn->valueAt(row)); ydataVector.append(yDataColumn->valueAt(row)); } } } //number of data points to transform unsigned int n = (unsigned int)ydataVector.size(); if (n == 0) { transformResult.available = true; transformResult.valid = false; transformResult.status = i18n("No data points available."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // transform settings const nsl_sf_window_type windowType = transformData.windowType; const nsl_dft_result_type type = transformData.type; const bool twoSided = transformData.twoSided; const bool shifted = transformData.shifted; const nsl_dft_xscale xScale = transformData.xScale; DEBUG("n =" << n); DEBUG("window type:" << nsl_sf_window_type_name[windowType]); DEBUG("type:" << nsl_dft_result_type_name[type]); DEBUG("scale:" << nsl_dft_xscale_name[xScale]); DEBUG("two sided:" << twoSided); DEBUG("shifted:" << shifted); #ifndef NDEBUG QDebug out = qDebug(); for (unsigned int i=0; i < n; i++) out<= n/2 && shifted) xdata[i] = (n-1)/(xmax-xmin)*(i/(double)n-1.); else xdata[i] = (n-1)*i/(xmax-xmin)/n; } break; case nsl_dft_xscale_index: for (unsigned int i=0; i < N; i++) { if (i >= n/2 && shifted) xdata[i] = (int)i-(int) N; else xdata[i] = i; } break; case nsl_dft_xscale_period: { double f0 = (n-1)/(xmax-xmin)/n; for (unsigned int i=0; i < N; i++) { double f = (n-1)*i/(xmax-xmin)/n; xdata[i] = 1/(f+f0); } break; } } #ifndef NDEBUG out = qDebug(); for (unsigned int i=0; i < N; i++) out << ydata[i] << '(' << xdata[i] << ')'; #endif xVector->resize((int)N); yVector->resize((int)N); if(shifted) { memcpy(xVector->data(), &xdata[n/2], n/2*sizeof(double)); memcpy(&xVector->data()[n/2], xdata, n/2*sizeof(double)); memcpy(yVector->data(), &ydata[n/2], n/2*sizeof(double)); memcpy(&yVector->data()[n/2], ydata, n/2*sizeof(double)); } else { memcpy(xVector->data(), xdata, N*sizeof(double)); memcpy(yVector->data(), ydata, N*sizeof(double)); } /////////////////////////////////////////////////////////// //write the result transformResult.available = true; transformResult.valid = true; transformResult.status = gslErrorToString(status); transformResult.elapsedTime = timer.elapsed(); //redraw the curve - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierTransformCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierTransformCurve); writer->writeStartElement("xyFourierTransformCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fourier_transform-curve specific information //transform data writer->writeStartElement("transformData"); writer->writeAttribute( "autoRange", QString::number(d->transformData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->transformData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->transformData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->transformData.type) ); writer->writeAttribute( "twoSided", QString::number(d->transformData.twoSided) ); writer->writeAttribute( "shifted", QString::number(d->transformData.shifted) ); writer->writeAttribute( "xScale", QString::number(d->transformData.xScale) ); writer->writeAttribute( "windowType", QString::number(d->transformData.windowType) ); writer->writeEndElement();// transformData //transform results (generated columns) writer->writeStartElement("transformResult"); writer->writeAttribute( "available", QString::number(d->transformResult.available) ); writer->writeAttribute( "valid", QString::number(d->transformResult.valid) ); writer->writeAttribute( "status", d->transformResult.status ); writer->writeAttribute( "time", QString::number(d->transformResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"transformResult" writer->writeEndElement(); //"xyFourierTransformCurve" } //! Load from XML bool XYFourierTransformCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierTransformCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFourierTransformCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "transformData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", transformData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", transformData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", transformData.xRange.last()); READ_INT_VALUE("type", transformData.type, nsl_dft_result_type); READ_INT_VALUE("twoSided", transformData.twoSided, bool); READ_INT_VALUE("shifted", transformData.shifted, bool); READ_INT_VALUE("xScale", transformData.xScale, nsl_dft_xscale); READ_INT_VALUE("windowType", transformData.windowType, nsl_sf_window_type); } else if (!preview && reader->name() == "transformResult") { attribs = reader->attributes(); READ_INT_VALUE("available", transformResult.available, int); READ_INT_VALUE("valid", transformResult.valid, int); READ_STRING_VALUE("status", transformResult.status); READ_INT_VALUE("time", transformResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } else qWarning()<<" d->xColumn == NULL!"; return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp index 3ee9e96ed..11092a825 100644 --- a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp @@ -1,365 +1,365 @@ /*************************************************************************** File : XYIntegrationCurve.cpp Project : LabPlot Description : A xy-curve defined by an integration -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYIntegrationCurve \brief A xy-curve defined by an integration \ingroup worksheet */ #include "XYIntegrationCurve.h" #include "XYIntegrationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" extern "C" { #include } #include #include #include #include XYIntegrationCurve::XYIntegrationCurve(const QString& name) : XYAnalysisCurve(name, new XYIntegrationCurvePrivate(this)) { } XYIntegrationCurve::XYIntegrationCurve(const QString& name, XYIntegrationCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYIntegrationCurve::~XYIntegrationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYIntegrationCurve::recalculate() { Q_D(XYIntegrationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYIntegrationCurve::icon() const { return QIcon::fromTheme("labplot-xy-integration-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYIntegrationCurve, XYIntegrationCurve::IntegrationData, integrationData, integrationData) const XYIntegrationCurve::IntegrationResult& XYIntegrationCurve::integrationResult() const { Q_D(const XYIntegrationCurve); return d->integrationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYIntegrationCurve, SetIntegrationData, XYIntegrationCurve::IntegrationData, integrationData, recalculate); void XYIntegrationCurve::setIntegrationData(const XYIntegrationCurve::IntegrationData& integrationData) { Q_D(XYIntegrationCurve); exec(new XYIntegrationCurveSetIntegrationDataCmd(d, integrationData, i18n("%1: set options and perform the integration"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYIntegrationCurvePrivate::XYIntegrationCurvePrivate(XYIntegrationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYIntegrationCurvePrivate::~XYIntegrationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYIntegrationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create integration result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result integrationResult = XYIntegrationCurve::IntegrationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { integrationResult.available = true; integrationResult.valid = false; integrationResult.status = i18n("Number of x and y data points must be equal."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the integration to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (integrationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = integrationData.xRange.first(); xmax = integrationData.xRange.last(); } for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } const size_t n = (size_t)xdataVector.size(); // number of data points to integrate if (n < 2) { integrationResult.available = true; integrationResult.valid = false; integrationResult.status = i18n("Not enough data points available."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // integration settings const nsl_int_method_type method = integrationData.method; const bool absolute = integrationData.absolute; DEBUG("method:"<resize((int)np); yVector->resize((int)np); memcpy(xVector->data(), xdata, np * sizeof(double)); memcpy(yVector->data(), ydata, np * sizeof(double)); /////////////////////////////////////////////////////////// //write the result integrationResult.available = true; integrationResult.valid = true; integrationResult.status = QString::number(status); integrationResult.elapsedTime = timer.elapsed(); integrationResult.value = ydata[np-1]; //redraw the curve - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYIntegrationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYIntegrationCurve); writer->writeStartElement("xyIntegrationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-integration-curve specific information // integration data writer->writeStartElement("integrationData"); writer->writeAttribute( "autoRange", QString::number(d->integrationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->integrationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->integrationData.xRange.last()) ); writer->writeAttribute( "method", QString::number(d->integrationData.method) ); writer->writeAttribute( "absolute", QString::number(d->integrationData.absolute) ); writer->writeEndElement();// integrationData // integration results (generated columns) writer->writeStartElement("integrationResult"); writer->writeAttribute( "available", QString::number(d->integrationResult.available) ); writer->writeAttribute( "valid", QString::number(d->integrationResult.valid) ); writer->writeAttribute( "status", d->integrationResult.status ); writer->writeAttribute( "time", QString::number(d->integrationResult.elapsedTime) ); writer->writeAttribute( "value", QString::number(d->integrationResult.value) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"integrationResult" writer->writeEndElement(); //"xyIntegrationCurve" } //! Load from XML bool XYIntegrationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYIntegrationCurve); if (!reader->isStartElement() || reader->name() != "xyIntegrationCurve") { reader->raiseError(i18n("no xy integration curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyIntegrationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "integrationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", integrationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", integrationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", integrationData.xRange.last()); READ_INT_VALUE("method", integrationData.method, nsl_int_method_type); READ_INT_VALUE("absolute", integrationData.absolute, bool); } else if (!preview && reader->name() == "integrationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", integrationResult.available, int); READ_INT_VALUE("valid", integrationResult.valid, int); READ_STRING_VALUE("status", integrationResult.status); READ_INT_VALUE("time", integrationResult.elapsedTime, int); READ_DOUBLE_VALUE("value", integrationResult.value); } else if (!preview && reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp index 03928c8ba..75f375a05 100644 --- a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp @@ -1,554 +1,554 @@ /*************************************************************************** File : XYInterpolationCurve.cpp Project : LabPlot Description : A xy-curve defined by an interpolation -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYInterpolationCurve \brief A xy-curve defined by an interpolation \ingroup worksheet */ #include "XYInterpolationCurve.h" #include "XYInterpolationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include #include #include "backend/nsl/nsl_diff.h" #include "backend/nsl/nsl_int.h" } #include #include #include XYInterpolationCurve::XYInterpolationCurve(const QString& name) : XYAnalysisCurve(name, new XYInterpolationCurvePrivate(this)) { } XYInterpolationCurve::XYInterpolationCurve(const QString& name, XYInterpolationCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYInterpolationCurve::~XYInterpolationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYInterpolationCurve::recalculate() { Q_D(XYInterpolationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYInterpolationCurve::icon() const { return QIcon::fromTheme("labplot-xy-interpolation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, XYInterpolationCurve::InterpolationData, interpolationData, interpolationData) const XYInterpolationCurve::InterpolationResult& XYInterpolationCurve::interpolationResult() const { Q_D(const XYInterpolationCurve); return d->interpolationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYInterpolationCurve, SetInterpolationData, XYInterpolationCurve::InterpolationData, interpolationData, recalculate); void XYInterpolationCurve::setInterpolationData(const XYInterpolationCurve::InterpolationData& interpolationData) { Q_D(XYInterpolationCurve); exec(new XYInterpolationCurveSetInterpolationDataCmd(d, interpolationData, i18n("%1: set options and perform the interpolation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYInterpolationCurvePrivate::XYInterpolationCurvePrivate(XYInterpolationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYInterpolationCurvePrivate::~XYInterpolationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYInterpolationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create interpolation result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result interpolationResult = XYInterpolationCurve::InterpolationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Number of x and y data points must be equal."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the interpolation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (interpolationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = interpolationData.xRange.first(); xmax = interpolationData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to interpolate const size_t n = (size_t)xdataVector.size(); if (n < 2) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Not enough data points available."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // interpolation settings const nsl_interp_type type = interpolationData.type; const nsl_interp_pch_variant variant = interpolationData.variant; const double tension = interpolationData.tension; const double continuity = interpolationData.continuity; const double bias = interpolationData.bias; const nsl_interp_evaluate evaluate = interpolationData.evaluate; const size_t npoints = interpolationData.npoints; DEBUG("type:"<data(), yVector->data(), npoints); break; case nsl_interp_evaluate_second_derivative: nsl_diff_second_deriv_second_order(xVector->data(), yVector->data(), npoints); break; case nsl_interp_evaluate_integral: nsl_int_trapezoid(xVector->data(), yVector->data(), npoints, 0); break; } } // check values for (int i = 0; i < (int)npoints; i++) { if ((*yVector)[i] > std::numeric_limits::max()) (*yVector)[i] = std::numeric_limits::max(); else if ((*yVector)[i] < std::numeric_limits::lowest()) (*yVector)[i] = std::numeric_limits::lowest(); } gsl_spline_free(spline); gsl_interp_accel_free(acc); /////////////////////////////////////////////////////////// //write the result interpolationResult.available = true; interpolationResult.valid = true; interpolationResult.status = gslErrorToString(status); interpolationResult.elapsedTime = timer.elapsed(); //redraw the curve - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYInterpolationCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYInterpolationCurve); writer->writeStartElement("xyInterpolationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-interpolation-curve specific information // interpolation data writer->writeStartElement("interpolationData"); writer->writeAttribute( "autoRange", QString::number(d->interpolationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->interpolationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->interpolationData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->interpolationData.type) ); writer->writeAttribute( "variant", QString::number(d->interpolationData.variant) ); writer->writeAttribute( "tension", QString::number(d->interpolationData.tension) ); writer->writeAttribute( "continuity", QString::number(d->interpolationData.continuity) ); writer->writeAttribute( "bias", QString::number(d->interpolationData.bias) ); writer->writeAttribute( "npoints", QString::number(d->interpolationData.npoints) ); writer->writeAttribute( "pointsMode", QString::number(d->interpolationData.pointsMode) ); writer->writeAttribute( "evaluate", QString::number(d->interpolationData.evaluate) ); writer->writeEndElement();// interpolationData // interpolation results (generated columns) writer->writeStartElement("interpolationResult"); writer->writeAttribute( "available", QString::number(d->interpolationResult.available) ); writer->writeAttribute( "valid", QString::number(d->interpolationResult.valid) ); writer->writeAttribute( "status", d->interpolationResult.status ); writer->writeAttribute( "time", QString::number(d->interpolationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"interpolationResult" writer->writeEndElement(); //"xyInterpolationCurve" } //! Load from XML bool XYInterpolationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYInterpolationCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyInterpolationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "interpolationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", interpolationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", interpolationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", interpolationData.xRange.last()); READ_INT_VALUE("type", interpolationData.type, nsl_interp_type); READ_INT_VALUE("variant", interpolationData.variant, nsl_interp_pch_variant); READ_DOUBLE_VALUE("tension", interpolationData.tension); READ_DOUBLE_VALUE("continuity", interpolationData.continuity); READ_DOUBLE_VALUE("bias", interpolationData.bias); READ_INT_VALUE("npoints", interpolationData.npoints, size_t); READ_INT_VALUE("pointsMode", interpolationData.pointsMode, XYInterpolationCurve::PointsMode); READ_INT_VALUE("evaluate", interpolationData.evaluate, nsl_interp_evaluate); } else if (!preview && reader->name() == "interpolationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", interpolationResult.available, int); READ_INT_VALUE("valid", interpolationResult.valid, int); READ_STRING_VALUE("status", interpolationResult.status); READ_INT_VALUE("time", interpolationResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp index 51dbf1b63..ec98b98a7 100644 --- a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp @@ -1,383 +1,383 @@ /*************************************************************************** File : XYSmoothCurve.cpp Project : LabPlot Description : A xy-curve defined by a smooth -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYSmoothCurve \brief A xy-curve defined by a smooth \ingroup worksheet */ #include "XYSmoothCurve.h" #include "XYSmoothCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include extern "C" { #include // gsl_pow_* #include "backend/nsl/nsl_stats.h" #include "backend/nsl/nsl_sf_kernel.h" } XYSmoothCurve::XYSmoothCurve(const QString& name) : XYAnalysisCurve(name, new XYSmoothCurvePrivate(this)) { } XYSmoothCurve::XYSmoothCurve(const QString& name, XYSmoothCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYSmoothCurve::~XYSmoothCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYSmoothCurve::recalculate() { Q_D(XYSmoothCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYSmoothCurve::icon() const { return QIcon::fromTheme("labplot-xy-smooth-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYSmoothCurve, XYSmoothCurve::SmoothData, smoothData, smoothData) const XYSmoothCurve::SmoothResult& XYSmoothCurve::smoothResult() const { Q_D(const XYSmoothCurve); return d->smoothResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYSmoothCurve, SetSmoothData, XYSmoothCurve::SmoothData, smoothData, recalculate); void XYSmoothCurve::setSmoothData(const XYSmoothCurve::SmoothData& smoothData) { Q_D(XYSmoothCurve); exec(new XYSmoothCurveSetSmoothDataCmd(d, smoothData, i18n("%1: set options and perform the smooth"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYSmoothCurvePrivate::XYSmoothCurvePrivate(XYSmoothCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYSmoothCurvePrivate::~XYSmoothCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYSmoothCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create smooth result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result smoothResult = XYSmoothCurve::SmoothResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { smoothResult.available = true; smoothResult.valid = false; smoothResult.status = i18n("Number of x and y data points must be equal."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the smooth to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (smoothData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = smoothData.xRange.first(); xmax = smoothData.xRange.last(); } for (int row = 0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to smooth const size_t n = (size_t)xdataVector.size(); if (n < 2) { smoothResult.available = true; smoothResult.valid = false; smoothResult.status = i18n("Not enough data points available."); - emit (q->dataChanged()); + emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // smooth settings const nsl_smooth_type type = smoothData.type; const size_t points = smoothData.points; const nsl_smooth_weight_type weight = smoothData.weight; const double percentile = smoothData.percentile; const int order = smoothData.order; const nsl_smooth_pad_mode mode = smoothData.mode; const double lvalue = smoothData.lvalue; const double rvalue = smoothData.rvalue; DEBUG("type:"<writeStartElement("smoothData"); writer->writeAttribute( "autoRange", QString::number(d->smoothData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->smoothData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->smoothData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->smoothData.type) ); writer->writeAttribute( "points", QString::number(d->smoothData.points) ); writer->writeAttribute( "weight", QString::number(d->smoothData.weight) ); writer->writeAttribute( "percentile", QString::number(d->smoothData.percentile) ); writer->writeAttribute( "order", QString::number(d->smoothData.order) ); writer->writeAttribute( "mode", QString::number(d->smoothData.mode) ); writer->writeAttribute( "lvalue", QString::number(d->smoothData.lvalue) ); writer->writeAttribute( "rvalue", QString::number(d->smoothData.rvalue) ); writer->writeEndElement();// smoothData // smooth results (generated columns) writer->writeStartElement("smoothResult"); writer->writeAttribute( "available", QString::number(d->smoothResult.available) ); writer->writeAttribute( "valid", QString::number(d->smoothResult.valid) ); writer->writeAttribute( "status", d->smoothResult.status ); writer->writeAttribute( "time", QString::number(d->smoothResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"smoothResult" writer->writeEndElement(); //"xySmoothCurve" } //! Load from XML bool XYSmoothCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYSmoothCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xySmoothCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "smoothData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", smoothData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", smoothData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", smoothData.xRange.last()); READ_INT_VALUE("type", smoothData.type, nsl_smooth_type); READ_INT_VALUE("points", smoothData.points, size_t); READ_INT_VALUE("weight", smoothData.weight, nsl_smooth_weight_type); READ_DOUBLE_VALUE("percentile", smoothData.percentile); READ_INT_VALUE("order", smoothData.order, int); READ_INT_VALUE("mode", smoothData.mode, nsl_smooth_pad_mode); READ_DOUBLE_VALUE("lvalue", smoothData.lvalue); READ_DOUBLE_VALUE("rvalue", smoothData.rvalue); } else if (!preview && reader->name() == "smoothResult") { attribs = reader->attributes(); READ_INT_VALUE("available", smoothResult.available, int); READ_INT_VALUE("valid", smoothResult.valid, int); READ_STRING_VALUE("status", smoothResult.status); READ_INT_VALUE("time", smoothResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/commonfrontend/ProjectExplorer.cpp b/src/commonfrontend/ProjectExplorer.cpp index 6ec4443cb..ed474febf 100644 --- a/src/commonfrontend/ProjectExplorer.cpp +++ b/src/commonfrontend/ProjectExplorer.cpp @@ -1,851 +1,851 @@ /*************************************************************************** File : ProjectExplorer.cpp Project : LabPlot Description : A tree view for displaying and editing an AspectTreeModel. -------------------------------------------------------------------- Copyright : (C) 2007-2008 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2010-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ProjectExplorer.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/AbstractPart.h" #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/core/PartMdiView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ProjectExplorer \brief A tree view for displaying and editing an AspectTreeModel. In addition to the functionality of QTreeView, ProjectExplorer allows the usage of the context menus provided by AspectTreeModel and propagates the item selection in the view to the model. Furthermore, features for searching and filtering in the model are provided. \ingroup commonfrontend */ ProjectExplorer::ProjectExplorer(QWidget* parent) : m_columnToHide(0), m_treeView(new QTreeView(parent)), m_project(nullptr), m_dragStarted(false), m_frameFilter(new QFrame(this)) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); QHBoxLayout* layoutFilter= new QHBoxLayout(m_frameFilter); layoutFilter->setSpacing(0); layoutFilter->setContentsMargins(0, 0, 0, 0); QLabel* lFilter = new QLabel(i18n("Search/Filter:")); layoutFilter->addWidget(lFilter); m_leFilter = new QLineEdit(m_frameFilter); m_leFilter->setClearButtonEnabled(true); m_leFilter->setPlaceholderText(i18n("Search/Filter text")); layoutFilter->addWidget(m_leFilter); bFilterOptions = new QPushButton(m_frameFilter); bFilterOptions->setIcon(QIcon::fromTheme("configure")); bFilterOptions->setCheckable(true); layoutFilter->addWidget(bFilterOptions); layout->addWidget(m_frameFilter); m_treeView->setAnimated(true); m_treeView->setAlternatingRowColors(true); m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_treeView->setUniformRowHeights(true); m_treeView->viewport()->installEventFilter(this); m_treeView->header()->setStretchLastSection(true); m_treeView->header()->installEventFilter(this); m_treeView->setDragEnabled(true); m_treeView->setAcceptDrops(true); m_treeView->setDropIndicatorShown(true); m_treeView->setDragDropMode(QAbstractItemView::InternalMove); layout->addWidget(m_treeView); this->createActions(); connect(m_leFilter, SIGNAL(textChanged(QString)), this, SLOT(filterTextChanged(QString))); connect(bFilterOptions, SIGNAL(toggled(bool)), this, SLOT(toggleFilterOptionsMenu(bool))); } void ProjectExplorer::createActions() { caseSensitiveAction = new QAction(i18n("case sensitive"), this); caseSensitiveAction->setCheckable(true); caseSensitiveAction->setChecked(false); connect(caseSensitiveAction, SIGNAL(triggered()), this, SLOT(toggleFilterCaseSensitivity())); matchCompleteWordAction = new QAction(i18n("match complete word"), this); matchCompleteWordAction->setCheckable(true); matchCompleteWordAction->setChecked(false); connect(matchCompleteWordAction, SIGNAL(triggered()), this, SLOT(toggleFilterMatchCompleteWord())); expandTreeAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("expand all"), this); connect(expandTreeAction, SIGNAL(triggered()), m_treeView, SLOT(expandAll())); expandSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("expand selected"), this); connect(expandSelectedTreeAction, SIGNAL(triggered()), this, SLOT(expandSelected())); collapseTreeAction = new QAction(i18n("collapse all"), this); connect(collapseTreeAction, SIGNAL(triggered()), m_treeView, SLOT(collapseAll())); collapseSelectedTreeAction = new QAction(i18n("collapse selected"), this); connect(collapseSelectedTreeAction, SIGNAL(triggered()), this, SLOT(collapseSelected())); deleteSelectedTreeAction = new QAction(QIcon::fromTheme("edit-delete"), i18n("delete selected"), this); connect(deleteSelectedTreeAction, SIGNAL(triggered()), this, SLOT(deleteSelected())); toggleFilterAction = new QAction(QIcon::fromTheme(QLatin1String("view-filter")), i18n("hide search/filter options"), this); connect(toggleFilterAction, SIGNAL(triggered()), this, SLOT(toggleFilterWidgets())); showAllColumnsAction = new QAction(i18n("show all"),this); showAllColumnsAction->setCheckable(true); showAllColumnsAction->setChecked(true); showAllColumnsAction->setEnabled(false); connect(showAllColumnsAction, SIGNAL(triggered()), this, SLOT(showAllColumns())); } /*! shows the context menu in the tree. In addition to the context menu of the currently selected aspect, treeview specific options are added. */ void ProjectExplorer::contextMenuEvent(QContextMenuEvent *event) { if(!m_treeView->model()) return; const QModelIndex& index = m_treeView->indexAt(m_treeView->viewport()->mapFrom(this, event->pos())); if (!index.isValid()) m_treeView->clearSelection(); const QModelIndexList& items = m_treeView->selectionModel()->selectedIndexes(); QMenu* menu = nullptr; if (items.size()/4 == 1) { AbstractAspect* aspect = static_cast(index.internalPointer()); menu = aspect->createContextMenu(); } else { menu = new QMenu(); if (items.size()/4 > 1) { menu->addAction(expandSelectedTreeAction); menu->addAction(collapseSelectedTreeAction); menu->addSeparator(); menu->addAction(deleteSelectedTreeAction); menu->addSeparator(); } else { menu->addAction(expandTreeAction); menu->addAction(collapseTreeAction); menu->addSeparator(); menu->addAction(toggleFilterAction); //Menu for showing/hiding the columns in the tree view QMenu* columnsMenu = menu->addMenu(i18n("show/hide columns")); columnsMenu->addAction(showAllColumnsAction); columnsMenu->addSeparator(); for (int i=0; iaddAction(list_showColumnActions.at(i)); //TODO //Menu for showing/hiding the top-level aspects (Worksheet, Spreadhsheet, etc) in the tree view // QMenu* objectsMenu = menu->addMenu(i18n("show/hide objects")); } } menu->exec(event->globalPos()); delete menu; } void ProjectExplorer::setCurrentAspect(const AbstractAspect* aspect) { const AspectTreeModel* tree_model = qobject_cast(m_treeView->model()); if(tree_model) m_treeView->setCurrentIndex(tree_model->modelIndexOfAspect(aspect)); } /*! Sets the \c model for the tree view to present. */ void ProjectExplorer::setModel(AspectTreeModel* treeModel) { m_treeView->setModel(treeModel); connect(treeModel, SIGNAL(renameRequested(QModelIndex)), m_treeView, SLOT(edit(QModelIndex))); connect(treeModel, SIGNAL(indexSelected(QModelIndex)), this, SLOT(selectIndex(QModelIndex))); connect(treeModel, SIGNAL(indexDeselected(QModelIndex)), this, SLOT(deselectIndex(QModelIndex))); connect(treeModel, SIGNAL(hiddenAspectSelected(const AbstractAspect*)), this, SIGNAL(hiddenAspectSelected(const AbstractAspect*))); connect(m_treeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentChanged(QModelIndex,QModelIndex)) ); connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged(QItemSelection,QItemSelection)) ); //create action for showing/hiding the columns in the tree. //this is done here since the number of columns is not available in createActions() yet. if (list_showColumnActions.size()==0) { showColumnsSignalMapper = new QSignalMapper(this); for (int i=0; imodel()->columnCount(); i++) { QAction* showColumnAction = new QAction(treeModel->headerData(i, Qt::Horizontal).toString(), this); showColumnAction->setCheckable(true); showColumnAction->setChecked(true); list_showColumnActions.append(showColumnAction); connect(showColumnAction, SIGNAL(triggered(bool)), showColumnsSignalMapper, SLOT(map())); showColumnsSignalMapper->setMapping(showColumnAction, i); } connect(showColumnsSignalMapper, SIGNAL(mapped(int)), this, SLOT(toggleColumn(int))); } else { for (int i=0; iisChecked()) m_treeView->hideColumn(i); } } } void ProjectExplorer::setProject(Project* project) { connect(project, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(aspectAdded(const AbstractAspect*))); connect(project, SIGNAL(requestSaveState(QXmlStreamWriter*)), this, SLOT(save(QXmlStreamWriter*))); connect(project, SIGNAL(requestLoadState(XmlStreamReader*)), this, SLOT(load(XmlStreamReader*))); connect(project, SIGNAL(requestNavigateTo(QString)), this, SLOT(navigateTo(QString))); connect(project, SIGNAL(loaded()), this, SLOT(resizeHeader())); m_project = project; //for newly created projects, resize the header to fit the size of the header section names. //for projects loaded from a file, this function will be called laterto fit the sizes //of the content once the project is loaded resizeHeader(); } QModelIndex ProjectExplorer::currentIndex() const { return m_treeView->currentIndex(); } /*! handles the contextmenu-event of the horizontal header in the tree view. Provides a menu for selective showing and hiding of columns. */ bool ProjectExplorer::eventFilter(QObject* obj, QEvent* event) { if (obj == m_treeView->header() && event->type() == QEvent::ContextMenu) { //Menu for showing/hiding the columns in the tree view QMenu* columnsMenu = new QMenu(m_treeView->header()); columnsMenu->addSection(i18n("Columns")); columnsMenu->addAction(showAllColumnsAction); columnsMenu->addSeparator(); for (int i=0; iaddAction(list_showColumnActions.at(i)); QContextMenuEvent* e = static_cast(event); columnsMenu->exec(e->globalPos()); delete columnsMenu; return true; } else if (obj == m_treeView->viewport()) { QMouseEvent* e = static_cast(event); if (event->type() == QEvent::MouseButtonPress) { if (e->button() == Qt::LeftButton) { m_dragStartPos = e->globalPos(); m_dragStarted = false; } } else if (event->type() == QEvent::MouseMove) { if ( !m_dragStarted && m_treeView->selectionModel()->selectedIndexes().size() > 0 && (e->globalPos() - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { m_dragStarted = true; QDrag* drag = new QDrag(this); QMimeData* mimeData = new QMimeData; //determine the selected objects and serialize the pointers to QMimeData QVector vec; QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); //there are four model indices in each row -> divide by 4 to obtain the number of selected rows (=aspects) for (int i = 0; i < items.size()/4; ++i) { const QModelIndex& index = items.at(i*4); AbstractAspect* aspect = static_cast(index.internalPointer()); vec << (quintptr)aspect; } QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << vec; mimeData->setData("labplot-dnd", data); drag->setMimeData(mimeData); drag->exec(); } } else if (event->type() == QEvent::DragEnter) { //ignore events not related to internal drags of columns etc., e.g. dropping of external files onto LabPlot QDragEnterEvent* dragEnterEvent = static_cast(event); const QMimeData* mimeData = dragEnterEvent->mimeData(); if (!mimeData) { event->ignore(); return false; } if (mimeData->formats().at(0) != QLatin1String("labplot-dnd")) { event->ignore(); return false; } event->setAccepted(true); } else if (event->type() == QEvent::DragMove) { // only accept drop events if we have a plot under the cursor where we can drop columns onto QDragMoveEvent* dragMoveEvent = static_cast(event); QModelIndex index = m_treeView->indexAt(dragMoveEvent->pos()); if (!index.isValid()) return false; AbstractAspect* aspect = static_cast(index.internalPointer()); const bool isPlot = (dynamic_cast(aspect) != nullptr); event->setAccepted(isPlot); } else if (event->type() == QEvent::Drop) { QDropEvent* dropEvent = static_cast(event); QModelIndex index = m_treeView->indexAt(dropEvent->pos()); if (!index.isValid()) return false; AbstractAspect* aspect = static_cast(index.internalPointer()); CartesianPlot* plot = dynamic_cast(aspect); if (plot != nullptr) plot->processDropEvent(dropEvent); } } return QObject::eventFilter(obj, event); } //############################################################################## //################################# SLOTS #################################### //############################################################################## /*! expand the aspect \c aspect (the tree index corresponding to it) in the tree view and makes it visible and selected. Called when a new aspect is added to the project. */ void ProjectExplorer::aspectAdded(const AbstractAspect* aspect) { if (m_project->isLoading()) return; //don't do anything if hidden aspects were added if (aspect->hidden()) return; //don't do anything for newly added data spreadsheets of data picker curves if (aspect->inherits("Spreadsheet") && aspect->parentAspect()->inherits("DatapickerCurve")) return; const AspectTreeModel* tree_model = qobject_cast(m_treeView->model()); const QModelIndex& index = tree_model->modelIndexOfAspect(aspect); //expand and make the aspect visible m_treeView->setExpanded(index, true); // newly added columns are only expanded but not selected, return here if ( aspect->inherits("Column") ) { m_treeView->setExpanded(tree_model->modelIndexOfAspect(aspect->parentAspect()), true); return; } m_treeView->scrollTo(index); m_treeView->setCurrentIndex(index); m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2); } void ProjectExplorer::navigateTo(const QString& path) { const AspectTreeModel* tree_model = qobject_cast(m_treeView->model()); if(tree_model) m_treeView->setCurrentIndex(tree_model->modelIndexOfAspect(path)); } void ProjectExplorer::currentChanged(const QModelIndex & current, const QModelIndex & previous) { Q_UNUSED(previous); emit currentAspectChanged(static_cast(current.internalPointer())); } void ProjectExplorer::toggleColumn(int index) { //determine the total number of checked column actions int checked = 0; for (const auto* action : list_showColumnActions) { if (action->isChecked()) checked++; } if (list_showColumnActions.at(index)->isChecked()) { m_treeView->showColumn(index); m_treeView->header()->resizeSection(0,0 ); m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); for (auto* action : list_showColumnActions) action->setEnabled(true); //deactivate the "show all column"-action, if all actions are checked if ( checked == list_showColumnActions.size() ) { showAllColumnsAction->setEnabled(false); showAllColumnsAction->setChecked(true); } } else { m_treeView->hideColumn(index); showAllColumnsAction->setEnabled(true); showAllColumnsAction->setChecked(false); //if there is only one checked column-action, deactivated it. //It should't be possible to hide all columns if ( checked == 1 ) { int i=0; while( !list_showColumnActions.at(i)->isChecked() ) i++; list_showColumnActions.at(i)->setEnabled(false); } } } void ProjectExplorer::showAllColumns() { for (int i=0; imodel()->columnCount(); i++) { m_treeView->showColumn(i); m_treeView->header()->resizeSection(0,0 ); m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); } showAllColumnsAction->setEnabled(false); for (auto* action : list_showColumnActions) { action->setEnabled(true); action->setChecked(true); } } /*! shows/hides the frame with the search/filter widgets */ void ProjectExplorer::toggleFilterWidgets() { if (m_frameFilter->isVisible()) { m_frameFilter->hide(); toggleFilterAction->setText(i18n("show search/filter options")); } else { m_frameFilter->show(); toggleFilterAction->setText(i18n("hide search/filter options")); } } /*! toggles the menu for the filter/search options */ void ProjectExplorer::toggleFilterOptionsMenu(bool checked) { if (checked) { QMenu menu; menu.addAction(caseSensitiveAction); menu.addAction(matchCompleteWordAction); connect(&menu, SIGNAL(aboutToHide()), bFilterOptions, SLOT(toggle())); menu.exec(bFilterOptions->mapToGlobal(QPoint(0,bFilterOptions->height()))); } } void ProjectExplorer::resizeHeader() { m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2); //make the column "Name" somewhat bigger } /*! called when the filter/search text was changend. */ void ProjectExplorer::filterTextChanged(const QString& text) { QModelIndex root = m_treeView->model()->index(0,0); filter(root, text); } bool ProjectExplorer::filter(const QModelIndex& index, const QString& text) { Qt::CaseSensitivity sensitivity = caseSensitiveAction->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive; bool matchCompleteWord = matchCompleteWordAction->isChecked(); bool childVisible = false; const int rows = index.model()->rowCount(index); for (int i=0; i(child.internalPointer()); bool visible; if(text.isEmpty()) visible = true; else if (matchCompleteWord) visible = aspect->name().startsWith(text, sensitivity); else visible = aspect->name().contains(text, sensitivity); if (visible) { //current item is visible -> make all its children visible without applying the filter for (int j=0; jrowCount(child); ++j) { m_treeView->setRowHidden(j, child, false); if(text.isEmpty()) filter(child, text); } childVisible = true; } else { //check children items. if one of the children is visible, make the parent (current) item visible too. visible = filter(child, text); if (visible) childVisible = true; } m_treeView->setRowHidden(i, index, !visible); } return childVisible; } void ProjectExplorer::toggleFilterCaseSensitivity() { filterTextChanged(m_leFilter->text()); } void ProjectExplorer::toggleFilterMatchCompleteWord() { filterTextChanged(m_leFilter->text()); } void ProjectExplorer::selectIndex(const QModelIndex& index) { if (m_project->isLoading()) return; if ( !m_treeView->selectionModel()->isSelected(index) ) { m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); m_treeView->setExpanded(index, true); m_treeView->scrollTo(index); } } void ProjectExplorer::deselectIndex(const QModelIndex & index) { if (m_project->isLoading()) return; if ( m_treeView->selectionModel()->isSelected(index) ) m_treeView->selectionModel()->select(index, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); } void ProjectExplorer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndex index; QModelIndexList items; AbstractAspect* aspect = 0; //there are four model indices in each row //-> divide by 4 to obtain the number of selected rows (=aspects) items = selected.indexes(); for (int i=0; i(index.internalPointer()); aspect->setSelected(true); } items = deselected.indexes(); for (int i = 0; i < items.size()/4; ++i) { index = items.at(i*4); aspect = static_cast(index.internalPointer()); aspect->setSelected(false); } items = m_treeView->selectionModel()->selectedRows(); QList selectedAspects; for (const QModelIndex& index : items) { aspect = static_cast(index.internalPointer()); selectedAspects<selectionModel()->selectedIndexes(); for (const auto& index : items) m_treeView->setExpanded(index, true); } void ProjectExplorer::collapseSelected() { const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); for (const auto& index : items) m_treeView->setExpanded(index, false); } void ProjectExplorer::deleteSelected() { const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); if (!items.size()) return; int rc = KMessageBox::warningYesNo( this, i18np("Do you really want to delete the selected object?", "Do you really want to delete the selected %1 objects?", items.size()/4), i18np("Delete selected object", "Delete selected objects", items.size()/4)); if (rc == KMessageBox::No) return; m_project->beginMacro(i18np("Project Explorer: delete %1 selected object", "Project Explorer: delete %1 selected objects", items.size()/4)); for (int i = 0; i < items.size()/4; ++i) { const QModelIndex& index = items.at(i*4); AbstractAspect* aspect = static_cast(index.internalPointer()); aspect->remove(); } m_project->endMacro(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## struct ViewState { Qt::WindowStates state; QRect geometry; }; /** * \brief Save the current state of the tree view * (expanded items and the currently selected item) as XML */ void ProjectExplorer::save(QXmlStreamWriter* writer) const { AspectTreeModel* model = qobject_cast(m_treeView->model()); QList selected; QList expanded; QList withView; - QList viewStates; + QVector viewStates; int currentRow = -1; //row corresponding to the current index in the tree view, -1 for the root element (=project) QModelIndexList selectedRows = m_treeView->selectionModel()->selectedRows(); //check whether the project node itself is expanded if (m_treeView->isExpanded(m_treeView->model()->index(0,0))) expanded.push_back(-1); int row = 0; QVector aspects = const_cast(m_project)->children("AbstractAspect", AbstractAspect::Recursive); for (const auto* aspect : aspects) { const QModelIndex& index = model->modelIndexOfAspect(aspect); const AbstractPart* part = dynamic_cast(aspect); if (part && part->hasMdiSubWindow()) { withView.push_back(row); ViewState s = {part->view()->windowState(), part->view()->geometry()}; viewStates.push_back(s); } if (model->rowCount(index)>0 && m_treeView->isExpanded(index)) expanded.push_back(row); if (selectedRows.indexOf(index) != -1) selected.push_back(row); if (index == m_treeView->currentIndex()) currentRow = row; row++; } writer->writeStartElement("state"); writer->writeStartElement("expanded"); for (int i = 0; i < expanded.size(); ++i) writer->writeTextElement("row", QString::number(expanded.at(i))); writer->writeEndElement(); writer->writeStartElement("selected"); for (int i = 0; i < selected.size(); ++i) writer->writeTextElement("row", QString::number(selected.at(i))); writer->writeEndElement(); writer->writeStartElement("view"); for (int i = 0; i < withView.size(); ++i) { writer->writeStartElement("row"); const ViewState& s = viewStates.at(i); writer->writeAttribute( "state", QString::number(s.state) ); writer->writeAttribute( "x", QString::number(s.geometry.x()) ); writer->writeAttribute( "y", QString::number(s.geometry.y()) ); writer->writeAttribute( "width", QString::number(s.geometry.width()) ); writer->writeAttribute( "height", QString::number(s.geometry.height()) ); writer->writeCharacters(QString::number(withView.at(i))); writer->writeEndElement(); } writer->writeEndElement(); writer->writeStartElement("current"); writer->writeTextElement("row", QString::number(currentRow)); writer->writeEndElement(); writer->writeEndElement(); } /** * \brief Load from XML */ bool ProjectExplorer::load(XmlStreamReader* reader) { const AspectTreeModel* model = qobject_cast(m_treeView->model()); const QVector aspects = const_cast(m_project)->children("AbstractAspect", AbstractAspect::Recursive); bool expandedItem = false; bool selectedItem = false; bool viewItem = false; (void)viewItem; // because of a strange g++-warning about unused viewItem bool currentItem = false; QModelIndex currentIndex; QString str; int row; - QList selected; + QVector selected; QList expanded; QXmlStreamAttributes attribs; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "state") break; if (!reader->isStartElement()) continue; if (reader->name() == "expanded") { expandedItem = true; selectedItem = false; viewItem = false; currentItem = false; } else if (reader->name() == "selected") { expandedItem = false; selectedItem = true; viewItem = false; currentItem = false; } else if (reader->name() == "view") { expandedItem = false; selectedItem = false; viewItem = true; currentItem = false; } else if (reader->name() == "current") { expandedItem = false; selectedItem = false; viewItem = false; currentItem = true; } else if (reader->name() == "row") { attribs = reader->attributes(); row = reader->readElementText().toInt(); QModelIndex index; if (row == -1) index = model->modelIndexOfAspect(m_project); //-1 corresponds tothe project-item (s.a. ProjectExplorer::save()) else if (row >= aspects.size()) continue; else index = model->modelIndexOfAspect(aspects.at(row)); if (expandedItem) expanded.push_back(index); else if (selectedItem) selected.push_back(index); else if (currentItem) currentIndex = index; else if (viewItem) { AbstractPart* part = dynamic_cast(aspects.at(row)); if (!part) continue; //TODO: add error/warning message here? emit currentAspectChanged(part); str = attribs.value("state").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'state'")); else { part->view()->setWindowState(Qt::WindowStates(str.toInt())); part->mdiSubWindow()->setWindowState(Qt::WindowStates(str.toInt())); } if (str != "0") continue; //no geometry settings required for maximized/minimized windows QRect geometry; str = attribs.value("x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else geometry.setX(str.toInt()); str = attribs.value("y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else geometry.setY(str.toInt()); str = attribs.value("width").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'width'")); else geometry.setWidth(str.toInt()); str = attribs.value("height").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'height'")); else geometry.setHeight(str.toInt()); part->mdiSubWindow()->setGeometry(geometry); } } } for (const auto& index : expanded) { m_treeView->setExpanded(index, true); collapseParents(index, expanded);//collapse all parent indices if they are not expanded } for (const auto& index : selected) m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); m_treeView->setCurrentIndex(currentIndex); m_treeView->scrollTo(currentIndex); //when setting the current index above it gets expanded, collapse all parent indices if they are were not expanded when saved collapseParents(currentIndex, expanded); return true; } void ProjectExplorer::collapseParents(const QModelIndex& index, const QList& expanded) { //root index doesn't have any parents - this case is not caught by the second if-statement below if (index.column()==0 && index.row()==0) return; const QModelIndex parent = index.parent(); if (parent == QModelIndex()) return; if (expanded.indexOf(parent) == -1) m_treeView->collapse(parent); } diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.h b/src/commonfrontend/spreadsheet/SpreadsheetView.h index d02b8a917..a7e56257b 100644 --- a/src/commonfrontend/spreadsheet/SpreadsheetView.h +++ b/src/commonfrontend/spreadsheet/SpreadsheetView.h @@ -1,265 +1,264 @@ /*************************************************************************** File : SpreadsheetView.h Project : LabPlot Description : View class for Spreadsheet -------------------------------------------------------------------- Copyright : (C) 2010-2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef SPREADSHEETVIEW_H #define SPREADSHEETVIEW_H #include #include "backend/core/AbstractColumn.h" #include "backend/lib/IntervalAttribute.h" class Column; class Spreadsheet; class SpreadsheetModel; class SpreadsheetItemDelegate; class SpreadsheetHeaderView; class AbstractAspect; class QTableView; class QPrinter; class QMenu; class QToolBar; class QModelIndex; class QItemSelection; class SpreadsheetView : public QWidget { Q_OBJECT public: explicit SpreadsheetView(Spreadsheet* spreadsheet, bool readOnly = false); ~SpreadsheetView() override; void resizeHeader(); void showComments(bool on = true); bool areCommentsShown() const; int selectedColumnCount(bool full = false) const; int selectedColumnCount(AbstractColumn::PlotDesignation) const; bool isColumnSelected(int col, bool full = false) const; QVector selectedColumns(bool full = false) const; int firstSelectedColumn(bool full = false) const; int lastSelectedColumn(bool full = false) const; bool isRowSelected(int row, bool full = false) const; int firstSelectedRow(bool full = false) const; int lastSelectedRow(bool full = false) const; IntervalAttribute selectedRows(bool full = false) const; bool isCellSelected(int row, int col) const; void setCellSelected(int row, int col, bool select = true); void setCellsSelected(int first_row, int first_col, int last_row, int last_col, bool select = true); void getCurrentCell(int* row, int* col) const; bool exportView(); bool printView(); bool printPreview(); private: void init(); void initActions(); void initMenus(); void connectActions(); - + bool formulaModeActive() const; void exportToFile(const QString&, const bool, const QString&) const; void exportToLaTeX(const QString&, const bool exportHeaders, const bool gridLines, const bool captions, const bool latexHeaders, const bool skipEmptyRows,const bool exportEntire) const; void exportToFits(const QString &fileName, const int exportTo, const bool commentsAsUnits) const; QTableView* m_tableView; Spreadsheet* m_spreadsheet; SpreadsheetItemDelegate* m_delegate; SpreadsheetModel* m_model; SpreadsheetHeaderView* m_horizontalHeader; bool m_suppressSelectionChangedEvent; bool m_readOnly; bool eventFilter(QObject*, QEvent*) override; void checkSpreadsheetMenu(); //selection related actions QAction* action_cut_selection; QAction* action_copy_selection; QAction* action_paste_into_selection; QAction* action_mask_selection; QAction* action_unmask_selection; QAction* action_clear_selection; // QAction* action_set_formula; // QAction* action_recalculate; QAction* action_fill_row_numbers; QAction* action_fill_sel_row_numbers; QAction* action_fill_random; QAction* action_fill_equidistant; QAction* action_fill_random_nonuniform; QAction* action_fill_const; QAction* action_fill_function; //spreadsheet related actions QAction* action_toggle_comments; QAction* action_select_all; QAction* action_clear_spreadsheet; QAction* action_clear_masks; QAction* action_sort_spreadsheet; QAction* action_go_to_cell; QAction* action_statistics_all_columns; //column related actions QAction* action_insert_column_left; QAction* action_insert_column_right; QAction* action_remove_columns; QAction* action_clear_columns; QAction* action_add_columns; QAction* action_set_as_none; QAction* action_set_as_x; QAction* action_set_as_y; QAction* action_set_as_z; QAction* action_set_as_xerr; QAction* action_set_as_xerr_plus; QAction* action_set_as_xerr_minus; QAction* action_set_as_yerr; QAction* action_set_as_yerr_plus; QAction* action_set_as_yerr_minus; QAction* action_reverse_columns; QAction* action_drop_values; QAction* action_mask_values; QAction* action_join_columns; QAction* action_normalize_columns; QAction* action_normalize_selection; QAction* action_sort_columns; QAction* action_sort_asc_column; QAction* action_sort_desc_column; QAction* action_statistics_columns; //row related actions QAction* action_insert_row_above; QAction* action_insert_row_below; QAction* action_remove_rows; QAction* action_clear_rows; QAction* action_statistics_rows; //analysis and plot data menu actions QAction* action_plot_data; QAction* addDataOperationAction; QAction* addDataReductionAction; QAction* addDifferentiationAction; QAction* addIntegrationAction; QAction* addInterpolationAction; QAction* addSmoothAction; QVector addFitAction; QAction* addFourierFilterAction; //Menus QMenu* m_selectionMenu; QMenu* m_columnMenu; QMenu* m_columnSetAsMenu; QMenu* m_columnGenerateDataMenu; QMenu* m_columnSortMenu; QMenu* m_rowMenu; QMenu* m_spreadsheetMenu; QMenu* m_analyzePlotMenu; public slots: void createContextMenu(QMenu*); void fillToolBar(QToolBar*); void print(QPrinter*) const; private slots: void createColumnContextMenu(QMenu*); void goToCell(int row, int col); void toggleComments(); void goToNextColumn(); void goToPreviousColumn(); void goToCell(); void sortSpreadsheet(); void sortDialog(QVector); void cutSelection(); void copySelection(); void pasteIntoSelection(); void clearSelectedCells(); void maskSelection(); void unmaskSelection(); // void recalculateSelectedCells(); void plotData(); void fillSelectedCellsWithRowNumbers(); void fillWithRowNumbers(); void fillSelectedCellsWithRandomNumbers(); void fillWithRandomValues(); void fillWithEquidistantValues(); void fillWithFunctionValues(); void fillSelectedCellsWithConstValues(); void insertRowAbove(); void insertRowBelow(); void removeSelectedRows(); void clearSelectedRows(); void insertColumnLeft(); void insertColumnRight(); void removeSelectedColumns(); void clearSelectedColumns(); void reverseColumns(); void dropColumnValues(); void maskColumnValues(); void joinColumns(); void normalizeSelectedColumns(); void normalizeSelection(); void sortSelectedColumns(); void sortColumnAscending(); void sortColumnDescending(); void setSelectionAs(); void activateFormulaMode(bool on); - bool formulaModeActive() const; void showColumnStatistics(bool forAll = false); void showAllColumnsStatistics(); void showRowStatistics(); void handleHorizontalSectionResized(int logicalIndex, int oldSize, int newSize); void handleHorizontalSectionMoved(int index, int from, int to); void handleHorizontalHeaderDoubleClicked(int index); void handleHeaderDataChanged(Qt::Orientation orientation, int first, int last); void currentColumnChanged(const QModelIndex& current, const QModelIndex & previous); void handleAspectAdded(const AbstractAspect*); void handleAspectAboutToBeRemoved(const AbstractAspect*); void updateHeaderGeometry(Qt::Orientation o, int first, int last); void selectColumn(int); void deselectColumn(int); void columnClicked(int); void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); }; #endif diff --git a/src/kdefrontend/datasources/FITSOptionsWidget.cpp b/src/kdefrontend/datasources/FITSOptionsWidget.cpp index 5eecf5dda..a4adb78c5 100644 --- a/src/kdefrontend/datasources/FITSOptionsWidget.cpp +++ b/src/kdefrontend/datasources/FITSOptionsWidget.cpp @@ -1,177 +1,177 @@ /*************************************************************************** File : FITSOptionsWidget.cpp Project : LabPlot Description : Widget providing options for the import of FITS data -------------------------------------------------------------------- Copyright : (C) 2016 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "FITSOptionsWidget.h" #include "ImportFileWidget.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/lib/macros.h" FITSOptionsWidget::FITSOptionsWidget(QWidget* parent, ImportFileWidget* fileWidget) : QWidget(parent), m_fileWidget(fileWidget) { ui.setupUi(parent); ui.twExtensions->headerItem()->setText(0, i18n("Content")); ui.twExtensions->setSelectionMode(QAbstractItemView::SingleSelection); ui.twExtensions->setAlternatingRowColors(true); ui.twExtensions->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.twPreview->setEditTriggers(QAbstractItemView::NoEditTriggers); connect(ui.twExtensions, &QTreeWidget::itemSelectionChanged, this, &FITSOptionsWidget::fitsTreeWidgetSelectionChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, fileWidget, &ImportFileWidget::refreshPreview); } void FITSOptionsWidget::clear() { ui.twExtensions->clear(); ui.twPreview->clear(); } QString FITSOptionsWidget::currentExtensionName() { QString name; if (ui.twExtensions->currentItem() != 0 && ui.twExtensions->currentItem()->text(0) != i18n("Primary header")) name = ui.twExtensions->currentItem()->text(ui.twExtensions->currentColumn()); return name; } void FITSOptionsWidget::updateContent(FITSFilter *filter, const QString& fileName) { ui.twExtensions->clear(); filter->parseExtensions(fileName, ui.twExtensions, true); } /*! updates the selected var name of a NetCDF file when the tree widget item is selected */ //TODO void FITSOptionsWidget::fitsTreeWidgetSelectionChanged() { DEBUG("fitsTreeWidgetSelectionChanges()"); QDEBUG("SELECTED ITEMS =" << ui.twExtensions->selectedItems()); if (ui.twExtensions->selectedItems().isEmpty()) return; QTreeWidgetItem* item = ui.twExtensions->selectedItems().first(); int column = ui.twExtensions->currentColumn(); WAIT_CURSOR; const QString& itemText = item->text(column); QString selectedExtension; int extType = 0; if (itemText.contains(QLatin1String("IMAGE #")) || itemText.contains(QLatin1String("ASCII_TBL #")) || itemText.contains(QLatin1String("BINARY_TBL #"))) extType = 1; else if (!itemText.compare(i18n("Primary header"))) extType = 2; if (extType == 0) { if (item->parent() != 0) { if (item->parent()->parent() != 0) selectedExtension = item->parent()->parent()->text(0) + QLatin1String("[") + item->text(column) + QLatin1String("]"); } } else if (extType == 1) { if (item->parent() != 0) { if (item->parent()->parent() != 0) { bool ok; - int hduNum = itemText.right(1).toInt(&ok); + int hduNum = itemText.rightRef(1).toInt(&ok); selectedExtension = item->parent()->parent()->text(0) + QLatin1String("[") + QString::number(hduNum-1) + QLatin1String("]"); } } } else { if (item->parent()->parent() != 0) selectedExtension = item->parent()->parent()->text(column); } if (!selectedExtension.isEmpty()) { FITSFilter* filter = dynamic_cast(m_fileWidget->currentFileFilter()); bool readFitsTableToMatrix; const QVector importedStrings = filter->readChdu(selectedExtension, &readFitsTableToMatrix, ui.sbPreviewLines->value()); emit m_fileWidget->checkedFitsTableToMatrix(readFitsTableToMatrix); const int rows = importedStrings.size(); ui.twPreview->clear(); ui.twPreview->setRowCount(rows); int colCount = 0; const int maxColumns = 300; for (int i = 0; i < rows; ++i) { QStringList lineString = importedStrings[i]; if (i == 0) { colCount = lineString.size() > maxColumns ? maxColumns : lineString.size(); ui.twPreview->setColumnCount(colCount); } colCount = lineString.size() > maxColumns ? maxColumns : lineString.size(); for (int j = 0; j < colCount; ++j) { QTableWidgetItem* item = new QTableWidgetItem(lineString[j]); ui.twPreview->setItem(i, j, item); } } ui.twPreview->resizeColumnsToContents(); } RESET_CURSOR; } /*! return list of selected FITS extension names */ const QStringList FITSOptionsWidget::selectedFITSExtensions() const { QStringList names; for (const auto* item: ui.twExtensions->selectedItems()) names << item->text(0); return names; } const QString FITSOptionsWidget::extensionName(bool* ok) { if (ui.twExtensions->currentItem() != 0) { const QTreeWidgetItem* item = ui.twExtensions->currentItem(); const int currentColumn = ui.twExtensions->currentColumn(); QString itemText = item->text(currentColumn); int extType = 0; if (itemText.contains(QLatin1String("IMAGE #")) || itemText.contains(QLatin1String("ASCII_TBL #")) || itemText.contains(QLatin1String("BINARY_TBL #"))) extType = 1; else if (!itemText.compare(i18n("Primary header"))) extType = 2; if (extType == 0) { if (item->parent() != 0 && item->parent()->parent() != 0) return item->parent()->parent()->text(0) + QLatin1String("[")+ item->text(currentColumn) + QLatin1String("]"); } else if (extType == 1) { if (item->parent() != 0 && item->parent()->parent() != 0) { - int hduNum = itemText.right(1).toInt(ok); + int hduNum = itemText.rightRef(1).toInt(ok); return item->parent()->parent()->text(0) + QLatin1String("[") + QString::number(hduNum-1) + QLatin1String("]"); } } else { if (item->parent()->parent() != 0) return item->parent()->parent()->text(currentColumn); } } return QString(); } diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index f2c622fc7..879fe6e58 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,1142 +1,1142 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2017 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ImageFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_fileEmpty(false), m_liveDataSource(true), m_suppressRefresh(false) { ui.setupUi(this); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leFileName->setCompleter(completer); ui.cbFileType->addItems(LiveDataSource::fileTypes()); QStringList filterItems; filterItems << i18n("Automatic") << i18n("Custom"); ui.cbFilter->addItems(filterItems); // file type specific option widgets QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); ui.swOptions->insertWidget(LiveDataSource::Ascii, asciiw); QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->insertWidget(LiveDataSource::Binary, binaryw); QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->insertWidget(LiveDataSource::Image, imagew); QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->insertWidget(LiveDataSource::HDF5, hdf5w); QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(LiveDataSource::NETCDF, netcdfw); QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->insertWidget(LiveDataSource::FITS, fitsw); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); QHBoxLayout* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // default filter ui.swOptions->setCurrentIndex(LiveDataSource::Ascii); #if !defined(HAVE_HDF5) || !defined(HAVE_NETCDF) || !defined(HAVE_FITS) const QStandardItemModel* model = qobject_cast(ui.cbFileType->model()); #endif #ifndef HAVE_HDF5 // disable HDF5 item QStandardItem* item = model->item(LiveDataSource::HDF5); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_NETCDF // disable NETCDF item QStandardItem* item2 = model->item(LiveDataSource::NETCDF); item2->setFlags(item2->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_FITS // disable FITS item QStandardItem* item3 = model->item(LiveDataSource::FITS); item3->setFlags(item3->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.bFileInfo->setIcon( QIcon::fromTheme("help-about") ); ui.bManageFilters->setIcon( QIcon::fromTheme("configure") ); ui.bSaveFilter->setIcon( QIcon::fromTheme("document-save") ); ui.bRefreshPreview->setIcon( QIcon::fromTheme("view-refresh") ); connect( ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString)) ); connect( ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile()) ); connect( ui.bFileInfo, SIGNAL(clicked()), this, SLOT (fileInfoDialog()) ); connect( ui.bSaveFilter, SIGNAL(clicked()), this, SLOT (saveFilter()) ); connect( ui.bManageFilters, SIGNAL(clicked()), this, SLOT (manageFilters()) ); connect( ui.cbFileType, SIGNAL(currentIndexChanged(int)), SLOT(fileTypeChanged(int)) ); connect( ui.cbUpdateType, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypeChanged(int))); connect( ui.cbReadType, SIGNAL(currentIndexChanged(int)), this, SLOT(readingTypeChanged(int))); connect( ui.cbFilter, SIGNAL(activated(int)), SLOT(filterChanged(int)) ); connect( ui.bRefreshPreview, SIGNAL(clicked()), SLOT(refreshPreview()) ); connect(ui.leHost, SIGNAL(textChanged(QString)), this, SIGNAL(hostChanged())); connect(ui.lePort, SIGNAL(textChanged(QString)), this, SIGNAL(portChanged())); connect( ui.cbSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceTypeChanged(int))); //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); //defer the loading of settings a bit in order to show the dialog prior to blocking the GUI in refreshPreview() QTimer::singleShot( 100, this, SLOT(loadSettings()) ); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //settings for data type specific widgets m_asciiOptionsWidget->loadSettings(); m_binaryOptionsWidget->loadSettings(); m_imageOptionsWidget->loadSettings(); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings ui.cbFileType->setCurrentIndex(conf.readEntry("Type", 0)); ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed if (m_fileName.isEmpty()) ui.leFileName->setText(conf.readEntry("LastImportedFile", "")); else ui.leFileName->setText(m_fileName); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate").toInt()); ui.cbReadType->setCurrentIndex(conf.readEntry("ReadType").toInt()); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType").toInt()); ui.leHost->setText(conf.readEntry("Host","")); ui.leKeepLastValues->setText(conf.readEntry("KeepLastNValues","")); ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleRate->setValue(conf.readEntry("SampleRate").toInt()); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval").toInt()); m_suppressRefresh = false; refreshPreview(); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", ui.cbFileType->currentIndex()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", ui.leFileName->text()); //live data related settings conf.writeEntry("SourceType", ui.cbSourceType->currentIndex()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadType", ui.cbReadType->currentIndex()); conf.writeEntry("SampleRate", ui.sbSampleRate->value()); conf.writeEntry("KeepLastNValues", ui.leKeepLastValues->text()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); // data type specific settings m_asciiOptionsWidget->saveSettings(); m_binaryOptionsWidget->saveSettings(); m_imageOptionsWidget->saveSettings(); } void ImportFileWidget::hideDataSource() { m_liveDataSource = false; ui.gbUpdateOptions->hide(); ui.chbLinkFile->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.cbUpdateType->hide(); ui.lUpdateType->hide(); ui.sbUpdateInterval->hide(); ui.lUpdateInterval->hide(); } void ImportFileWidget::showAsciiHeaderOptions(bool b) { m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { if (currentFileType() == LiveDataSource::FITS) { QString extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) return ui.leFileName->text() + QLatin1String("[") + extensionName + QLatin1String("]"); } return ui.leFileName->text(); } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { LiveDataSource::FileType fileType = static_cast(ui.cbFileType->currentIndex()); LiveDataSource::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = static_cast(ui.cbSourceType->currentIndex()); LiveDataSource::ReadingType readingType = static_cast(ui.cbReadType->currentIndex()); source->setComment( ui.leFileName->text() ); source->setFileType(fileType); source->setFilter(this->currentFileFilter()); source->setSourceType(sourceType); source->setReadingType(readingType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); else source->setFileWatched(true); if (!ui.leKeepLastValues->text().isEmpty()) { source->setKeepLastValues(true); source->setKeepNvalues(ui.leKeepLastValues->text().toInt()); } source->setUpdateType(updateType); if (readingType != LiveDataSource::ReadingType::TillEnd) source->setSampleRate(ui.sbSampleRate->value()); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName( ui.leFileName->text() ); source->setFileLinked( ui.chbLinkFile->isChecked() ); break; case LiveDataSource::SourceType::LocalSocket: source->setLocalSocketName(ui.leFileName->text()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; default: break; } } /*! returns the currently used file type. */ LiveDataSource::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentIndex()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("currentFileFilter()"); LiveDataSource::FileType fileType = static_cast(ui.cbFileType->currentIndex()); switch (fileType) { case LiveDataSource::Ascii: { //TODO std::unique_ptr filter(new AsciiFilter()); AsciiFilter* filter = new AsciiFilter(); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings( ui.cbFilter->currentText() ); //save the data portion to import filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case LiveDataSource::Binary: { BinaryFilter* filter = new BinaryFilter(); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if ( ui.cbFilter->currentIndex() == 1 ) { //"custom" filter->setAutoModeEnabled(false); m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } case LiveDataSource::Image: { ImageFilter* filter = new ImageFilter(); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case LiveDataSource::HDF5: { HDF5Filter* filter = new HDF5Filter(); QStringList names = selectedHDF5Names(); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case LiveDataSource::NETCDF: { NetCDFFilter* filter = new NetCDFFilter(); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case LiveDataSource::FITS: { FITSFilter* filter = new FITSFilter(); filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } } return 0; } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileWidget"); QString dir = conf.readEntry("LastDir", ""); QString path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastDir", newDir); } ui.leFileName->setText(path); //TODO: decide whether the selection of several files should be possible // QStringList filelist = QFileDialog::getOpenFileNames(this,i18n("Select one or more files to open")); // if (! filelist.isEmpty() ) // ui.leFileName->setText(filelist.join(";")); } /************** SLOTS **************************************************************/ /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { QString fileName = name; #ifndef HAVE_WINDOWS // make relative path - if ( !fileName.isEmpty() && fileName.left(1) != QDir::separator()) + if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif bool fileExists = QFile::exists(fileName); if (fileExists) ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.chbLinkFile->setEnabled(fileExists); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tePreview->clear(); m_twPreview->clear(); m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_fitsOptionsWidget->clear(); emit fileNameChanged(); return; } if (currentSourceType() == LiveDataSource::FileOrPipe) { QString fileInfo; #ifndef HAVE_WINDOWS //check, if we can guess the file type by content QProcess* proc = new QProcess(this); QStringList args; args << "-b" << ui.leFileName->text(); proc->start("file", args); if (proc->waitForReadyRead(1000) == false) { QDEBUG("ERROR: reading file type of file" << fileName); return; } fileInfo = proc->readLine(); #endif QByteArray imageFormat = QImageReader::imageFormat(fileName); if (fileInfo.contains(QLatin1String("compressed data")) || fileInfo.contains(QLatin1String("ASCII")) || fileName.endsWith(QLatin1String("dat"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("txt"), Qt::CaseInsensitive)) { //probably ascii data ui.cbFileType->setCurrentIndex(LiveDataSource::Ascii); } else if (fileInfo.contains(QLatin1String("Hierarchical Data Format")) || fileName.endsWith(QLatin1String("h5"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf5"), Qt::CaseInsensitive) ) { ui.cbFileType->setCurrentIndex(LiveDataSource::HDF5); // update HDF5 tree widget using current selected file m_hdf5OptionsWidget->updateContent((HDF5Filter*)this->currentFileFilter(), fileName); } else if (fileInfo.contains(QLatin1String("NetCDF Data Format")) || fileName.endsWith(QLatin1String("nc"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("netcdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("cdf"), Qt::CaseInsensitive)) { ui.cbFileType->setCurrentIndex(LiveDataSource::NETCDF); // update NetCDF tree widget using current selected file m_netcdfOptionsWidget->updateContent((NetCDFFilter*)this->currentFileFilter(), fileName); } else if (fileInfo.contains(QLatin1String("FITS image data")) || fileName.endsWith(QLatin1String("fits"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fit"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fts"), Qt::CaseInsensitive)) { #ifdef HAVE_FITS ui.cbFileType->setCurrentIndex(LiveDataSource::FITS); #endif // update FITS tree widget using current selected file m_fitsOptionsWidget->updateContent((FITSFilter*)this->currentFileFilter(), fileName); } else if (fileInfo.contains("image") || fileInfo.contains("bitmap") || !imageFormat.isEmpty()) ui.cbFileType->setCurrentIndex(LiveDataSource::Image); else ui.cbFileType->setCurrentIndex(LiveDataSource::Binary); } refreshPreview(); emit fileNameChanged(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined fllter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int fileType) { ui.swOptions->setCurrentIndex(fileType); //default ui.lFilter->show(); ui.cbFilter->show(); //if we switch from netCDF-format (only two tabs available), add the data preview-tab again if (ui.tabWidget->count() == 2) { ui.tabWidget->setTabText(0, i18n("Data format")); ui.tabWidget->insertTab(1, ui.tabDataPreview, i18n("Preview")); } ui.lPreviewLines->show(); ui.sbPreviewLines->show(); ui.lStartColumn->show(); ui.sbStartColumn->show(); ui.lEndColumn->show(); ui.sbEndColumn->show(); switch (fileType) { case LiveDataSource::Ascii: break; case LiveDataSource::Binary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; case LiveDataSource::HDF5: case LiveDataSource::NETCDF: ui.lFilter->hide(); ui.cbFilter->hide(); // hide global preview tab. we have our own ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; case LiveDataSource::Image: ui.lPreviewLines->hide(); ui.sbPreviewLines->hide(); ui.lFilter->hide(); ui.cbFilter->hide(); break; case LiveDataSource::FITS: ui.lFilter->hide(); ui.cbFilter->hide(); ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; default: DEBUG("unknown file type"); } m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); refreshPreview(); } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedHDF5Names(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNetCDFNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedFITSExtensions(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = ui.leFileName->text().split(';'); FileInfoDialog* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats if (ui.cbFileType->currentIndex() == LiveDataSource::HDF5 || ui.cbFileType->currentIndex() == LiveDataSource::NETCDF || ui.cbFileType->currentIndex() == LiveDataSource::Image || ui.cbFileType->currentIndex() == LiveDataSource::FITS) { ui.swOptions->setEnabled(true); return; } if (index == 0) { // "automatic" ui.swOptions->setEnabled(false); ui.bSaveFilter->setEnabled(false); } else if (index == 1) { //custom ui.swOptions->setEnabled(true); ui.bSaveFilter->setEnabled(true); } else { // predefined filter settings were selected. //load and show them in the GUI. //TODO } } void ImportFileWidget::refreshPreview() { if (m_suppressRefresh) return; DEBUG("refreshPreview()"); WAIT_CURSOR; QString fileName = ui.leFileName->text(); #ifndef HAVE_WINDOWS - if (fileName.left(1) != QDir::separator()) + if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif QVector importedStrings; LiveDataSource::FileType fileType = (LiveDataSource::FileType)ui.cbFileType->currentIndex(); // generic table widget if (fileType == LiveDataSource::Ascii || fileType == LiveDataSource::Binary) m_twPreview->show(); else m_twPreview->hide(); int lines = ui.sbPreviewLines->value(); bool ok = true; QTableWidget *tmpTableWidget = nullptr; QStringList vectorNameList; QVector columnModes; switch (fileType) { case LiveDataSource::Ascii: { ui.tePreview->clear(); AsciiFilter *filter = static_cast(this->currentFileFilter()); switch (currentSourceType()) { case LiveDataSource::SourceType::FileOrPipe: { importedStrings = filter->preview(fileName, lines); break; } case LiveDataSource::SourceType::LocalSocket: { QLocalSocket lsocket{this}; lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); bool localSocketConnected = lsocket.waitForConnected(2000); if (localSocketConnected) { DEBUG("connected to local socket"); bool canread = lsocket.waitForReadyRead(500); if (canread) importedStrings = filter->preview(lsocket); lsocket.disconnectFromServer(); connect(&lsocket, SIGNAL(disconnected()), &lsocket, SLOT(deleteLater())); } break; } case LiveDataSource::SourceType::NetworkTcpSocket: { QTcpSocket tsocket{this}; tsocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); bool tcpSocketConnected = tsocket.waitForConnected(2000); if (tcpSocketConnected) { bool canread = tsocket.waitForReadyRead(500); DEBUG("connected to TCP socket"); if (canread) { importedStrings = filter->preview(tsocket); } tsocket.disconnectFromHost(); connect(&tsocket, SIGNAL(disconnected()), &tsocket, SLOT(deleteLater())); } break; } case LiveDataSource::SourceType::NetworkUdpSocket: { QUdpSocket usocket{this}; usocket.connectToHost(host(), port().toInt(), QUdpSocket::ReadOnly); bool udpSocketConnected = usocket.waitForConnected(2000); if (udpSocketConnected) { bool canread = usocket.waitForReadyRead(500); DEBUG("connected to UDP socket"); if (canread) importedStrings = filter->preview(usocket); qDebug()<<"can read " << canread << " " << importedStrings; usocket.disconnectFromHost(); connect(&usocket, SIGNAL(disconnected()), &usocket, SLOT(deleteLater())); } break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; sPort.setBaudRate(baudRate()); sPort.setPortName(serialPort()); bool serialPortOpened = sPort.open(QIODevice::ReadOnly); if (serialPortOpened) { bool canread = sPort.waitForReadyRead(500); if (canread) importedStrings = filter->preview(sPort); sPort.close(); } break; } } tmpTableWidget = m_twPreview; vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case LiveDataSource::Binary: { ui.tePreview->clear(); BinaryFilter *filter = (BinaryFilter *)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; break; } case LiveDataSource::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case LiveDataSource::HDF5: { HDF5Filter *filter = (HDF5Filter *)this->currentFileFilter(); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, NULL, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case LiveDataSource::NETCDF: { NetCDFFilter *filter = (NetCDFFilter *)this->currentFileFilter(); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, NULL, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case LiveDataSource::FITS: { FITSFilter* filter = (FITSFilter*)this->currentFileFilter(); lines = m_fitsOptionsWidget->lines(); QString extensionName = m_fitsOptionsWidget->extensionName(&ok); if (!extensionName.isEmpty()) fileName = extensionName; bool readFitsTableToMatrix; importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); emit checkedFitsTableToMatrix(readFitsTableToMatrix); tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if( !importedStrings.isEmpty() ) { QDEBUG("importedStrings =" << importedStrings); if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); QTableWidgetItem* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { QDEBUG(importedStrings[i]); int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); // new if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { QTableWidgetItem* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else { m_fileEmpty = true; } RESET_CURSOR; } void ImportFileWidget::updateTypeChanged(int idx) { LiveDataSource::UpdateType type = static_cast(idx); if (type == LiveDataSource::UpdateType::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); ui.lUpdateIntervalUnit->show(); } else if (type == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); ui.lUpdateIntervalUnit->hide(); } } void ImportFileWidget::readingTypeChanged(int idx) { LiveDataSource::ReadingType type = static_cast(idx); if (type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile) { ui.lSampleRate->hide(); ui.sbSampleRate->hide(); } else { ui.lSampleRate->show(); ui.sbSampleRate->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { LiveDataSource::SourceType type = static_cast(idx); int itemIdx = -1; switch (type) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); ui.leFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); ui.chbLinkFile->show(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); fileNameChanged(m_fileName); itemIdx = -1; for (int i = 0; i < ui.cbReadType->count(); ++i) { if (ui.cbReadType->itemText(i) == QLatin1String("Read whole file")) { itemIdx = i; break; } } if (itemIdx == -1) { ui.cbReadType->addItem(QLatin1String("Read whole file")); } break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); ui.leFileName->show(); ui.bOpen->show(); ui.bFileInfo->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); itemIdx = -1; for (int i = 0; i < ui.cbReadType->count(); ++i) { if (ui.cbReadType->itemText(i) == QLatin1String("Read whole file")) { itemIdx = i; break; } } if (itemIdx != -1) { ui.cbReadType->removeItem(itemIdx); } break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); itemIdx = -1; for (int i = 0; i < ui.cbReadType->count(); ++i) { if (ui.cbReadType->itemText(i) == QLatin1String("Read whole file")) { itemIdx = i; break; } } if (itemIdx != -1) { ui.cbReadType->removeItem(itemIdx); } break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); itemIdx = -1; for (int i = 0; i < ui.cbReadType->count(); ++i) { if (ui.cbReadType->itemText(i) == QLatin1String("Read whole file")) { itemIdx = i; break; } } if (itemIdx != -1) { ui.cbReadType->removeItem(itemIdx); } break; default: break; } emit sourceTypeChanged(); refreshPreview(); } void ImportFileWidget::initializeAndFillPortsAndBaudRates() { for (int i = 2; i < ui.swOptions->count(); ++i) ui.swOptions->removeWidget(ui.swOptions->widget(i)); const int size = ui.cbFileType->count(); for (int i = 2; i < size; ++i) ui.cbFileType->removeItem(2); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.leKeepLastValues->setValidator(new QIntValidator(2, 100000)); ui.tabWidget->removeTab(2); } diff --git a/src/kdefrontend/datasources/ImportProjectDialog.cpp b/src/kdefrontend/datasources/ImportProjectDialog.cpp index 5c08d6a73..c4fff439c 100644 --- a/src/kdefrontend/datasources/ImportProjectDialog.cpp +++ b/src/kdefrontend/datasources/ImportProjectDialog.cpp @@ -1,412 +1,412 @@ /*************************************************************************** File : ImportProjectDialog.cpp Project : LabPlot Description : import project dialog -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportProjectDialog.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/datasources/projects/LabPlotProjectParser.h" #include "backend/datasources/projects/OriginProjectParser.h" #include "kdefrontend/MainWin.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include #include #include #include #include #include #include /*! \class ImportProjectDialog \brief Dialog for importing project files. \ingroup kdefrontend */ ImportProjectDialog::ImportProjectDialog(MainWin* parent, ProjectType type) : QDialog(parent), m_mainWin(parent), m_projectParser(nullptr), m_projectType(type), m_aspectTreeModel(new AspectTreeModel(parent->project())) { QVBoxLayout* vLayout = new QVBoxLayout(this); //main widget QWidget* mainWidget = new QWidget(this); ui.setupUi(mainWidget); ui.chbUnusedObjects->hide(); vLayout->addWidget(mainWidget); ui.tvPreview->setAnimated(true); ui.tvPreview->setAlternatingRowColors(true); ui.tvPreview->setSelectionBehavior(QAbstractItemView::SelectRows); ui.tvPreview->setSelectionMode(QAbstractItemView::ExtendedSelection); ui.tvPreview->setUniformRowHeights(true); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); m_cbAddTo = new TreeViewComboBox(ui.gbImportTo); m_cbAddTo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); ui.gbImportTo->layout()->addWidget(m_cbAddTo); QList list; list << "Folder"; m_cbAddTo->setTopLevelClasses(list); m_aspectTreeModel->setSelectableAspects(list); m_cbAddTo->setModel(m_aspectTreeModel); m_bNewFolder = new QPushButton(ui.gbImportTo); m_bNewFolder->setIcon(QIcon::fromTheme("list-add")); m_bNewFolder->setToolTip(i18n("Add new folder")); ui.gbImportTo->layout()->addWidget(m_bNewFolder); //dialog buttons m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); vLayout->addWidget(m_buttonBox); //ok-button is only enabled if some project objects were selected (s.a. ImportProjectDialog::selectionChanged()) m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); //Signals/Slots connect(ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString))); connect(ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile())); connect(m_bNewFolder, SIGNAL(clicked()), this, SLOT(newFolder())); connect(ui.chbUnusedObjects, &QCheckBox::stateChanged, this, &ImportProjectDialog::refreshPreview); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QString title; switch (m_projectType) { case (ProjectLabPlot): m_projectParser = new LabPlotProjectParser(); title = i18n("Import LabPlot Project"); break; case (ProjectOrigin): m_projectParser = new OriginProjectParser(); title = i18n("Import Origin Project"); break; } //dialog title and icon setWindowTitle(title); setWindowIcon(QIcon::fromTheme("document-import")); QTimer::singleShot(0, this, &ImportProjectDialog::loadSettings); } void ImportProjectDialog::loadSettings() { //restore saved settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); KWindowConfig::restoreWindowSize(windowHandle(), conf); QString lastImportedFile; switch (m_projectType) { case (ProjectLabPlot): lastImportedFile = QLatin1String("LastImportedLabPlotProject"); break; case (ProjectOrigin): lastImportedFile = QLatin1String("LastImportedOriginProject"); break; } QApplication::processEvents(QEventLoop::AllEvents, 100); ui.leFileName->setText(conf.readEntry(lastImportedFile, "")); } ImportProjectDialog::~ImportProjectDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); QString lastImportedFile; switch (m_projectType) { case (ProjectLabPlot): lastImportedFile = QLatin1String("LastImportedLabPlotProject"); break; case (ProjectOrigin): lastImportedFile = QLatin1String("LastImportedOriginProject"); break; } conf.writeEntry(lastImportedFile, ui.leFileName->text()); } void ImportProjectDialog::setCurrentFolder(const Folder* folder) { m_cbAddTo->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(folder)); } void ImportProjectDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportProjectDialog::importTo()"); //determine the selected objects, convert the model indexes to string pathes const QModelIndexList& indexes = ui.tvPreview->selectionModel()->selectedIndexes(); QStringList selectedPathes; for (int i = 0; i < indexes.size()/4; ++i) { QModelIndex index = indexes.at(i*4); const AbstractAspect* aspect = static_cast(index.internalPointer()); //path of the current aspect and the pathes of all aspects it depends on selectedPathes << aspect->path(); QDEBUG(" aspect path: " << aspect->path()); for (const auto* depAspect : aspect->dependsOn()) selectedPathes << depAspect->path(); } selectedPathes.removeDuplicates(); Folder* targetFolder = static_cast(m_cbAddTo->currentModelIndex().internalPointer()); //check whether the selected pathes already exist in the target folder and warn the user const QString& targetFolderPath = targetFolder->path(); const Project* targetProject = targetFolder->project(); QStringList targetAllPathes; for (const auto* aspect : targetProject->children(AbstractAspect::Recursive)) { if (!dynamic_cast(aspect)) targetAllPathes << aspect->path(); } QStringList existingPathes; for (const auto& path : selectedPathes) { const QString& newPath = targetFolderPath + path.right(path.length() - path.indexOf('/')); if (targetAllPathes.indexOf(newPath) != -1) existingPathes << path; } QDEBUG("project objects to be imported: " << selectedPathes); QDEBUG("all already available project objects: " << targetAllPathes); QDEBUG("already available project objects to be overwritten: " << existingPathes); if (!existingPathes.isEmpty()) { QString msg = i18np("The object listed below already exists in target folder and will be overwritten:", "The objects listed below already exist in target folder and will be overwritten:", existingPathes.size()); msg += '\n'; for (const auto& path : existingPathes) msg += '\n' + path.right(path.length() - path.indexOf('/') - 1); //strip away the name of the root folder "Project" msg += "\n\n" + i18n("Do you want to proceed?"); const int rc = KMessageBox::warningYesNo(0, msg, i18n("Override existing objects?")); if (rc == KMessageBox::No) return; } //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setMinimum(0); progressBar->setMaximum(100); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QApplication::processEvents(QEventLoop::AllEvents, 100); //import the selected project objects into the specified folder QTime timer; timer.start(); connect(m_projectParser, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); if (m_projectType == ProjectOrigin && ui.chbUnusedObjects->isVisible() && ui.chbUnusedObjects->isChecked()) reinterpret_cast(m_projectParser)->setImportUnusedObjects(true); m_projectParser->importTo(targetFolder, selectedPathes); statusBar->showMessage( i18n("Project data imported in %1 seconds.", (float)timer.elapsed()/1000) ); QApplication::restoreOverrideCursor(); statusBar->removeWidget(progressBar); } /*! * show the content of the project in the tree view */ void ImportProjectDialog::refreshPreview() { QString project = ui.leFileName->text(); m_projectParser->setProjectFileName(project); if (m_projectType == ProjectOrigin) { OriginProjectParser* originParser = reinterpret_cast(m_projectParser); if (originParser->hasUnusedObjects()) ui.chbUnusedObjects->show(); else ui.chbUnusedObjects->hide(); originParser->setImportUnusedObjects(ui.chbUnusedObjects->isVisible() && ui.chbUnusedObjects->isChecked()); } ui.tvPreview->setModel(m_projectParser->model()); connect(ui.tvPreview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged(QItemSelection,QItemSelection)) ); //show top-level containers only if (ui.tvPreview->model()) { QModelIndex root = ui.tvPreview->model()->index(0,0); showTopLevelOnly(root); } //extand the tree to show all available top-level objects and adjust the header sizes ui.tvPreview->expandAll(); ui.tvPreview->header()->resizeSections(QHeaderView::ResizeToContents); } /*! Hides the non-toplevel items of the model used in the tree view. */ void ImportProjectDialog::showTopLevelOnly(const QModelIndex& index) { int rows = index.model()->rowCount(index); for (int i = 0; i < rows; ++i) { QModelIndex child = index.child(i, 0); showTopLevelOnly(child); const AbstractAspect* aspect = static_cast(child.internalPointer()); ui.tvPreview->setRowHidden(i, index, !isTopLevel(aspect)); } } /*! checks whether \c aspect is one of the allowed top level types */ bool ImportProjectDialog::isTopLevel(const AbstractAspect* aspect) const { foreach (const char* classString, m_projectParser->topLevelClasses()) { if (aspect->inherits(classString)) return true; } return false; } //############################################################################## //################################# SLOTS #################################### //############################################################################## void ImportProjectDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { Q_UNUSED(deselected); //determine the dependent objects and select/deselect them too const QModelIndexList& indexes = selected.indexes(); if (indexes.isEmpty()) return; //for the just selected aspect, determine all the objects it depends on and select them, too //TODO: we need a better "selection", maybe with tri-state check boxes in the tree view const AbstractAspect* aspect = static_cast(indexes.at(0).internalPointer()); const QVector aspects = aspect->dependsOn(); AspectTreeModel* model = reinterpret_cast(ui.tvPreview->model()); for (const auto* aspect : aspects) { QModelIndex index = model->modelIndexOfAspect(aspect, 0); ui.tvPreview->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); } //Ok-button is only enabled if some project objects were selected bool enable = (selected.indexes().size() != 0); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); if (enable) m_buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18n("Close the dialog and import the selected objects.")); else m_buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18n("Select object(s) to be imported.")); } /*! opens a file dialog and lets the user select the project file. */ void ImportProjectDialog::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); QString title; QString lastDir; QString supportedFormats; QString lastDirConfEntryName; switch (m_projectType) { case (ProjectLabPlot): title = i18n("Open LabPlot Project"); lastDirConfEntryName = QLatin1String("LastImportLabPlotProjectDir"); supportedFormats = i18n("LabPlot Projects (%1)", Project::supportedExtensions()); break; case (ProjectOrigin): title = i18n("Open Origin Project"); lastDirConfEntryName = QLatin1String("LastImportOriginProjecttDir"); supportedFormats = i18n("Origin Projects (%1)", OriginProjectParser::supportedExtensions()); break; } lastDir = conf.readEntry(lastDirConfEntryName, ""); QString path = QFileDialog::getOpenFileName(this, title, lastDir, supportedFormats); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != lastDir) conf.writeEntry(lastDirConfEntryName, newDir); } ui.leFileName->setText(path); refreshPreview(); } void ImportProjectDialog::fileNameChanged(const QString& name) { QString fileName = name; #ifndef HAVE_WINDOWS // make relative path - if ( !fileName.isEmpty() && fileName.left(1) != QDir::separator()) + if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif bool fileExists = QFile::exists(fileName); if (fileExists) ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tvPreview->setModel(nullptr); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } refreshPreview(); } void ImportProjectDialog::newFolder() { QString path = ui.leFileName->text(); QString name = path.right( path.length()-path.lastIndexOf(QDir::separator())-1 ); bool ok; QInputDialog* dlg = new QInputDialog(this); name = dlg->getText(this, i18n("Add new folder"), i18n("Folder name:"), QLineEdit::Normal, name, &ok); if (ok) { Folder* folder = new Folder(name); m_mainWin->addAspectToProject(folder); m_cbAddTo->setCurrentModelIndex(m_mainWin->model()->modelIndexOfAspect(folder)); } delete dlg; } diff --git a/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp b/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp index c237b2cb7..5805ee924 100644 --- a/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp +++ b/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp @@ -1,446 +1,446 @@ /*************************************************************************** File : ImportSQLDatabaseWidget.cpp Project : LabPlot Description : Datapicker -------------------------------------------------------------------- Copyright : (C) 2016 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportSQLDatabaseWidget.h" #include "DatabaseManagerDialog.h" #include "DatabaseManagerWidget.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/lib/macros.h" #include #include #include #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING #include #include #include #endif #include #include ImportSQLDatabaseWidget::ImportSQLDatabaseWidget(QWidget* parent) : QWidget(parent), m_cols(0), m_rows(0), m_databaseTreeModel(0), m_initializing(0), m_valid(false), m_numeric(false) { ui.setupUi(this); ui.cbImportFrom->addItem(i18n("Table")); ui.cbImportFrom->addItem(i18n("Custom query")); ui.bDatabaseManager->setIcon(QIcon::fromTheme("network-server-database")); ui.twPreview->setEditTriggers(QAbstractItemView::NoEditTriggers); ui.cbNumberFormat->addItems(AbstractFileFilter::numberFormats()); ui.cbDateTimeFormat->addItems(AbstractColumn::dateTimeFormats()); #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING m_highlighter = new KSyntaxHighlighting::SyntaxHighlighter(ui.teQuery->document()); m_highlighter->setDefinition(m_repository.definitionForName("SQL")); m_highlighter->setTheme( (palette().color(QPalette::Base).lightness() < 128) ? m_repository.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme) : m_repository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme) ); #endif m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).first() + "sql_connections"; connect( ui.cbConnection, SIGNAL(currentIndexChanged(int)), SLOT(connectionChanged()) ); connect( ui.cbImportFrom, SIGNAL(currentIndexChanged(int)), SLOT(importFromChanged(int)) ); connect( ui.bDatabaseManager, SIGNAL(clicked()), this, SLOT(showDatabaseManager()) ); connect( ui.lwTables, SIGNAL(currentRowChanged(int)), this, SLOT(refreshPreview()) ); connect( ui.bRefreshPreview, SIGNAL(clicked()), this, SLOT(refreshPreview()) ); //defer the loading of settings a bit in order to show the dialog prior to blocking the GUI in refreshPreview() QTimer::singleShot( 100, this, SLOT(loadSettings()) ); } void ImportSQLDatabaseWidget::loadSettings() { m_initializing = true; //read available connections readConnections(); //load last used connection and other settings KConfigGroup config(KSharedConfig::openConfig(), "ImportSQLDatabaseWidget"); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(config.readEntry("Connection", ""))); ui.cbImportFrom->setCurrentIndex(config.readEntry("ImportFrom", 0)); importFromChanged(ui.cbImportFrom->currentIndex()); ui.cbNumberFormat->setCurrentIndex(config.readEntry("NumberFormat", (int)QLocale::AnyLanguage)); ui.cbDateTimeFormat->setCurrentItem(config.readEntry("DateTimeFormat", "yyyy-dd-MM hh:mm:ss:zzz")); QList defaultSizes; defaultSizes << 100 << 100; ui.splitterMain->setSizes(config.readEntry("SplitterMainSizes", defaultSizes)); ui.splitterPreview->setSizes(config.readEntry("SplitterPreviewSizes", defaultSizes)); //TODO m_initializing = false; //all settings loaded -> trigger the selection of the last used connection in order to get the data preview connectionChanged(); } ImportSQLDatabaseWidget::~ImportSQLDatabaseWidget() { // save current settings KConfigGroup config(KSharedConfig::openConfig(), "ImportSQLDatabaseWidget"); config.writeEntry("Connection", ui.cbConnection->currentText()); config.writeEntry("ImportFrom", ui.cbImportFrom->currentIndex()); config.writeEntry("NumberFormat", ui.cbNumberFormat->currentText()); config.writeEntry("DateTimeFormat", ui.cbDateTimeFormat->currentText()); config.writeEntry("SplitterMainSizes", ui.splitterMain->sizes()); config.writeEntry("SplitterPreviewSizes", ui.splitterPreview->sizes()); //TODO } /*! * in case the import from a table is selected, returns the currently selected database table. * returns empty string otherwise. */ QString ImportSQLDatabaseWidget::selectedTable() const { if (ui.cbImportFrom->currentIndex() == 0) { if (ui.lwTables->currentItem()) return ui.lwTables->currentItem()->text(); } return QString(); } /*! returns \c true if a working connections was selected and a table (or custom query) is provided and ready to be imported. returns \c false otherwise. */ bool ImportSQLDatabaseWidget::isValid() const { return m_valid; } /*! returns \c true if the selected table or the result of a custom query contains numeric data only. returns \c false otherwise. */ bool ImportSQLDatabaseWidget::isNumericData() const { return m_numeric; } /*! loads all available saved connections */ void ImportSQLDatabaseWidget::readConnections() { DEBUG("ImportSQLDatabaseWidget: reading available connections"); KConfig config(m_configPath, KConfig::SimpleConfig); - for (auto name : config.groupList()) + for (const auto& name : config.groupList()) ui.cbConnection->addItem(name); } void ImportSQLDatabaseWidget::connectionChanged() { if (m_initializing) return; QDEBUG("ImportSQLDatabaseWidget: connecting to " + ui.cbConnection->currentText()); //clear the previously shown content ui.teQuery->clear(); ui.lwTables->clear(); ui.twPreview->clear(); if (ui.cbConnection->currentIndex() == -1) return; //connection name was changed, determine the current connections settings KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); //open the selected connection const QString driver = group.readEntry("Driver"); m_db = QSqlDatabase::addDatabase(driver); m_db.setDatabaseName( group.readEntry("DatabaseName") ); if (!DatabaseManagerWidget::isFileDB(driver)) { m_db.setHostName( group.readEntry("HostName") ); m_db.setPort( group.readEntry("Port", 0) ); m_db.setUserName( group.readEntry("UserName") ); m_db.setPassword( group.readEntry("Password") ); } if (!m_db.open()) { KMessageBox::error(this, i18n("Failed to connect to the database '%1'. Please check the connection settings.", ui.cbConnection->currentText()), i18n("Connection failed")); setInvalid(); return; } //show all available database tables if (m_db.tables().size()) { ui.lwTables->addItems(m_db.tables()); ui.lwTables->setCurrentRow(0); for (int i = 0; i < ui.lwTables->count(); ++i) ui.lwTables->item(i)->setIcon(QIcon::fromTheme("view-form-table")); } else setInvalid(); } void ImportSQLDatabaseWidget::refreshPreview() { if (!ui.lwTables->currentItem()) { setInvalid(); return; } WAIT_CURSOR; ui.twPreview->clear(); //execute the current query (select on a table or a custom query) const QString& query = currentQuery(true); if (query.isEmpty()) { RESET_CURSOR; setInvalid(); return; } QSqlQuery q; q.prepare(currentQuery(true)); q.setForwardOnly(true); q.exec(); if (!q.isActive()) { RESET_CURSOR; if (!q.lastError().databaseText().isEmpty()) KMessageBox::error(this, q.lastError().databaseText(), i18n("Unable to execute query")); setInvalid(); return; } //resize the table to the number of columns (=number of fields in the result set) m_cols = q.record().count(); ui.twPreview->setColumnCount(m_cols); //determine the names and the data type (column modes) of the table columns. //check whether we have numerical data only by checking the data types of the first record. m_columnNames.clear(); m_columnModes.clear(); bool numeric = true; QLocale::Language numberFormat = (QLocale::Language)ui.cbNumberFormat->currentIndex(); const QString& dateTimeFormat = ui.cbDateTimeFormat->currentText(); q.next(); //go to the first record // ui.twPreview->setRowCount(1); //add the first row for the check boxes for (int i = 0; i < m_cols; ++i) { //name m_columnNames << q.record().fieldName(i); //value and type const QString valueString = q.record().value(i).toString(); AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); m_columnModes << mode; if (mode != AbstractColumn::Numeric) numeric = false; //header item QTableWidgetItem* item = new QTableWidgetItem(m_columnNames[i] + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, mode) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(mode)); ui.twPreview->setHorizontalHeaderItem(i, item); //create checked items // QTableWidgetItem* itemChecked = new QTableWidgetItem(); // itemChecked->setCheckState(Qt::Checked); // ui.twPreview->setItem(0, i, itemChecked); } //preview the data const bool customQuery = (ui.cbImportFrom->currentIndex() != 0); int row = 0; do { for(int col = 0; col < m_cols; ++col) { ui.twPreview->setRowCount(row+1); ui.twPreview->setItem(row, col, new QTableWidgetItem(q.value(col).toString()) ); } row++; //in case a custom query is executed, check whether the row number limit is reached if (customQuery && row >= ui.sbPreviewLines->value()) break; } while (q.next()); ui.twPreview->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); setValid(); if (numeric != m_numeric) { m_numeric = numeric; emit stateChanged(); } RESET_CURSOR; } void ImportSQLDatabaseWidget::importFromChanged(int index) { if (index==0) { //import from a table ui.gbQuery->hide(); ui.lwTables->show(); } else { //import the result set of a custom query ui.gbQuery->show(); ui.lwTables->hide(); ui.twPreview->clear(); } refreshPreview(); } void ImportSQLDatabaseWidget::read(AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { if (!dataSource) return; WAIT_CURSOR; //execute the current query (select on a table or a custom query) QSqlQuery q; // q.setForwardOnly(true); //TODO: crashes most probably because of q.last() and q.first() below q.prepare(currentQuery()); if (!q.exec() || !q.isActive()) { RESET_CURSOR; if (!q.lastError().databaseText().isEmpty()) KMessageBox::error(this, q.lastError().databaseText(), i18n("Unable to execute query")); setInvalid(); return; } //determine the number of rows/records to read q.last(); const int rows = q.at()+1; q.first(); // pointers to the actual data containers //columnOffset indexes the "start column" in the datasource. Data will be imported starting from this column. QVector dataContainer; int columnOffset = dataSource->prepareImport(dataContainer, importMode, rows, m_cols, m_columnNames, m_columnModes); //number and DateTime formatting const QString& dateTimeFormat = ui.cbDateTimeFormat->currentText(); const QLocale numberFormat = QLocale((QLocale::Language)ui.cbNumberFormat->currentIndex()); //read the data int row = 0; do { for(int col = 0; col < m_cols; ++col) { const QString valueString = q.record().value(col).toString(); // set value depending on data type switch (m_columnModes[col]) { case AbstractColumn::Numeric: { bool isNumber; const double value = numberFormat.toDouble(valueString, &isNumber); static_cast*>(dataContainer[col])->operator[](row) = (isNumber ? value : NAN); break; } case AbstractColumn::Integer: { bool isNumber; const int value = numberFormat.toInt(valueString, &isNumber); static_cast*>(dataContainer[col])->operator[](row) = (isNumber ? value : NAN); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(dataContainer[col])->operator[](row) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: static_cast*>(dataContainer[col])->operator[](row) = valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } row++; emit completed(100 * row/rows); } while (q.next()); dataSource->finalizeImport(columnOffset, 1, m_cols, dateTimeFormat, importMode); RESET_CURSOR; } QString ImportSQLDatabaseWidget::currentQuery(bool preview) { QString query; const bool customQuery = (ui.cbImportFrom->currentIndex() != 0); if ( !customQuery ) { const QString& tableName = ui.lwTables->currentItem()->text(); if (!preview) { query = QLatin1String("SELECT * FROM ") + tableName; } else { //preview the content of the currently selected table const QString& driver = m_db.driverName(); const QString& limit = QString::number(ui.sbPreviewLines->value()); if ( (driver == QLatin1String("QSQLITE3")) || (driver == QLatin1String("QMYSQL3")) || (driver == QLatin1String("QPSQL")) || (driver == QLatin1String("QSQLITE")) || (driver == QLatin1String("QMYSQL")) ) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" LIMIT ") + limit; else if (driver == QLatin1String("QOCI")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" ROWNUM<=") + limit; else if (driver == QLatin1String("QDB2")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" FETCH FIRST ") + limit + QLatin1String(" ROWS ONLY"); else if (driver == QLatin1String("QIBASE")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" ROWS ") + limit; else query = QLatin1String("SELECT TOP ") + limit + QLatin1String(" * FROM ") + tableName; } } else { //preview the result of a custom query query = ui.teQuery->toPlainText().simplified(); } return query; } /*! shows the database manager where the connections are created and edited. The selected connection is selected in the connection combo box in this widget. **/ void ImportSQLDatabaseWidget::showDatabaseManager() { DatabaseManagerDialog* dlg = new DatabaseManagerDialog(this, ui.cbConnection->currentText()); if (dlg->exec() == QDialog::Accepted) { //re-read the available connections to be in sync with the changes in DatabaseManager ui.cbConnection->clear(); readConnections(); //select the connection the user has selected in DataabaseManager QString conn = dlg->connection(); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conn)); } delete dlg; } void ImportSQLDatabaseWidget::setInvalid() { if (m_valid) { m_valid = false; emit stateChanged(); } } void ImportSQLDatabaseWidget::setValid() { if (!m_valid) { m_valid = true; emit stateChanged(); } } diff --git a/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp b/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp index 86fde62f8..257f21eb8 100644 --- a/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp +++ b/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp @@ -1,630 +1,630 @@ /*************************************************************************** File : FITSHeaderEditWidget.cpp Project : LabPlot Description : Widget for listing/editing FITS header keywords -------------------------------------------------------------------- Copyright : (C) 2016-2017 by Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "FITSHeaderEditWidget.h" #include "ui_fitsheadereditwidget.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/lib/macros.h" #include "FITSHeaderEditNewKeywordDialog.h" #include "FITSHeaderEditAddUnitDialog.h" #include #include #include #include #include #include #include #include /*! \class FITSHeaderEditWidget * \brief Widget for listing/editing FITS header keywords * \since 2.4.0 * \ingroup kdefrontend/widgets */ FITSHeaderEditWidget::FITSHeaderEditWidget(QWidget* parent) : QWidget(parent), ui(new Ui::FITSHeaderEditWidget()), m_fitsFilter(new FITSFilter()), m_initializingTable(false) { ui->setupUi(this); initActions(); connectActions(); initContextMenus(); ui->bOpen->setIcon(QIcon::fromTheme("document-open")); ui->bAddKey->setIcon(QIcon::fromTheme("list-add")); ui->bAddKey->setEnabled(false); ui->bAddKey->setToolTip(i18n("Add new keyword")); ui->bRemoveKey->setIcon(QIcon::fromTheme("list-remove")); ui->bRemoveKey->setEnabled(false); ui->bRemoveKey->setToolTip(i18n("Remove selected keyword")); ui->bAddUnit->setIcon(QIcon::fromTheme("document-new")); ui->bAddUnit->setEnabled(false); ui->bAddUnit->setToolTip(i18n("Add unit to keyword")); ui->bClose->setIcon(QIcon::fromTheme("document-close")); ui->bClose->setEnabled(false); ui->bClose->setToolTip(i18n("Close file")); ui->twKeywordsTable->setColumnCount(3); ui->twExtensions->setSelectionMode(QAbstractItemView::SingleSelection); ui->twExtensions->headerItem()->setText(0, i18n("Content")); ui->twKeywordsTable->setHorizontalHeaderItem(0, new QTableWidgetItem(i18n("Key"))); ui->twKeywordsTable->setHorizontalHeaderItem(1, new QTableWidgetItem(i18n("Value"))); ui->twKeywordsTable->setHorizontalHeaderItem(2, new QTableWidgetItem(i18n("Comment"))); ui->twKeywordsTable->setAlternatingRowColors(true); ui->twKeywordsTable->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); ui->twKeywordsTable->horizontalHeader()->setStretchLastSection(true); ui->twKeywordsTable->installEventFilter(this); ui->twExtensions->installEventFilter(this); setAttribute(Qt::WA_DeleteOnClose); connect(ui->bAddUnit, &QPushButton::clicked, m_action_addmodify_unit, &QAction::triggered); connect(ui->bClose, &QPushButton::clicked, this, &FITSHeaderEditWidget::closeFile); connect(ui->bOpen, &QPushButton::clicked, this, &FITSHeaderEditWidget::openFile); connect(ui->bAddKey, &QPushButton::clicked, this, &FITSHeaderEditWidget::addKeyword); connect(ui->bRemoveKey, &QPushButton::clicked, this, &FITSHeaderEditWidget::removeKeyword); connect(ui->twKeywordsTable, &QTableWidget::itemClicked, this, &FITSHeaderEditWidget::enableButtonAddUnit); connect(ui->twKeywordsTable, &QTableWidget::itemChanged, this, &FITSHeaderEditWidget::updateKeyword); connect(ui->twExtensions, &QTreeWidget::itemClicked, this, &FITSHeaderEditWidget::fillTableSlot); connect(ui->twExtensions, &QTreeWidget::itemClicked, this, &FITSHeaderEditWidget::enableButtonCloseFile); } /*! * \brief Destructor */ FITSHeaderEditWidget::~FITSHeaderEditWidget() { delete m_fitsFilter; } /*! * \brief Fills the keywords tablewidget. * If the selected extension was not yet selected before, then the keywords are read from the file * and then the table is filled, otherwise the table is filled using the already existing keywords. */ void FITSHeaderEditWidget::fillTable() { m_initializingTable = true; if (!m_extensionDatas.contains(m_seletedExtension)) { m_extensionDatas[m_seletedExtension].keywords = m_fitsFilter->chduKeywords(m_seletedExtension); m_extensionDatas[m_seletedExtension].updates.updatedKeywords.reserve(m_extensionDatas[m_seletedExtension].keywords.size()); m_extensionDatas[m_seletedExtension].updates.updatedKeywords.resize(m_extensionDatas[m_seletedExtension].keywords.size()); m_fitsFilter->parseHeader(m_seletedExtension, ui->twKeywordsTable); } else { QList keywords = m_extensionDatas[m_seletedExtension].keywords; for (int i = 0; i < m_extensionDatas[m_seletedExtension].updates.updatedKeywords.size(); ++i) { FITSFilter::Keyword keyword = m_extensionDatas[m_seletedExtension].updates.updatedKeywords.at(i); if (!keyword.key.isEmpty()) keywords.operator [](i).key = keyword.key; if (!keyword.value.isEmpty()) keywords.operator [](i).value = keyword.value; if (!keyword.comment.isEmpty()) keywords.operator [](i).comment = keyword.comment; } for (const FITSFilter::Keyword& key : m_extensionDatas[m_seletedExtension].updates.newKeywords) keywords.append(key); m_fitsFilter->parseHeader(QString(), ui->twKeywordsTable, false, keywords); } m_initializingTable = false; } /*! * \brief Fills the tablewidget with the keywords of extension \a item * \param item the extension selected * \param col the column of the selected item */ void FITSHeaderEditWidget::fillTableSlot(QTreeWidgetItem *item, int col) { WAIT_CURSOR; const QString& itemText = item->text(col); QString selectedExtension; int extType = 0; if (itemText.contains(QLatin1String("IMAGE #")) || itemText.contains(QLatin1String("ASCII_TBL #")) || itemText.contains(QLatin1String("BINARY_TBL #"))) extType = 1; else if (!itemText.compare(QLatin1String("Primary header"))) extType = 2; if (extType == 0) { if (item->parent() != 0) { if (item->parent()->parent() != 0) selectedExtension = item->parent()->parent()->text(0) + '[' + item->text(col) + ']'; } } else if (extType == 1) { if (item->parent() != 0) { if (item->parent()->parent() != 0) { bool ok; - int hduNum = itemText.right(1).toInt(&ok); + int hduNum = itemText.rightRef(1).toInt(&ok); selectedExtension = item->parent()->parent()->text(0) + '[' + QString::number(hduNum-1) + ']'; } } } else { if (item->parent()->parent() != 0) selectedExtension = item->parent()->parent()->text(col); } if (!selectedExtension.isEmpty()) { if (!(m_seletedExtension == selectedExtension)) { m_seletedExtension = selectedExtension; fillTable(); } } RESET_CURSOR; } /*! * \brief Shows a dialog for opening a FITS file * If the returned file name is not empty (so a FITS file was selected) and it's not opened yet * then the file is parsed, so the treeview for the extensions is built and the table is filled. */ void FITSHeaderEditWidget::openFile() { KConfigGroup conf(KSharedConfig::openConfig(), "FITSHeaderEditWidget"); QString dir = conf.readEntry("LastDir", ""); QString fileName = QFileDialog::getOpenFileName(this, i18n("Open FITS file"), dir, i18n("FITS files (*.fits *.fit *.fts)")); if (fileName.isEmpty()) return; int pos = fileName.lastIndexOf(QDir::separator()); if (pos!=-1) { QString newDir = fileName.left(pos); if (newDir!=dir) conf.writeEntry("LastDir", newDir); } WAIT_CURSOR; QTreeWidgetItem* root = ui->twExtensions->invisibleRootItem(); const int childCount = root->childCount(); bool opened = false; for (int i = 0; i < childCount; ++i) { if(root->child(i)->text(0) == fileName) { opened = true; break; } } if (!opened) { for (QTreeWidgetItem* item : ui->twExtensions->selectedItems()) item->setSelected(false); m_fitsFilter->parseExtensions(fileName, ui->twExtensions); ui->twExtensions->resizeColumnToContents(0); if (ui->twExtensions->selectedItems().size() > 0) fillTableSlot(ui->twExtensions->selectedItems().at(0), 0); ui->bAddKey->setEnabled(true); ui->bRemoveKey->setEnabled(true); ui->bAddUnit->setEnabled(true); ui->bClose->setEnabled(false); } else { KMessageBox::information(this, i18n("Cannot open file, file already opened."), i18n("File already opened")); } enableButtonAddUnit(); RESET_CURSOR; } /*! * \brief Triggered when clicking the Save button * Saves the modifications (new keywords, new keyword units, keyword modifications, * deleted keywords, deleted extensions) to the FITS files. * \return \c true if there was something saved, otherwise false */ bool FITSHeaderEditWidget::save() { bool saved = false; for (const QString& fileName : m_extensionDatas.keys()) { if (m_extensionDatas[fileName].updates.newKeywords.size() > 0) { m_fitsFilter->addNewKeyword(fileName,m_extensionDatas[fileName].updates.newKeywords); if (!saved) saved = true; } if (m_extensionDatas[fileName].updates.removedKeywords.size() > 0) { m_fitsFilter->deleteKeyword(fileName, m_extensionDatas[fileName].updates.removedKeywords); if (!saved) saved = true; } if (!saved) { for (const FITSFilter::Keyword& key : m_extensionDatas[fileName].updates.updatedKeywords) { if (!key.isEmpty()) { saved = true; break; } } } m_fitsFilter->updateKeywords(fileName, m_extensionDatas[fileName].keywords, m_extensionDatas[fileName].updates.updatedKeywords); m_fitsFilter->addKeywordUnit(fileName, m_extensionDatas[fileName].keywords); m_fitsFilter->addKeywordUnit(fileName, m_extensionDatas[fileName].updates.newKeywords); } if (m_removedExtensions.size() > 0) { m_fitsFilter->removeExtensions(m_removedExtensions); if (!saved) saved = true; } if (saved) { //to reset the window title emit changed(false); } return saved; } /*! * \brief Initializes the context menu's actions. */ void FITSHeaderEditWidget::initActions() { m_action_add_keyword = new QAction(QIcon::fromTheme("list-add"), i18n("Add new keyword"), this); m_action_remove_keyword = new QAction(QIcon::fromTheme("list-remove"), i18n("Remove keyword"), this); m_action_remove_extension = new QAction(i18n("Delete"), this); m_action_addmodify_unit = new QAction(i18n("Add unit"), this); } /*! * \brief Connects signals of the actions to the appropriate slots. */ void FITSHeaderEditWidget::connectActions() { connect(m_action_add_keyword, SIGNAL(triggered()), this, SLOT(addKeyword())); connect(m_action_remove_keyword, SIGNAL(triggered()), this, SLOT(removeKeyword())); connect(m_action_remove_extension, SIGNAL(triggered()), this, SLOT(removeExtension())); connect(m_action_addmodify_unit, SIGNAL(triggered()), this, SLOT(addModifyKeywordUnit())); } /*! * \brief Initializes the context menus. */ void FITSHeaderEditWidget::initContextMenus() { m_KeywordActionsMenu = new QMenu(this); m_KeywordActionsMenu->addAction(m_action_add_keyword); m_KeywordActionsMenu->addAction(m_action_remove_keyword); m_KeywordActionsMenu->addSeparator(); m_KeywordActionsMenu->addAction(m_action_addmodify_unit); m_ExtensionActionsMenu = new QMenu(this); m_ExtensionActionsMenu->addAction(m_action_remove_extension); } /*! * \brief Shows a FITSHeaderEditNewKeywordDialog and decides whether the new keyword provided in the dialog * can be added to the new keywords or not. Updates the tablewidget if it's needed. */ void FITSHeaderEditWidget::addKeyword() { FITSHeaderEditNewKeywordDialog* newKeywordDialog = new FITSHeaderEditNewKeywordDialog; m_initializingTable = true; if (newKeywordDialog->exec() == QDialog::Accepted) { FITSFilter::Keyword newKeyWord = newKeywordDialog->newKeyword(); QList currentKeywords = m_extensionDatas[m_seletedExtension].keywords; for(const FITSFilter::Keyword& keyword : currentKeywords) { if (keyword.operator==(newKeyWord)) { KMessageBox::information(this, i18n("Cannot add keyword, keyword already added"), i18n("Cannot add keyword")); return; } } for(const FITSFilter::Keyword& keyword : m_extensionDatas[m_seletedExtension].updates.newKeywords) { if (keyword.operator==(newKeyWord)) { KMessageBox::information(this, i18n("Cannot add keyword, keyword already added"), i18n("Cannot add keyword")); return; } } for(const QString& keyword : mandatoryKeywords()) { if (!keyword.compare(newKeyWord.key)) { KMessageBox::information(this, i18n("Cannot add mandatory keyword, they are already present"), i18n("Cannot add keyword")); return; } } /* - Column related keyword (TFIELDS, TTYPEn,TFORMn, etc.) in an image - SIMPLE, EXTEND, or BLOCKED keyword in any extension - BSCALE, BZERO, BUNIT, BLANK, DATAMAX, DATAMIN keywords in a table - Keyword name contains illegal character */ m_extensionDatas[m_seletedExtension].updates.newKeywords.append(newKeyWord); const int lastRow = ui->twKeywordsTable->rowCount(); ui->twKeywordsTable->setRowCount(lastRow + 1); QTableWidgetItem* newKeyWordItem = new QTableWidgetItem(newKeyWord.key); newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui->twKeywordsTable->setItem(lastRow, 0, newKeyWordItem); newKeyWordItem = new QTableWidgetItem(newKeyWord.value); newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui->twKeywordsTable->setItem(lastRow, 1, newKeyWordItem); newKeyWordItem = new QTableWidgetItem(newKeyWord.comment); newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui->twKeywordsTable->setItem(lastRow, 2, newKeyWordItem); emit changed(true); } m_initializingTable = false; delete newKeywordDialog; } /*! * \brief Shows a messagebox whether we want to remove the keyword or not. * Mandatory keywords cannot be deleted. */ void FITSHeaderEditWidget::removeKeyword() { const int row = ui->twKeywordsTable->currentRow(); if (row == -1) return; QString key = ui->twKeywordsTable->item(row, 0)->text(); const int rc = KMessageBox::questionYesNo(this, i18n("Are you sure you want to delete the keyword '%1'?").arg(key), i18n("Confirm deletion")); if (rc == KMessageBox::Yes) { bool remove = true; for(const QString& k : mandatoryKeywords()) { if (!k.compare(key)) { remove = false; break; } } if (remove) { FITSFilter::Keyword toRemove = FITSFilter::Keyword(key, ui->twKeywordsTable->item(row, 1)->text(), ui->twKeywordsTable->item(row, 2)->text()); ui->twKeywordsTable->removeRow(row); m_extensionDatas[m_seletedExtension].keywords.removeAt(row); m_extensionDatas[m_seletedExtension].updates.removedKeywords.append(toRemove); emit changed(true); } else KMessageBox::information(this, i18n("Cannot remove mandatory keyword."), i18n("Removing keyword")); } enableButtonAddUnit(); } /*! * \brief Trigggered when an item was updated by the user in the tablewidget * \param item the item which was updated */ void FITSHeaderEditWidget::updateKeyword(QTableWidgetItem *item) { if (!m_initializingTable) { const int row = item->row(); int idx; bool fromNewKeyword = false; if (row > m_extensionDatas[m_seletedExtension].keywords.size()-1) { idx = row - m_extensionDatas[m_seletedExtension].keywords.size(); fromNewKeyword = true; } else idx = row; if (item->column() == 0) { if (!fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.updatedKeywords.operator [](idx).key = item->text(); m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.keyUpdated = true; } else { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).key = item->text(); m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.keyUpdated = true; } } else if (item->column() == 1) { if (!fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.updatedKeywords.operator [](idx).value = item->text(); m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.valueUpdated = true; } else { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).value = item->text(); m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.valueUpdated = true; } } else { if (!fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.updatedKeywords.operator [](idx).comment = item->text(); m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.commentUpdated = true; } else { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).comment = item->text(); m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.commentUpdated = true; } } emit changed(true); } } /*! * \brief Shows a FITSHeaderEditAddUnitDialog on the selected keyword (provides the keyword's unit to the * dialog if it had one) and if the dialog was accepted then the new keyword unit is set and the tablewidget * is updated (filled with the modifications). */ void FITSHeaderEditWidget::addModifyKeywordUnit() { FITSHeaderEditAddUnitDialog* addUnitDialog; const int selectedRow = ui->twKeywordsTable->currentRow(); int idx; bool fromNewKeyword = false; if (selectedRow > m_extensionDatas[m_seletedExtension].keywords.size()-1) { idx = selectedRow - m_extensionDatas[m_seletedExtension].keywords.size(); fromNewKeyword = true; } else idx = selectedRow; QString unit; if (fromNewKeyword) { if (!m_extensionDatas[m_seletedExtension].updates.newKeywords.at(idx).unit.isEmpty()) unit = m_extensionDatas[m_seletedExtension].updates.newKeywords.at(idx).unit; } else { if (!m_extensionDatas[m_seletedExtension].keywords.at(idx).unit.isEmpty()) unit = m_extensionDatas[m_seletedExtension].keywords.at(idx).unit; } addUnitDialog = new FITSHeaderEditAddUnitDialog(unit); if (addUnitDialog->exec() == QDialog::Accepted) { if (fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).unit = addUnitDialog->unit(); if (!m_extensionDatas[m_seletedExtension].updates.newKeywords.at(idx).unit.isEmpty()) { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.unitUpdated = true;; } } else { m_extensionDatas[m_seletedExtension].keywords.operator [](idx).unit = addUnitDialog->unit(); if (!m_extensionDatas[m_seletedExtension].keywords.at(idx).unit.isEmpty()) m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.unitUpdated = true; } emit changed(true); fillTable(); } delete addUnitDialog; } /*! * \brief Removes the selected extension from the extensions treeview * If the last extension is removed from the tree, then the extension and the file will be removed too. */ void FITSHeaderEditWidget::removeExtension() { QTreeWidgetItem* current = ui->twExtensions->currentItem(); QTreeWidgetItem* newCurrent = ui->twExtensions->itemBelow(current); if (current->parent()) { if (current->parent()->childCount() < 2) delete current->parent(); else delete current; } const QStringList keys = m_extensionDatas.keys(); const int selectedidx = keys.indexOf(m_seletedExtension); if (selectedidx > 0) { const QString& ext = m_seletedExtension; m_extensionDatas.remove(ext); m_removedExtensions.append(ext); m_seletedExtension = keys.at(selectedidx-1); fillTable(); } ui->twExtensions->setCurrentItem(newCurrent); emit changed(true); } /*! * \brief Returns a list of mandatory keywords according to the currently selected extension. * If the currently selected extension is an image then it returns the mandatory keywords of an image, * otherwise the mandatory keywords of a table * \return a list of mandatory keywords */ QList FITSHeaderEditWidget::mandatoryKeywords() const { QList mandatoryKeywords; const QTreeWidgetItem* currentItem = ui->twExtensions->currentItem(); if (currentItem->parent()->text(0).compare(QLatin1String("Images"))) mandatoryKeywords = FITSFilter::mandatoryImageExtensionKeywords(); else mandatoryKeywords = FITSFilter::mandatoryTableExtensionKeywords(); return mandatoryKeywords; } /*! * \brief Manipulates the contextmenu event of the widget * \param watched the object on which the event occurred * \param event the event watched * \return */ bool FITSHeaderEditWidget::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *cm_event = static_cast(event); const QPoint& global_pos = cm_event->globalPos(); if (watched == ui->twKeywordsTable) { if (ui->twExtensions->selectedItems().size() != 0) m_KeywordActionsMenu->exec(global_pos); } else if (watched == ui->twExtensions) { if (ui->twExtensions->selectedItems().size() != 0) { QTreeWidgetItem* current = ui->twExtensions->currentItem(); int col = ui->twExtensions->currentColumn(); if (current->parent()) { if ((current->text(col) != QLatin1String("Images")) && (current->text(col) != QLatin1String("Tables"))) m_ExtensionActionsMenu->exec(global_pos); } } } else return QWidget::eventFilter(watched, event); return true; } else return QWidget::eventFilter(watched, event); } void FITSHeaderEditWidget::closeFile() { if (ui->twExtensions->currentItem()) { QTreeWidgetItem* current = ui->twExtensions->currentItem(); int idxOfCurrentAsTopLevel = -1; for (int i = 0; i < ui->twExtensions->topLevelItemCount(); ++i) { if (current == ui->twExtensions->topLevelItem(i)) { idxOfCurrentAsTopLevel = i; break; } } QTreeWidgetItem* newCurrent = (QTreeWidgetItem*)0; if (idxOfCurrentAsTopLevel == 0) { if (ui->twExtensions->topLevelItemCount() == 1) { //last file closed, deactivate action buttons, clear keywords table ui->twKeywordsTable->setRowCount(0); ui->bClose->setEnabled(false); ui->bAddUnit->setEnabled(false); ui->bAddKey->setEnabled(false); ui->bRemoveKey->setEnabled(false); } else newCurrent = ui->twExtensions->topLevelItem(idxOfCurrentAsTopLevel + 1); } else newCurrent = ui->twExtensions->topLevelItem(idxOfCurrentAsTopLevel - 1); if (newCurrent) { m_seletedExtension = newCurrent->text(0); fillTable(); } for(const QString& key : m_extensionDatas.keys()) { if (key.startsWith(current->text(0))) m_extensionDatas.remove(key); } delete current; enableButtonAddUnit(); emit changed(true); } } void FITSHeaderEditWidget::enableButtonAddUnit() { if (ui->twKeywordsTable->currentItem() != nullptr) ui->bAddUnit->setEnabled(true); else ui->bAddUnit->setEnabled(false); } void FITSHeaderEditWidget::enableButtonCloseFile(QTreeWidgetItem* item,int col) { Q_UNUSED(col) ui->bClose->setEnabled(item->parent() ? false : true); }