diff --git a/src/kdefrontend/GuiObserver.cpp b/src/kdefrontend/GuiObserver.cpp index 082d41727..8692e3e4f 100644 --- a/src/kdefrontend/GuiObserver.cpp +++ b/src/kdefrontend/GuiObserver.cpp @@ -1,679 +1,679 @@ /*************************************************************************** File : GuiObserver.cpp Project : LabPlot Description : GUI observer -------------------------------------------------------------------- Copyright : (C) 2010-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2015-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2016 Garvit Khatri (garvitdelhi@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 "kdefrontend/GuiObserver.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/AbstractAspect.h" #include "backend/datasources/LiveDataSource.h" #include "backend/matrix/Matrix.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.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/Axis.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/TextLabel.h" #ifdef HAVE_CANTOR_LIBS #include "backend/cantorWorksheet/CantorWorksheet.h" #endif #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include "backend/datasources/MQTTSubscription.h" #include "backend/datasources/MQTTTopic.h" #endif #include "backend/core/Project.h" #include "backend/datapicker/Datapicker.h" #include "backend/datapicker/DatapickerImage.h" #include "backend/datapicker/DatapickerCurve.h" #include "commonfrontend/ProjectExplorer.h" #include "kdefrontend/MainWin.h" #include "kdefrontend/dockwidgets/AxisDock.h" #include "kdefrontend/dockwidgets/NoteDock.h" #include "kdefrontend/dockwidgets/CartesianPlotDock.h" #include "kdefrontend/dockwidgets/CartesianPlotLegendDock.h" #include "kdefrontend/dockwidgets/ColumnDock.h" #include "kdefrontend/dockwidgets/LiveDataDock.h" #include "kdefrontend/dockwidgets/MatrixDock.h" #include "kdefrontend/dockwidgets/ProjectDock.h" #include "kdefrontend/dockwidgets/SpreadsheetDock.h" #include "kdefrontend/dockwidgets/XYCurveDock.h" #include "kdefrontend/dockwidgets/HistogramDock.h" #include "kdefrontend/dockwidgets/XYEquationCurveDock.h" #include "kdefrontend/dockwidgets/XYDataReductionCurveDock.h" #include "kdefrontend/dockwidgets/XYDifferentiationCurveDock.h" #include "kdefrontend/dockwidgets/XYIntegrationCurveDock.h" #include "kdefrontend/dockwidgets/XYInterpolationCurveDock.h" #include "kdefrontend/dockwidgets/XYSmoothCurveDock.h" #include "kdefrontend/dockwidgets/XYFitCurveDock.h" #include "kdefrontend/dockwidgets/XYFourierFilterCurveDock.h" #include "kdefrontend/dockwidgets/XYFourierTransformCurveDock.h" #include "kdefrontend/dockwidgets/XYConvolutionCurveDock.h" #include "kdefrontend/dockwidgets/XYCorrelationCurveDock.h" #include "kdefrontend/dockwidgets/CustomPointDock.h" #include "kdefrontend/dockwidgets/WorksheetDock.h" #ifdef HAVE_CANTOR_LIBS #include "kdefrontend/dockwidgets/CantorWorksheetDock.h" #endif #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/widgets/DatapickerImageWidget.h" #include "kdefrontend/widgets/DatapickerCurveWidget.h" #include #include #include #include #include /*! \class GuiObserver \brief The GUI observer looks for the selection changes in the main window and shows/hides the correspondings dock widgets, toolbars etc. This class is intended to simplify (or not to overload) the code in MainWin. \ingroup kdefrontend */ GuiObserver::GuiObserver(MainWin* mainWin) { connect(mainWin->m_projectExplorer, SIGNAL(selectedAspectsChanged(QList&)), this, SLOT(selectedAspectsChanged(QList&)) ); connect(mainWin->m_projectExplorer, SIGNAL(hiddenAspectSelected(const AbstractAspect*)), this, SLOT(hiddenAspectSelected(const AbstractAspect*)) ); m_mainWindow = mainWin; } /*! called on selection changes in the project explorer. Determines the type of the currently selected objects (aspects) and activates the corresponding dockwidgets, toolbars etc. */ void GuiObserver::selectedAspectsChanged(QList& selectedAspects) const { if (selectedAspects.isEmpty()) { if (m_mainWindow->stackedWidget->currentWidget()) m_mainWindow->stackedWidget->currentWidget()->hide(); m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); return; } QString prevClassName, className; //check, whether objects of different types where selected //don't show any dock widgets in this case. for (auto* aspect : selectedAspects) { className = aspect->metaObject()->className(); if (className != prevClassName && !prevClassName.isEmpty()) { if (m_mainWindow->stackedWidget->currentWidget()) m_mainWindow->stackedWidget->currentWidget()->hide(); m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); return; } prevClassName = className; } if (m_mainWindow->stackedWidget->currentWidget()) m_mainWindow->stackedWidget->currentWidget()->show(); if (className == "Spreadsheet") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Spreadsheet")); if (!m_mainWindow->spreadsheetDock) { m_mainWindow->spreadsheetDock = new SpreadsheetDock(m_mainWindow->stackedWidget); connect(m_mainWindow->spreadsheetDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->spreadsheetDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->spreadsheetDock->setSpreadsheets(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->spreadsheetDock); } else if (className == "Column") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Column")); if (!m_mainWindow->columnDock) { m_mainWindow->columnDock = new ColumnDock(m_mainWindow->stackedWidget); connect(m_mainWindow->columnDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->columnDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->columnDock->setColumns(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->columnDock); } else if (className == "Matrix") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Matrix")); if (!m_mainWindow->matrixDock) { m_mainWindow->matrixDock = new MatrixDock(m_mainWindow->stackedWidget); connect(m_mainWindow->matrixDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->matrixDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->matrixDock->setMatrices(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->matrixDock); } else if (className == "Worksheet") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Worksheet")); if (!m_mainWindow->worksheetDock) { m_mainWindow->worksheetDock = new WorksheetDock(m_mainWindow->stackedWidget); connect(m_mainWindow->worksheetDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->worksheetDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->worksheetDock->setWorksheets(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->worksheetDock); } else if (className == "CartesianPlot") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Cartesian Plot")); if (!m_mainWindow->cartesianPlotDock) { m_mainWindow->cartesianPlotDock = new CartesianPlotDock(m_mainWindow->stackedWidget); connect(m_mainWindow->cartesianPlotDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->cartesianPlotDock->setPlots(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->cartesianPlotDock); } else if (className == "CartesianPlotLegend") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Legend")); if (!m_mainWindow->cartesianPlotLegendDock) { m_mainWindow->cartesianPlotLegendDock = new CartesianPlotLegendDock(m_mainWindow->stackedWidget); connect(m_mainWindow->cartesianPlotLegendDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotLegendDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->cartesianPlotLegendDock->setLegends(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->cartesianPlotLegendDock); } else if (className == "Axis") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Axis")); if (!m_mainWindow->axisDock) { m_mainWindow->axisDock = new AxisDock(m_mainWindow->stackedWidget); connect(m_mainWindow->axisDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->axisDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->axisDock->setAxes(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->axisDock); } else if (className == "XYCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "xy-Curve")); if (!m_mainWindow->xyCurveDock) { m_mainWindow->xyCurveDock = new XYCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyCurveDock->setupGeneral(); connect(m_mainWindow->xyCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyCurveDock); } else if (className == "XYEquationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "xy-Equation")); if (!m_mainWindow->xyEquationCurveDock) { m_mainWindow->xyEquationCurveDock = new XYEquationCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyEquationCurveDock->setupGeneral(); connect(m_mainWindow->xyEquationCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyEquationCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyEquationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyEquationCurveDock); } else if (className == "XYDataReductionCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Data Reduction")); if (!m_mainWindow->xyDataReductionCurveDock) { m_mainWindow->xyDataReductionCurveDock = new XYDataReductionCurveDock(m_mainWindow->stackedWidget, m_mainWindow->statusBar()); m_mainWindow->xyDataReductionCurveDock->setupGeneral(); connect(m_mainWindow->xyDataReductionCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyDataReductionCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyDataReductionCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyDataReductionCurveDock); } else if (className == "XYDifferentiationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Differentiation")); if (!m_mainWindow->xyDifferentiationCurveDock) { m_mainWindow->xyDifferentiationCurveDock = new XYDifferentiationCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyDifferentiationCurveDock->setupGeneral(); connect(m_mainWindow->xyDifferentiationCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyDifferentiationCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyDifferentiationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyDifferentiationCurveDock); } else if (className == "XYIntegrationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Integration")); if (!m_mainWindow->xyIntegrationCurveDock) { m_mainWindow->xyIntegrationCurveDock = new XYIntegrationCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyIntegrationCurveDock->setupGeneral(); connect(m_mainWindow->xyIntegrationCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyIntegrationCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyIntegrationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyIntegrationCurveDock); } else if (className == "XYInterpolationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Interpolation")); if (!m_mainWindow->xyInterpolationCurveDock) { m_mainWindow->xyInterpolationCurveDock = new XYInterpolationCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyInterpolationCurveDock->setupGeneral(); connect(m_mainWindow->xyInterpolationCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyInterpolationCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyInterpolationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyInterpolationCurveDock); } else if (className == "XYSmoothCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Smoothing")); if (!m_mainWindow->xySmoothCurveDock) { m_mainWindow->xySmoothCurveDock = new XYSmoothCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xySmoothCurveDock->setupGeneral(); connect(m_mainWindow->xySmoothCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xySmoothCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xySmoothCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xySmoothCurveDock); } else if (className == "XYFitCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fit")); if (!m_mainWindow->xyFitCurveDock) { m_mainWindow->xyFitCurveDock = new XYFitCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyFitCurveDock->setupGeneral(); connect(m_mainWindow->xyFitCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyFitCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyFitCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyFitCurveDock); } else if (className == "XYFourierTransformCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fourier Transform")); if (!m_mainWindow->xyFourierTransformCurveDock) { m_mainWindow->xyFourierTransformCurveDock = new XYFourierTransformCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyFourierTransformCurveDock->setupGeneral(); connect(m_mainWindow->xyFourierTransformCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyFourierTransformCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyFourierTransformCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyFourierTransformCurveDock); } else if (className == "XYFourierFilterCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fourier Filter")); if (!m_mainWindow->xyFourierFilterCurveDock) { m_mainWindow->xyFourierFilterCurveDock = new XYFourierFilterCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyFourierFilterCurveDock->setupGeneral(); connect(m_mainWindow->xyFourierFilterCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyFourierFilterCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyFourierFilterCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyFourierFilterCurveDock); } else if (className == "XYConvolutionCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Convolution/Deconvolution")); if (!m_mainWindow->xyConvolutionCurveDock) { m_mainWindow->xyConvolutionCurveDock = new XYConvolutionCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyConvolutionCurveDock->setupGeneral(); connect(m_mainWindow->xyConvolutionCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyConvolutionCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyConvolutionCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyConvolutionCurveDock); } else if (className == "XYCorrelationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Correlation")); if (!m_mainWindow->xyCorrelationCurveDock) { m_mainWindow->xyCorrelationCurveDock = new XYCorrelationCurveDock(m_mainWindow->stackedWidget); m_mainWindow->xyCorrelationCurveDock->setupGeneral(); connect(m_mainWindow->xyCorrelationCurveDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyCorrelationCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->xyCorrelationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyCorrelationCurveDock); } else if (className=="Histogram") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Histogram Properties")); if (!m_mainWindow->histogramDock) { m_mainWindow->histogramDock = new HistogramDock(m_mainWindow->stackedWidget); connect(m_mainWindow->histogramDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->histogramDock); } QList list; for (auto* aspect : selectedAspects) list<(aspect); m_mainWindow->histogramDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->histogramDock); } else if (className == "TextLabel") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Text Label")); if (!m_mainWindow->textLabelDock) { m_mainWindow->textLabelDock = new LabelWidget(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->textLabelDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->textLabelDock->setLabels(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->textLabelDock); } else if (className == "CustomPoint") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Custom Point")); if (!m_mainWindow->customPointDock) { m_mainWindow->customPointDock = new CustomPointDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->customPointDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->customPointDock->setPoints(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->customPointDock); } else if (className == "DatapickerCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Datapicker Curve")); if (!m_mainWindow->datapickerCurveDock) { m_mainWindow->datapickerCurveDock = new DatapickerCurveWidget(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->datapickerCurveDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->datapickerCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->datapickerCurveDock); } else if (className == "Datapicker") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Datapicker")); if (!m_mainWindow->datapickerImageDock) { m_mainWindow->datapickerImageDock = new DatapickerImageWidget(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->datapickerImageDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect)->image(); m_mainWindow->datapickerImageDock->setImages(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->datapickerImageDock); } else if (className == "Project") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Project")); if (!m_mainWindow->projectDock) { m_mainWindow->projectDock = new ProjectDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->projectDock); } m_mainWindow->projectDock->setProject(m_mainWindow->m_project); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->projectDock); } else if (className=="CantorWorksheet") { #ifdef HAVE_CANTOR_LIBS if (!m_mainWindow->cantorWorksheetDock) { m_mainWindow->cantorWorksheetDock = new CantorWorksheetDock(m_mainWindow->stackedWidget); connect(m_mainWindow->cantorWorksheetDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cantorWorksheetDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); if (list.size() == 1) m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window %1 is a Cantor backend", "%1 Properties", list.first()->backendName())); else m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "CAS Properties")); m_mainWindow->cantorWorksheetDock->setCantorWorksheets(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->cantorWorksheetDock); #endif } else if (className == "Notes") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Notes")); if (!m_mainWindow->notesDock) { m_mainWindow->notesDock = new NoteDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->notesDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->notesDock->setNotesList(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->notesDock); } #ifdef HAVE_MQTT - else if (className == "MQTTClient") { + else if (className == QLatin1String("MQTTClient")) { m_mainWindow->m_propertiesDock->setWindowTitle(i18n("MQTT Data Source")); if (!m_mainWindow->m_liveDataDock) { qDebug()<<"new live data dock"; m_mainWindow->m_liveDataDock = new LiveDataDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->m_liveDataDock->setMQTTClients(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); - } else if (className == "MQTTSubscription") { + } else if (className == QLatin1String("MQTTSubscription")) { m_mainWindow->m_propertiesDock->setWindowTitle(i18n("MQTT Data Source")); if (!m_mainWindow->m_liveDataDock) { qDebug()<<"new live data dock"; m_mainWindow->m_liveDataDock = new LiveDataDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } QList list; for (auto* aspect : selectedAspects) { - QString clientName = qobject_cast(aspect)->mqttClient()->name(); + const QString clientName = qobject_cast(aspect)->mqttClient()->name(); bool found = false; - for (int i = 0; i < list.size(); ++i) { - if(list.at(i)->name() == clientName) { + for (const auto* client : list) { + if(client->name() == clientName) { found = true; break; } } if(!found) list << qobject_cast(aspect)->mqttClient(); } m_mainWindow->m_liveDataDock->setMQTTClients(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); - } else if (className == "MQTTTopic") { + } else if (className == QLatin1String("MQTTTopic")) { m_mainWindow->m_propertiesDock->setWindowTitle(i18n("MQTT Data Source")); if (!m_mainWindow->m_liveDataDock) { m_mainWindow->m_liveDataDock = new LiveDataDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } QList list; for (auto* aspect : selectedAspects) { QString clientName = qobject_cast(qobject_cast(aspect)->mqttClient())->name(); bool found = false; - for (int i = 0; i < list.size(); ++i) { - if(list.at(i)->name() == clientName) { + for (const auto* client : list) { + if(client->name() == clientName) { found = true; break; } } if(!found) list << qobject_cast(qobject_cast(aspect)->mqttClient()); } m_mainWindow->m_liveDataDock->setMQTTClients(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); } #endif else if (className == "LiveDataSource") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Live Data Source")); if (!m_mainWindow->m_liveDataDock) { m_mainWindow->m_liveDataDock = new LiveDataDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect); m_mainWindow->m_liveDataDock->setLiveDataSources(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); } else { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); if (m_mainWindow->stackedWidget->currentWidget()) m_mainWindow->stackedWidget->currentWidget()->hide(); } } /*! handles the selection of a hidden aspect \c aspect in the view (relevant for WorksheetView only at the moment). Currently, a hidden aspect can only be a plot title lable or an axis label. -> Activate the corresponding DockWidget and make the title tab current. */ void GuiObserver::hiddenAspectSelected(const AbstractAspect* aspect) const { const AbstractAspect* parent = aspect->parentAspect(); if (!parent) return; QString className = parent->metaObject()->className(); if (className == "Axis") { if (!m_mainWindow->axisDock) { m_mainWindow->axisDock = new AxisDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->axisDock); } m_mainWindow->axisDock->activateTitleTab(); } else if (className == "CartesianPlot") { if (!m_mainWindow->cartesianPlotDock) { m_mainWindow->cartesianPlotDock = new CartesianPlotDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotDock); } m_mainWindow->cartesianPlotDock->activateTitleTab(); } else if (className == "CartesianPlotLegend") { if (!m_mainWindow->cartesianPlotLegendDock) { m_mainWindow->cartesianPlotLegendDock = new CartesianPlotLegendDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotLegendDock); } m_mainWindow->cartesianPlotLegendDock->activateTitleTab(); } } diff --git a/src/kdefrontend/MainWin.cpp b/src/kdefrontend/MainWin.cpp index 072542453..af14272b6 100644 --- a/src/kdefrontend/MainWin.cpp +++ b/src/kdefrontend/MainWin.cpp @@ -1,1977 +1,1977 @@ /*************************************************************************** File : MainWin.cc Project : LabPlot Description : Main window of the application -------------------------------------------------------------------- Copyright : (C) 2008-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-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 "MainWin.h" #include "backend/core/Project.h" #include "backend/core/Folder.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Workbook.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/worksheet/Worksheet.h" #include "backend/datasources/LiveDataSource.h" #ifdef HAVE_LIBORIGIN #include "backend/datasources/projects/OriginProjectParser.h" #endif #ifdef HAVE_CANTOR_LIBS #include "backend/cantorWorksheet/CantorWorksheet.h" #endif #include "backend/datapicker/Datapicker.h" #include "backend/note/Note.h" #include "backend/lib/macros.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include "commonfrontend/core/PartMdiView.h" #include "commonfrontend/ProjectExplorer.h" #include "commonfrontend/matrix/MatrixView.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "commonfrontend/worksheet/WorksheetView.h" #ifdef HAVE_CANTOR_LIBS #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" #endif #include "commonfrontend/datapicker/DatapickerView.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "commonfrontend/note/NoteView.h" #include "kdefrontend/datasources/ImportFileDialog.h" #include "kdefrontend/datasources/ImportProjectDialog.h" #include "kdefrontend/datasources/ImportSQLDatabaseDialog.h" #include "kdefrontend/dockwidgets/ProjectDock.h" #include "kdefrontend/HistoryDialog.h" #include "kdefrontend/SettingsDialog.h" #include "kdefrontend/GuiObserver.h" #include "kdefrontend/widgets/FITSHeaderEditDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CANTOR_LIBS #include #endif /*! \class MainWin \brief Main application window. \ingroup kdefrontend */ MainWin::MainWin(QWidget *parent, const QString& filename) : KXmlGuiWindow(parent), m_currentSubWindow(nullptr), m_project(nullptr), m_aspectTreeModel(nullptr), m_projectExplorer(nullptr), m_projectExplorerDock(nullptr), m_propertiesDock(nullptr), m_currentAspect(nullptr), m_currentFolder(nullptr), m_suppressCurrentSubWindowChangedEvent(false), m_closing(false), m_autoSaveActive(false), m_visibilityMenu(nullptr), m_newMenu(nullptr), m_editMenu(nullptr), axisDock(nullptr), notesDock(nullptr), cartesianPlotDock(nullptr), cartesianPlotLegendDock(nullptr), columnDock(nullptr), m_liveDataDock(nullptr), matrixDock(nullptr), spreadsheetDock(nullptr), projectDock(nullptr), xyCurveDock(nullptr), xyEquationCurveDock(nullptr), xyDataReductionCurveDock(nullptr), xyDifferentiationCurveDock(nullptr), xyIntegrationCurveDock(nullptr), xyInterpolationCurveDock(nullptr), xySmoothCurveDock(nullptr), xyFitCurveDock(nullptr), xyFourierFilterCurveDock(nullptr), xyFourierTransformCurveDock(nullptr), xyConvolutionCurveDock(nullptr), xyCorrelationCurveDock(nullptr), histogramDock(nullptr), worksheetDock(nullptr), textLabelDock(nullptr), customPointDock(nullptr), datapickerImageDock(nullptr), datapickerCurveDock(nullptr), #ifdef HAVE_CANTOR_LIBS cantorWorksheetDock(nullptr), #endif m_guiObserver(nullptr) { initGUI(filename); setAcceptDrops(true); //restore the geometry KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); restoreGeometry(group.readEntry("geometry", QByteArray())); } MainWin::~MainWin() { //save the recent opened files m_recentProjectsAction->saveEntries( KSharedConfig::openConfig()->group("Recent Files") ); KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); group.writeEntry("geometry", saveGeometry()); KSharedConfig::openConfig()->sync(); if (m_project != nullptr) { m_mdiArea->closeAllSubWindows(); disconnect(m_project, nullptr, this, nullptr); delete m_project; } if (m_aspectTreeModel) delete m_aspectTreeModel; if (m_guiObserver) delete m_guiObserver; } void MainWin::showPresenter() { Worksheet* w = activeWorksheet(); if (w) { auto* view = dynamic_cast(w->view()); view->presenterMode(); } else { //currently active object is not a worksheet but we're asked to start in the presenter mode //determine the first available worksheet and show it in the presenter mode QVector worksheets = m_project->children(); if (worksheets.size() > 0) { auto* view = qobject_cast(worksheets.first()->view()); view->presenterMode(); } else { QMessageBox::information(this, i18n("Presenter Mode"), i18n("No worksheets are available in the project. The presenter mode will not be started.")); } } } AspectTreeModel* MainWin::model() const { return m_aspectTreeModel; } Project* MainWin::project() const { return m_project; } void MainWin::initGUI(const QString& fileName) { m_mdiArea = new QMdiArea; setCentralWidget(m_mdiArea); connect(m_mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(handleCurrentSubWindowChanged(QMdiSubWindow*))); statusBar()->showMessage(i18nc("%1 is the LabPlot version", "Welcome to LabPlot %1", QLatin1String(LVERSION))); initActions(); #ifdef Q_OS_DARWIN setupGUI(Default, QLatin1String("/Applications/labplot2.app/Contents/Resources/labplot2ui.rc")); #else setupGUI(Default, QLatin1String("labplot2ui.rc")); #endif //all toolbars created via the KXMLGUI framework are locked on default: // * on the very first program start, unlock all toolbars // * on later program starts, set stored lock status //Furthermore, we want to show icons only after the first program start. KConfigGroup groupMain = KSharedConfig::openConfig()->group("MainWindow"); if (groupMain.exists()) { //KXMLGUI framework automatically stores "Disabled" for the key "ToolBarsMovable" //in case the toolbars are locked -> load this value const QString& str = groupMain.readEntry(QLatin1String("ToolBarsMovable"), ""); bool locked = (str == QLatin1String("Disabled")); KToolBar::setToolBarsLocked(locked); } else { //first start KToolBar::setToolBarsLocked(false); //show icons only for (auto* container : factory()->containers(QLatin1String("ToolBar"))) { auto* toolbar = dynamic_cast(container); if (toolbar) toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); } } initMenus(); auto* mainToolBar = qobject_cast(factory()->container("main_toolbar", this)); if (!mainToolBar) { QMessageBox::critical(this, i18n("GUI configuration file not found"), i18n("labplot2ui.rc file was not found. Please check your installation.")); //TODO: the application is not really usable if the rc file was not found. We should quit the application. The following line crashes //the application because of the splash screen. We need to find another solution. // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); //call close as soon as we enter the eventloop return; } auto* tbImport = new QToolButton(mainToolBar); tbImport->setPopupMode(QToolButton::MenuButtonPopup); tbImport->setMenu(m_importMenu); tbImport->setDefaultAction(m_importFileAction); mainToolBar->addWidget(tbImport); qobject_cast(factory()->container("import", this))->setIcon(QIcon::fromTheme("document-import")); setWindowIcon(QIcon::fromTheme("LabPlot2", QGuiApplication::windowIcon())); setAttribute( Qt::WA_DeleteOnClose ); //make the status bar of a fixed size in order to avoid height changes when placing a ProgressBar there. QFont font; font.setFamily(font.defaultFamily()); QFontMetrics fm(font); statusBar()->setFixedHeight(fm.height()+5); //load recently used projects m_recentProjectsAction->loadEntries( KSharedConfig::openConfig()->group("Recent Files") ); //set the view mode of the mdi area KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); int viewMode = group.readEntry("ViewMode", 0); if (viewMode == 1) { m_mdiArea->setViewMode(QMdiArea::TabbedView); int tabPosition = group.readEntry("TabPosition", 0); m_mdiArea->setTabPosition(QTabWidget::TabPosition(tabPosition)); m_mdiArea->setTabsClosable(true); m_mdiArea->setTabsMovable(true); m_tileWindows->setVisible(false); m_cascadeWindows->setVisible(false); } //auto-save m_autoSaveActive = group.readEntry("AutoSave", false); int interval = group.readEntry("AutoSaveInterval", 1); interval = interval*60*1000; m_autoSaveTimer.setInterval(interval); connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSaveProject())); if (!fileName.isEmpty()) { #ifdef HAVE_LIBORIGIN if (Project::isLabPlotProject(fileName) || OriginProjectParser::isOriginProject(fileName)) { #else if (Project::isLabPlotProject(fileName)) { #endif QTimer::singleShot(0, this, [=] () { openProject(fileName); }); } else { newProject(); QTimer::singleShot(0, this, [=] () { importFileDialog(fileName); }); } } else { //There is no file to open. Depending on the settings do nothing, //create a new project or open the last used project. int load = group.readEntry("LoadOnStart", 0); if (load == 1) //create new project newProject(); else if (load == 2) { //create new project with a worksheet newProject(); newWorksheet(); } else if (load == 3) { //open last used project if (!m_recentProjectsAction->urls().isEmpty()) { QDEBUG("TO OPEN m_recentProjectsAction->urls() =" << m_recentProjectsAction->urls().first()); openRecentProject( m_recentProjectsAction->urls().constFirst() ); } } } updateGUIOnProjectChanges(); } void MainWin::initActions() { // ******************** File-menu ******************************* //add some standard actions KStandardAction::openNew(this, SLOT(newProject()),actionCollection()); KStandardAction::open(this, SLOT(openProject()),actionCollection()); m_recentProjectsAction = KStandardAction::openRecent(this, SLOT(openRecentProject(QUrl)),actionCollection()); m_closeAction = KStandardAction::close(this, SLOT(closeProject()),actionCollection()); actionCollection()->setDefaultShortcut(m_closeAction, QKeySequence()); //remove the shortcut, QKeySequence::Close will be used for closing sub-windows m_saveAction = KStandardAction::save(this, SLOT(saveProject()),actionCollection()); m_saveAsAction = KStandardAction::saveAs(this, SLOT(saveProjectAs()),actionCollection()); m_printAction = KStandardAction::print(this, SLOT(print()),actionCollection()); m_printPreviewAction = KStandardAction::printPreview(this, SLOT(printPreview()),actionCollection()); KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollection()); //New Folder/Workbook/Spreadsheet/Matrix/Worksheet/Datasources m_newWorkbookAction = new QAction(QIcon::fromTheme("labplot-workbook-new"),i18n("Workbook"),this); actionCollection()->addAction("new_workbook", m_newWorkbookAction); connect(m_newWorkbookAction, SIGNAL(triggered()), SLOT(newWorkbook())); m_newDatapickerAction = new QAction(QIcon::fromTheme("color-picker-black"), i18n("Datapicker"), this); actionCollection()->addAction("new_datapicker", m_newDatapickerAction); connect(m_newDatapickerAction, SIGNAL(triggered()), SLOT(newDatapicker())); m_newSpreadsheetAction = new QAction(QIcon::fromTheme("labplot-spreadsheet-new"),i18n("Spreadsheet"),this); // m_newSpreadsheetAction->setShortcut(Qt::CTRL+Qt::Key_Equal); actionCollection()->addAction("new_spreadsheet", m_newSpreadsheetAction); connect(m_newSpreadsheetAction, SIGNAL(triggered()), SLOT(newSpreadsheet())); m_newMatrixAction = new QAction(QIcon::fromTheme("labplot-matrix-new"),i18n("Matrix"),this); // m_newMatrixAction->setShortcut(Qt::CTRL+Qt::Key_Equal); actionCollection()->addAction("new_matrix", m_newMatrixAction); connect(m_newMatrixAction, SIGNAL(triggered()), SLOT(newMatrix())); m_newWorksheetAction= new QAction(QIcon::fromTheme("labplot-worksheet-new"),i18n("Worksheet"),this); // m_newWorksheetAction->setShortcut(Qt::ALT+Qt::Key_X); actionCollection()->addAction("new_worksheet", m_newWorksheetAction); connect(m_newWorksheetAction, SIGNAL(triggered()), SLOT(newWorksheet())); m_newNotesAction= new QAction(QIcon::fromTheme("document-new"),i18n("Note"),this); actionCollection()->addAction("new_notes", m_newNotesAction); connect(m_newNotesAction, SIGNAL(triggered()), SLOT(newNotes())); // m_newScriptAction = new QAction(QIcon::fromTheme("insert-text"),i18n("Note/Script"),this); // actionCollection()->addAction("new_script", m_newScriptAction); // connect(m_newScriptAction, SIGNAL(triggered()),SLOT(newScript())); m_newFolderAction = new QAction(QIcon::fromTheme("folder-new"),i18n("Folder"),this); actionCollection()->addAction("new_folder", m_newFolderAction); connect(m_newFolderAction, SIGNAL(triggered()), SLOT(newFolder())); //"New file datasources" m_newLiveDataSourceAction = new QAction(QIcon::fromTheme("application-octet-stream"),i18n("Live Data Source"),this); actionCollection()->addAction("new_live_datasource", m_newLiveDataSourceAction); connect(m_newLiveDataSourceAction, SIGNAL(triggered()), this, SLOT(newLiveDataSourceActionTriggered())); //Import/Export m_importFileAction = new QAction(QIcon::fromTheme("document-import"), i18n("From File"), this); actionCollection()->setDefaultShortcut(m_importFileAction, Qt::CTRL+Qt::SHIFT+Qt::Key_I); actionCollection()->addAction("import_file", m_importFileAction); connect(m_importFileAction, SIGNAL(triggered()), SLOT(importFileDialog())); m_importSqlAction = new QAction(QIcon::fromTheme("document-import-database"), i18n("From SQL Database"), this); actionCollection()->addAction("import_sql", m_importSqlAction); connect(m_importSqlAction, SIGNAL(triggered()),SLOT(importSqlDialog())); m_importLabPlotAction = new QAction(QIcon::fromTheme("document-import"), i18n("LabPlot Project"), this); actionCollection()->addAction("import_labplot", m_importLabPlotAction); connect(m_importLabPlotAction, SIGNAL(triggered()),SLOT(importProjectDialog())); #ifdef HAVE_LIBORIGIN m_importOpjAction = new QAction(QIcon::fromTheme("document-import-database"), i18n("Origin Project (OPJ)"), this); actionCollection()->addAction("import_opj", m_importOpjAction); connect(m_importOpjAction, SIGNAL(triggered()),SLOT(importProjectDialog())); #endif m_exportAction = new QAction(QIcon::fromTheme("document-export"), i18n("Export"), this); actionCollection()->setDefaultShortcut(m_exportAction, Qt::CTRL+Qt::SHIFT+Qt::Key_E); actionCollection()->addAction("export", m_exportAction); connect(m_exportAction, SIGNAL(triggered()), SLOT(exportDialog())); m_editFitsFileAction = new QAction(i18n("FITS Metadata Editor"), this); actionCollection()->addAction("edit_fits", m_editFitsFileAction); connect(m_editFitsFileAction, SIGNAL(triggered()), SLOT(editFitsFileDialog())); // Edit //Undo/Redo-stuff m_undoAction = KStandardAction::undo(this, SLOT(undo()), actionCollection()); m_redoAction = KStandardAction::redo(this, SLOT(redo()), actionCollection()); m_historyAction = new QAction(QIcon::fromTheme("view-history"), i18n("Undo/Redo History"),this); actionCollection()->addAction("history", m_historyAction); connect(m_historyAction, SIGNAL(triggered()), SLOT(historyDialog())); // TODO: more menus // Appearance // Analysis: see WorksheetView.cpp // Drawing // Script //Windows QAction* action = new QAction(i18n("&Close"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::Close); action->setStatusTip(i18n("Close the active window")); actionCollection()->addAction("close window", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(closeActiveSubWindow())); action = new QAction(i18n("Close &All"), this); action->setStatusTip(i18n("Close all the windows")); actionCollection()->addAction("close all windows", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(closeAllSubWindows())); m_tileWindows = new QAction(i18n("&Tile"), this); m_tileWindows->setStatusTip(i18n("Tile the windows")); actionCollection()->addAction("tile windows", m_tileWindows); connect(m_tileWindows, SIGNAL(triggered()), m_mdiArea, SLOT(tileSubWindows())); m_cascadeWindows = new QAction(i18n("&Cascade"), this); m_cascadeWindows->setStatusTip(i18n("Cascade the windows")); actionCollection()->addAction("cascade windows", m_cascadeWindows); connect(m_cascadeWindows, SIGNAL(triggered()), m_mdiArea, SLOT(cascadeSubWindows())); action = new QAction(QIcon::fromTheme("go-next-view"), i18n("Ne&xt"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::NextChild); action->setStatusTip(i18n("Move the focus to the next window")); actionCollection()->addAction("next window", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(activateNextSubWindow())); action = new QAction(QIcon::fromTheme("go-previous-view"), i18n("Pre&vious"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::PreviousChild); action->setStatusTip(i18n("Move the focus to the previous window")); actionCollection()->addAction("previous window", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(activatePreviousSubWindow())); //"Standard actions" KStandardAction::preferences(this, SLOT(settingsDialog()), actionCollection()); KStandardAction::quit(this, SLOT(close()), actionCollection()); //Actions for window visibility auto* windowVisibilityActions = new QActionGroup(this); windowVisibilityActions->setExclusive(true); m_visibilityFolderAction = new QAction(QIcon::fromTheme("folder"), i18n("Current &Folder Only"), windowVisibilityActions); m_visibilityFolderAction->setCheckable(true); m_visibilityFolderAction->setData(Project::folderOnly); m_visibilitySubfolderAction = new QAction(QIcon::fromTheme("folder-documents"), i18n("Current Folder and &Subfolders"), windowVisibilityActions); m_visibilitySubfolderAction->setCheckable(true); m_visibilitySubfolderAction->setData(Project::folderAndSubfolders); m_visibilityAllAction = new QAction(i18n("&All"), windowVisibilityActions); m_visibilityAllAction->setCheckable(true); m_visibilityAllAction->setData(Project::allMdiWindows); connect(windowVisibilityActions, SIGNAL(triggered(QAction*)), this, SLOT(setMdiWindowVisibility(QAction*))); //Actions for hiding/showing the dock widgets auto* docksActions = new QActionGroup(this); docksActions->setExclusive(false); m_toggleProjectExplorerDockAction = new QAction(QIcon::fromTheme("view-list-tree"), i18n("Project Explorer"), docksActions); m_toggleProjectExplorerDockAction->setCheckable(true); m_toggleProjectExplorerDockAction->setChecked(true); actionCollection()->addAction("toggle_project_explorer_dock", m_toggleProjectExplorerDockAction); m_togglePropertiesDockAction = new QAction(QIcon::fromTheme("view-list-details"), i18n("Properties Explorer"), docksActions); m_togglePropertiesDockAction->setCheckable(true); m_togglePropertiesDockAction->setChecked(true); actionCollection()->addAction("toggle_properties_explorer_dock", m_togglePropertiesDockAction); connect(docksActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleDockWidget(QAction*))); } void MainWin::initMenus() { //menu for adding new aspects m_newMenu = new QMenu(i18n("Add New"), this); m_newMenu->setIcon(QIcon::fromTheme("document-new")); m_newMenu->addAction(m_newFolderAction); m_newMenu->addAction(m_newWorkbookAction); m_newMenu->addAction(m_newSpreadsheetAction); m_newMenu->addAction(m_newMatrixAction); m_newMenu->addAction(m_newWorksheetAction); m_newMenu->addAction(m_newNotesAction); m_newMenu->addAction(m_newDatapickerAction); m_newMenu->addSeparator(); m_newMenu->addAction(m_newLiveDataSourceAction); //import menu m_importMenu = new QMenu(this); m_importMenu->setIcon(QIcon::fromTheme("document-import")); m_importMenu ->addAction(m_importFileAction); m_importMenu ->addAction(m_importSqlAction); m_importMenu->addSeparator(); m_importMenu->addAction(m_importLabPlotAction); #ifdef HAVE_LIBORIGIN m_importMenu ->addAction(m_importOpjAction); #endif #ifdef HAVE_CANTOR_LIBS m_newMenu->addSeparator(); m_newCantorWorksheetMenu = new QMenu(i18n("CAS Worksheet")); m_newCantorWorksheetMenu->setIcon(QIcon::fromTheme("archive-insert")); //"Adding Cantor backends to menue and context menu" QStringList m_availableBackend = Cantor::Backend::listAvailableBackends(); if(m_availableBackend.count() > 0) { unplugActionList(QLatin1String("backends_list")); QList newBackendActions; for (Cantor::Backend* backend : Cantor::Backend::availableBackends()) { if (!backend->isEnabled()) continue; QAction* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(),this); action->setData(backend->name()); newBackendActions << action; m_newCantorWorksheetMenu->addAction(action); } connect(m_newCantorWorksheetMenu, SIGNAL(triggered(QAction*)), this, SLOT(newCantorWorksheet(QAction*))); plugActionList(QLatin1String("backends_list"), newBackendActions); } m_newMenu->addMenu(m_newCantorWorksheetMenu); #else delete this->guiFactory()->container("cas_worksheet", this); delete this->guiFactory()->container("new_cas_worksheet", this); delete this->guiFactory()->container("cas_worksheet_toolbar", this); #endif //menu subwindow visibility policy m_visibilityMenu = new QMenu(i18n("Window Visibility Policy"), this); m_visibilityMenu->setIcon(QIcon::fromTheme("window-duplicate")); m_visibilityMenu ->addAction(m_visibilityFolderAction); m_visibilityMenu ->addAction(m_visibilitySubfolderAction); m_visibilityMenu ->addAction(m_visibilityAllAction); //menu for editing files m_editMenu = new QMenu(i18n("Edit"), this); m_editMenu->addAction(m_editFitsFileAction); KColorSchemeManager schemeManager; KActionMenu* schemesMenu = schemeManager.createSchemeSelectionMenu(i18n("Color Theme"), this); schemesMenu->menu()->setTitle(i18n("Color Theme")); schemesMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color"))); QMenu* settingsMenu = dynamic_cast(factory()->container("settings", this)); if (settingsMenu) settingsMenu->insertMenu(settingsMenu->actions().constFirst(), schemesMenu->menu()); //set the action for the current color scheme checked KConfigGroup generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General"); QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze")); KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); QString schemeName = group.readEntry("ColorScheme", defaultSchemeName); for (auto* action : schemesMenu->menu()->actions()) { if (action->text() == schemeName) { action->setChecked(true); break; } } connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged); } void MainWin::colorSchemeChanged(QAction* action) { QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text()); //background of the mdi area is not updated on theme changes, do it here. KColorSchemeManager schemeManager; QModelIndex index = schemeManager.indexForScheme(schemeName); const QPalette& palette = KColorScheme::createApplicationPalette( KSharedConfig::openConfig(index.data(Qt::UserRole).toString()) ); const QBrush& brush = palette.brush(QPalette::Dark); m_mdiArea->setBackground(brush); //save the selected color scheme KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); group.writeEntry("ColorScheme", schemeName); group.sync(); } /*! Asks to save the project if it was modified. \return \c true if the project still needs to be saved ("cancel" clicked), \c false otherwise. */ bool MainWin::warnModified() { if (m_project->hasChanged()) { int want_save = KMessageBox::warningYesNoCancel( this, i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()), i18n("Save Project")); switch (want_save) { case KMessageBox::Yes: return !saveProject(); case KMessageBox::No: break; case KMessageBox::Cancel: return true; } } return false; } /*! * updates the state of actions, menus and toolbars (enabled or disabled) * on project changes (project closes and opens) */ void MainWin::updateGUIOnProjectChanges() { if (m_closing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } //disable all menus if there is no project bool b = (m_project == nullptr); m_saveAction->setEnabled(!b); m_saveAsAction->setEnabled(!b); m_printAction->setEnabled(!b); m_printPreviewAction->setEnabled(!b); m_importFileAction->setEnabled(!b); m_importSqlAction->setEnabled(!b); #ifdef HAVE_LIBORIGIN m_importOpjAction->setEnabled(!b); #endif m_exportAction->setEnabled(!b); m_newWorkbookAction->setEnabled(!b); m_newSpreadsheetAction->setEnabled(!b); m_newMatrixAction->setEnabled(!b); m_newWorksheetAction->setEnabled(!b); m_newDatapickerAction->setEnabled(!b); m_closeAction->setEnabled(!b); m_toggleProjectExplorerDockAction->setEnabled(!b); m_togglePropertiesDockAction->setEnabled(!b); if (!m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif } factory->container("new", this)->setEnabled(!b); factory->container("edit", this)->setEnabled(!b); factory->container("import", this)->setEnabled(!b); if (b) setCaption("LabPlot2"); else setCaption(m_project->name()); // undo/redo actions are disabled in both cases - when the project is closed or opened m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } /* * updates the state of actions, menus and toolbars (enabled or disabled) * depending on the currently active window (worksheet or spreadsheet). */ void MainWin::updateGUI() { if (m_project->isLoading()) return; if (m_closing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } if (!m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif return; } //Handle the Worksheet-object Worksheet* w = this->activeWorksheet(); if (w != nullptr) { //enable worksheet related menus factory->container("worksheet", this)->setEnabled(true); factory->container("analysis", this)->setEnabled(true); //TODO factory->container("drawing", this)->setEnabled(true); //disable spreadsheet and matrix related menus factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); //populate worksheet menu auto* view = qobject_cast(w->view()); auto* menu = qobject_cast(factory->container("worksheet", this)); menu->clear(); view->createContextMenu(menu); //populate analysis menu menu = qobject_cast(factory->container("analysis", this)); menu->clear(); view->createAnalysisMenu(menu); //populate worksheet-toolbar auto* toolbar = qobject_cast(factory->container("worksheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the toolbar for cartesian plots toolbar = qobject_cast(factory->container("cartesian_plot_toolbar", this)); toolbar->clear(); view->fillCartesianPlotToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //hide the spreadsheet toolbar factory->container("spreadsheet_toolbar", this)->setVisible(false); } else { factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); // factory->container("drawing", this)->setEnabled(false); factory->container("worksheet_toolbar", this)->setEnabled(false); factory->container("cartesian_plot_toolbar", this)->setEnabled(false); } //Handle the Spreadsheet-object const auto* spreadsheet = this->activeSpreadsheet(); if (spreadsheet) { //enable spreadsheet related menus factory->container("spreadsheet", this)->setEnabled(true); //populate spreadsheet-menu auto* view = qobject_cast(spreadsheet->view()); auto* menu = qobject_cast(factory->container("spreadsheet", this)); menu->clear(); view->createContextMenu(menu); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("spreadsheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); } else { factory->container("spreadsheet", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->setEnabled(false); } //Handle the Matrix-object const Matrix* matrix = this->activeMatrix(); if (matrix) { factory->container("matrix", this)->setEnabled(true); //populate matrix-menu auto* view = qobject_cast(matrix->view()); auto* menu = qobject_cast(factory->container("matrix", this)); menu->clear(); view->createContextMenu(menu); } else factory->container("matrix", this)->setEnabled(false); #ifdef HAVE_CANTOR_LIBS CantorWorksheet* cantorworksheet = this->activeCantorWorksheet(); if(cantorworksheet) { // enable Cantor Worksheet related menus factory->container("cas_worksheet", this)->setEnabled(true); auto* view = qobject_cast(cantorworksheet->view()); auto* menu = qobject_cast(factory->container("cas_worksheet", this)); menu->clear(); view->createContextMenu(menu); auto* toolbar = qobject_cast(factory->container("cas_worksheet_toolbar", this)); toolbar->setVisible(true); toolbar->clear(); view->fillToolBar(toolbar); } else { //no Cantor worksheet selected -> deactivate Cantor worksheet related menu and toolbar factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->setVisible(false); } #endif const Datapicker* datapicker = this->activeDatapicker(); if (datapicker) { factory->container("datapicker", this)->setEnabled(true); //populate datapicker-menu auto* view = qobject_cast(datapicker->view()); auto* menu = qobject_cast(factory->container("datapicker", this)); menu->clear(); view->createContextMenu(menu); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("datapicker_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); } else { factory->container("datapicker", this)->setEnabled(false); factory->container("datapicker_toolbar", this)->setVisible(false); } } /*! creates a new empty project. Returns \c true, if a new project was created. */ bool MainWin::newProject() { //close the current project, if available if (!closeProject()) return false; QApplication::processEvents(QEventLoop::AllEvents, 100); if (m_project) delete m_project; if (m_aspectTreeModel) delete m_aspectTreeModel; m_project = new Project(); m_currentAspect = m_project; m_currentFolder = m_project; KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); Project::MdiWindowVisibility vis = Project::MdiWindowVisibility(group.readEntry("MdiWindowVisibility", 0)); m_project->setMdiWindowVisibility( vis ); if (vis == Project::folderOnly) m_visibilityFolderAction->setChecked(true); else if (vis == Project::folderAndSubfolders) m_visibilitySubfolderAction->setChecked(true); else m_visibilityAllAction->setChecked(true); m_aspectTreeModel = new AspectTreeModel(m_project, this); //newProject is called for the first time, there is no project explorer yet //-> initialize the project explorer, the GUI-observer and the dock widgets. if (m_projectExplorer == nullptr) { m_projectExplorerDock = new QDockWidget(this); m_projectExplorerDock->setObjectName("projectexplorer"); m_projectExplorerDock->setWindowTitle(i18nc("@title:window", "Project Explorer")); addDockWidget(Qt::LeftDockWidgetArea, m_projectExplorerDock); m_projectExplorer = new ProjectExplorer(m_projectExplorerDock); m_projectExplorerDock->setWidget(m_projectExplorer); connect(m_projectExplorer, SIGNAL(currentAspectChanged(AbstractAspect*)), this, SLOT(handleCurrentAspectChanged(AbstractAspect*))); connect(m_projectExplorerDock, SIGNAL(visibilityChanged(bool)), SLOT(projectExplorerDockVisibilityChanged(bool))); //Properties dock m_propertiesDock = new QDockWidget(this); m_propertiesDock->setObjectName("aspect_properties_dock"); m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); addDockWidget(Qt::RightDockWidgetArea, m_propertiesDock); auto* sa = new QScrollArea(m_propertiesDock); stackedWidget = new QStackedWidget(sa); sa->setWidget(stackedWidget); sa->setWidgetResizable(true); m_propertiesDock->setWidget(sa); connect(m_propertiesDock, SIGNAL(visibilityChanged(bool)), SLOT(propertiesDockVisibilityChanged(bool))); //GUI-observer; m_guiObserver = new GuiObserver(this); } m_projectExplorer->setModel(m_aspectTreeModel); m_projectExplorer->setProject(m_project); m_projectExplorer->setCurrentAspect(m_project); m_projectExplorerDock->show(); m_propertiesDock->show(); updateGUIOnProjectChanges(); connect(m_project, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(handleAspectAdded(const AbstractAspect*))); connect(m_project, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), this, SLOT(handleAspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*))); connect(m_project, SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(handleAspectAboutToBeRemoved(const AbstractAspect*))); connect(m_project, SIGNAL(statusInfo(QString)), statusBar(), SLOT(showMessage(QString))); connect(m_project, SIGNAL(changed()), this, SLOT(projectChanged())); connect(m_project, SIGNAL(requestProjectContextMenu(QMenu*)), this, SLOT(createContextMenu(QMenu*))); connect(m_project, SIGNAL(requestFolderContextMenu(const Folder*,QMenu*)), this, SLOT(createFolderContextMenu(const Folder*,QMenu*))); connect(m_project, SIGNAL(mdiWindowVisibilityChanged()), this, SLOT(updateMdiWindowVisibility())); m_undoViewEmptyLabel = i18n("%1: created", m_project->name()); setCaption(m_project->name()); return true; } void MainWin::openProject() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); const QString& path = QFileDialog::getOpenFileName(this,i18n("Open Project"), dir, #ifdef HAVE_LIBORIGIN i18n("LabPlot Projects (%1);;Origin Projects (%2)", Project::supportedExtensions(), OriginProjectParser::supportedExtensions()) ); #else i18n("LabPlot Projects (%1)", Project::supportedExtensions()) ); #endif if (path.isEmpty())// "Cancel" was clicked return; this->openProject(path); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } } void MainWin::openProject(const QString& filename) { if (filename == m_currentFileName) { KMessageBox::information(this, i18n("The project file %1 is already opened.", filename), i18n("Open Project")); return; } if (!newProject()) return; WAIT_CURSOR; QElapsedTimer timer; timer.start(); bool rc = false; if (Project::isLabPlotProject(filename)) rc = m_project->load(filename); #ifdef HAVE_LIBORIGIN else if (OriginProjectParser::isOriginProject(filename)) { OriginProjectParser parser; parser.setProjectFileName(filename); parser.importTo(m_project, QStringList()); //TODO: add return code rc = true; } #endif if (!rc) { closeProject(); RESET_CURSOR; return; } m_currentFileName = filename; m_project->setFileName(filename); m_project->undoStack()->clear(); m_undoViewEmptyLabel = i18n("%1: opened", m_project->name()); m_recentProjectsAction->addUrl( QUrl(filename) ); setCaption(m_project->name()); updateGUIOnProjectChanges(); updateGUI(); //there are most probably worksheets or spreadsheets in the open project -> update the GUI m_saveAction->setEnabled(false); statusBar()->showMessage( i18n("Project successfully opened (in %1 seconds).", (float)timer.elapsed()/1000) ); if (m_autoSaveActive) m_autoSaveTimer.start(); RESET_CURSOR; } void MainWin::openRecentProject(const QUrl& url) { if (url.isLocalFile()) // fix for Windows this->openProject(url.toLocalFile()); else this->openProject(url.path()); } /*! Closes the current project, if available. Return \c true, if the project was closed. */ bool MainWin::closeProject() { if (m_project == nullptr) return true; //nothing to close if (warnModified()) return false; delete m_aspectTreeModel; m_aspectTreeModel = nullptr; delete m_project; m_project = nullptr; m_currentFileName = ""; //update the UI if we're just closing a project //and not closing(quitting) the application if (!m_closing) { m_projectExplorerDock->hide(); m_propertiesDock->hide(); m_currentAspect = nullptr; m_currentFolder = nullptr; updateGUIOnProjectChanges(); if (m_autoSaveActive) m_autoSaveTimer.stop(); } return true; } bool MainWin::saveProject() { const QString& fileName = m_project->fileName(); if (fileName.isEmpty()) return saveProjectAs(); else return save(fileName); } bool MainWin::saveProjectAs() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); QString path = QFileDialog::getSaveFileName(this, i18n("Save Project As"), dir, i18n("LabPlot Projects (*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ)")); if (path.isEmpty())// "Cancel" was clicked return false; if (path.contains(QLatin1String(".lml"), Qt::CaseInsensitive) == false) path.append(QLatin1String(".lml")); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } return save(path); } /*! * auxiliary function that does the actual saving of the project */ bool MainWin::save(const QString& fileName) { WAIT_CURSOR; // use file ending to find out how to compress file QIODevice* file; // if ending is .lml, do gzip compression anyway if (fileName.endsWith(QLatin1String(".lml"))) file = new KCompressionDevice(fileName, KCompressionDevice::GZip); else file = new KFilterDev(fileName); if (file == nullptr) file = new QFile(fileName); bool ok; if (file->open(QIODevice::WriteOnly)) { m_project->setFileName(fileName); QXmlStreamWriter writer(file); m_project->save(&writer); m_project->undoStack()->clear(); m_project->setChanged(false); file->close(); setCaption(m_project->name()); statusBar()->showMessage(i18n("Project saved")); m_saveAction->setEnabled(false); m_recentProjectsAction->addUrl( QUrl(fileName) ); ok = true; //if the project dock is visible, refresh the shown content //(version and modification time might have been changed) if (stackedWidget->currentWidget() == projectDock) projectDock->setProject(m_project); //we have a file name now // -> auto save can be activated now if not happened yet if (m_autoSaveActive && !m_autoSaveTimer.isActive()) m_autoSaveTimer.start(); } else { KMessageBox::error(this, i18n("Sorry. Could not open file for writing.")); ok = false; } delete file; RESET_CURSOR; return ok; } /*! * automatically saves the project in the specified time interval. */ void MainWin::autoSaveProject() { //don't auto save when there are no changes or the file name //was not provided yet (the project was never explicitly saved yet). if ( !m_project->hasChanged() || m_project->fileName().isEmpty()) return; this->saveProject(); } /*! prints the current sheet (worksheet, spreadsheet or matrix) */ void MainWin::print() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printView()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->showMessage(""); } void MainWin::printPreview() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printPreview()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->showMessage(""); } /**************************************************************************************/ /*! adds a new Folder to the project. */ void MainWin::newFolder() { Folder* folder = new Folder(i18n("Folder")); this->addAspectToProject(folder); } /*! adds a new Workbook to the project. */ void MainWin::newWorkbook() { Workbook* workbook = new Workbook(i18n("Workbook")); this->addAspectToProject(workbook); } /*! adds a new Datapicker to the project. */ void MainWin::newDatapicker() { Datapicker* datapicker = new Datapicker(i18n("Datapicker")); this->addAspectToProject(datapicker); } /*! adds a new Spreadsheet to the project. */ void MainWin::newSpreadsheet() { Spreadsheet* spreadsheet = new Spreadsheet(i18n("Spreadsheet")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new spreadsheet to the workbook Workbook* workbook = activeWorkbook(); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits("Folder")) { workbook->addChild(spreadsheet); return; } } this->addAspectToProject(spreadsheet); } /*! adds a new Matrix to the project. */ void MainWin::newMatrix() { Matrix* matrix = new Matrix(i18n("Matrix")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new matrix to the workbook Workbook* workbook = activeWorkbook(); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits("Folder")) { workbook->addChild(matrix); return; } } this->addAspectToProject(matrix); } /*! adds a new Worksheet to the project. */ void MainWin::newWorksheet() { Worksheet* worksheet = new Worksheet(i18n("Worksheet")); this->addAspectToProject(worksheet); } /*! adds a new Note to the project. */ void MainWin::newNotes() { Note* notes = new Note(i18n("Note")); this->addAspectToProject(notes); } /*! returns a pointer to a Workbook-object, if the currently active Mdi-Subwindow is \a WorkbookView. Otherwise returns \a 0. */ Workbook* MainWin::activeWorkbook() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } /*! returns a pointer to a Datapicker-object, if the currently active Mdi-Subwindow is \a DatapickerView. Otherwise returns \a 0. */ Datapicker* MainWin::activeDatapicker() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } /*! returns a pointer to a \c Spreadsheet object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c SpreadsheetView Otherwise returns \c 0. */ Spreadsheet* MainWin::activeSpreadsheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); Spreadsheet* spreadsheet = nullptr; const auto* workbook = dynamic_cast(part); if (workbook) { spreadsheet = workbook->currentSpreadsheet(); if (!spreadsheet) { //potentially, the spreadsheet was not selected in workbook yet since the selection in project explorer //arrives in workbook's slot later than in this function //->check whether we have a spreadsheet or one of its columns currently selected in the project explorer spreadsheet = dynamic_cast(m_currentAspect); if (!spreadsheet) { if (m_currentAspect->parentAspect()) spreadsheet = dynamic_cast(m_currentAspect->parentAspect()); } } } else spreadsheet = dynamic_cast(part); return spreadsheet; } /*! returns a pointer to a \c Matrix object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c MatrixView Otherwise returns \c 0. */ Matrix* MainWin::activeMatrix() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); Matrix* matrix = nullptr; const auto* workbook = dynamic_cast(part); if (workbook) { matrix = workbook->currentMatrix(); if (!matrix) { //potentially, the matrix was not selected in workbook yet since the selection in project explorer //arrives in workbook's slot later than in this function //->check whether we have a matrix currently selected in the project explorer matrix = dynamic_cast(m_currentAspect); } } else matrix = dynamic_cast(part); return matrix; } /*! returns a pointer to a Worksheet-object, if the currently active Mdi-Subwindow is \a WorksheetView Otherwise returns \a 0. */ Worksheet* MainWin::activeWorksheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } #ifdef HAVE_CANTOR_LIBS /* adds a new Cantor Spreadsheet to the project. */ void MainWin::newCantorWorksheet(QAction* action) { CantorWorksheet* cantorworksheet = new CantorWorksheet(action->data().toString()); this->addAspectToProject(cantorworksheet); } /********************************************************************************/ /*! returns a pointer to a CantorWorksheet-object, if the currently active Mdi-Subwindow is \a CantorWorksheetView Otherwise returns \a 0. */ CantorWorksheet* MainWin::activeCantorWorksheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } #endif /*! called if there were changes in the project. Adds "changed" to the window caption and activates the save-Action. */ void MainWin::projectChanged() { setCaption(i18n("%1 [Changed]", m_project->name())); m_saveAction->setEnabled(true); m_undoAction->setEnabled(true); return; } void MainWin::handleCurrentSubWindowChanged(QMdiSubWindow* win) { if (!win) return; auto* view = qobject_cast(win); if (!view) { updateGUI(); return; } if (view == m_currentSubWindow) { //do nothing, if the current sub-window gets selected again. //This event happens, when labplot loses the focus (modal window is opened or the user switches to another application) //and gets it back (modal window is closed or the user switches back to labplot). return; } else m_currentSubWindow = view; updateGUI(); if (!m_suppressCurrentSubWindowChangedEvent) m_projectExplorer->setCurrentAspect(view->part()); } void MainWin::handleAspectAdded(const AbstractAspect* aspect) { const auto* part = dynamic_cast(aspect); if (part) { // connect(part, &AbstractPart::importFromFileRequested, this, &MainWin::importFileDialog); connect(part, SIGNAL(importFromFileRequested()), this, SLOT(importFileDialog())); connect(part, &AbstractPart::importFromSQLDatabaseRequested, this, &MainWin::importSqlDialog); //TODO: export, print and print preview should be handled in the views and not in MainWin. connect(part, SIGNAL(exportRequested()), this, SLOT(exportDialog())); connect(part, SIGNAL(printRequested()), this, SLOT(print())); connect(part, SIGNAL(printPreviewRequested()), this, SLOT(printPreview())); connect(part, SIGNAL(showRequested()), this, SLOT(handleShowSubWindowRequested())); } } void MainWin::handleAspectRemoved(const AbstractAspect* parent,const AbstractAspect* before,const AbstractAspect* aspect) { Q_UNUSED(before); Q_UNUSED(aspect); m_projectExplorer->setCurrentAspect(parent); } void MainWin::handleAspectAboutToBeRemoved(const AbstractAspect *aspect) { const auto* part = qobject_cast(aspect); if (!part) return; const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (!workbook && !datapicker) { PartMdiView* win = part->mdiSubWindow(); if (win) m_mdiArea->removeSubWindow(win); } } /*! called when the current aspect in the tree of the project explorer was changed. Selects the new aspect. */ void MainWin::handleCurrentAspectChanged(AbstractAspect *aspect) { if (!aspect) aspect = m_project; // should never happen, just in case m_suppressCurrentSubWindowChangedEvent = true; if (aspect->folder() != m_currentFolder) { m_currentFolder = aspect->folder(); updateMdiWindowVisibility(); } m_currentAspect = aspect; //activate the corresponding MDI sub window for the current aspect activateSubWindowForAspect(aspect); m_suppressCurrentSubWindowChangedEvent = false; updateGUI(); } void MainWin::activateSubWindowForAspect(const AbstractAspect* aspect) const { const auto* part = dynamic_cast(aspect); if (part) { //for LiveDataSource we currently don't show any view /*if (dynamic_cast(part)) return;*/ PartMdiView* win; //for aspects being children of a Workbook, we show workbook's window, otherwise the window of the selected part const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (workbook) win = workbook->mdiSubWindow(); else if (datapicker) win = datapicker->mdiSubWindow(); else win = part->mdiSubWindow(); if (m_mdiArea->subWindowList().indexOf(win) == -1) { if (dynamic_cast(part)) m_mdiArea->addSubWindow(win, Qt::Tool); else m_mdiArea->addSubWindow(win); win->show(); //Qt provides its own "system menu" for every sub-window. The shortcut for the close-action //in this menu collides with our global m_closeAction. //remove the shortcuts in the system menu to avoid this collision. QMenu* menu = win->systemMenu(); if (menu) { for (QAction* action : menu->actions()) action->setShortcut(QKeySequence()); } } m_mdiArea->setActiveSubWindow(win); } else { //activate the mdiView of the parent, if a child was selected const AbstractAspect* parent = aspect->parentAspect(); if (parent) { activateSubWindowForAspect(parent); //if the parent's parent is a Workbook (a column of a spreadsheet in workbook was selected), //we need to select the corresponding tab in WorkbookView too if (parent->parentAspect()) { auto* workbook = dynamic_cast(parent->parentAspect()); auto* datapicker = dynamic_cast(parent->parentAspect()); if (!datapicker) datapicker = dynamic_cast(parent->parentAspect()->parentAspect()); if (workbook) workbook->childSelected(parent); else if (datapicker) datapicker->childSelected(parent); } } } return; } void MainWin::setMdiWindowVisibility(QAction * action) { m_project->setMdiWindowVisibility((Project::MdiWindowVisibility)(action->data().toInt())); } /*! shows the sub window of a worksheet, matrix or a spreadsheet. Used if the window was closed before and the user asks to show the window again via the context menu in the project explorer. */ void MainWin::handleShowSubWindowRequested() { activateSubWindowForAspect(m_currentAspect); } /*! this is called on a right click on the root folder in the project explorer */ void MainWin::createContextMenu(QMenu* menu) const { menu->addMenu(m_newMenu); //The tabbed view collides with the visibility policy for the subwindows. //Hide the menus for the visibility policy if the tabbed view is used. if (m_mdiArea->viewMode() != QMdiArea::TabbedView) menu->addMenu(m_visibilityMenu); } /*! this is called on a right click on a non-root folder in the project explorer */ void MainWin::createFolderContextMenu(const Folder* folder, QMenu* menu) const { Q_UNUSED(folder); //Folder provides it's own context menu. Add a separator before adding additional actions. menu->addSeparator(); this->createContextMenu(menu); } void MainWin::undo() { WAIT_CURSOR; m_project->undoStack()->undo(); if (m_project->undoStack()->index()==0) { setCaption(m_project->name()); m_saveAction->setEnabled(false); m_undoAction->setEnabled(false); m_project->setChanged(false); } m_redoAction->setEnabled(true); RESET_CURSOR; } void MainWin::redo() { WAIT_CURSOR; m_project->undoStack()->redo(); projectChanged(); if (m_project->undoStack()->index() == m_project->undoStack()->count()) m_redoAction->setEnabled(false); RESET_CURSOR; } /*! Shows/hides mdi sub-windows depending on the current visibility policy. */ void MainWin::updateMdiWindowVisibility() const { QList windows = m_mdiArea->subWindowList(); PartMdiView* part_view; switch (m_project->mdiWindowVisibility()) { case Project::allMdiWindows: for (auto* window : windows) window->show(); break; case Project::folderOnly: for (auto* window : windows) { part_view = qobject_cast(window); Q_ASSERT(part_view); if (part_view->part()->folder() == m_currentFolder) part_view->show(); else part_view->hide(); } break; case Project::folderAndSubfolders: for (auto* window : windows) { part_view = qobject_cast(window); if (part_view->part()->isDescendantOf(m_currentFolder)) part_view->show(); else part_view->hide(); } break; } } void MainWin::toggleDockWidget(QAction* action) { if (action->objectName() == "toggle_project_explorer_dock") { if (m_projectExplorerDock->isVisible()) toggleHideWidget(m_projectExplorerDock, true); else toggleShowWidget(m_projectExplorerDock, true); } else if (action->objectName() == "toggle_properties_explorer_dock") { if (m_propertiesDock->isVisible()) toggleHideWidget(m_propertiesDock, false); else toggleShowWidget(m_propertiesDock, false); } } void MainWin::toggleHideWidget(QWidget* widget, bool hideToLeft) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=] { const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (hideToLeft) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Backward); timeline->start(); connect(timeline, &QTimeLine::finished, [widget] {widget->hide();}); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } void MainWin::toggleShowWidget(QWidget* widget, bool showToRight) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=]() { if (widget->isHidden()) { widget->show(); } const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (showToRight) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Forward); timeline->start(); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } void MainWin::projectExplorerDockVisibilityChanged(bool visible) { m_toggleProjectExplorerDockAction->setChecked(visible); } void MainWin::propertiesDockVisibilityChanged(bool visible) { m_togglePropertiesDockAction->setChecked(visible); } void MainWin::toggleFullScreen() { if (this->windowState() == Qt::WindowFullScreen) this->setWindowState(m_lastWindowState); else { m_lastWindowState = this->windowState(); this->showFullScreen(); } } void MainWin::closeEvent(QCloseEvent* event) { m_closing = true; if (!this->closeProject()) { m_closing = false; event->ignore(); } } void MainWin::dragEnterEvent(QDragEnterEvent* event) { event->accept(); } void MainWin::dropEvent(QDropEvent* event) { if (event->mimeData() && !event->mimeData()->urls().isEmpty()) { QUrl url = event->mimeData()->urls().at(0); const QString& f = url.toLocalFile(); #ifdef HAVE_LIBORIGIN if (Project::isLabPlotProject(f) || OriginProjectParser::isOriginProject(f)) #else if (Project::isLabPlotProject(f)) #endif openProject(f); else { if (!m_project) newProject(); importFileDialog(f); } event->accept(); } else event->ignore(); } void MainWin::handleSettingsChanges() { const KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); QMdiArea::ViewMode viewMode = QMdiArea::ViewMode(group.readEntry("ViewMode", 0)); if (m_mdiArea->viewMode() != viewMode) { m_mdiArea->setViewMode(viewMode); if (viewMode == QMdiArea::SubWindowView) this->updateMdiWindowVisibility(); } if (m_mdiArea->viewMode() == QMdiArea::TabbedView) { m_tileWindows->setVisible(false); m_cascadeWindows->setVisible(false); QTabWidget::TabPosition tabPosition = QTabWidget::TabPosition(group.readEntry("TabPosition", 0)); if (m_mdiArea->tabPosition() != tabPosition) m_mdiArea->setTabPosition(tabPosition); } else { m_tileWindows->setVisible(true); m_cascadeWindows->setVisible(true); } //autosave bool autoSave = group.readEntry("AutoSave", 0); if (m_autoSaveActive != autoSave) { m_autoSaveActive = autoSave; if (autoSave) m_autoSaveTimer.start(); else m_autoSaveTimer.stop(); } int interval = group.readEntry("AutoSaveInterval", 1); interval *= 60*1000; if (interval != m_autoSaveTimer.interval()) m_autoSaveTimer.setInterval(interval); } /***************************************************************************************/ /************************************** dialogs ***************************************/ /***************************************************************************************/ /*! shows the dialog with the Undo-history. */ void MainWin::historyDialog() { if (!m_project->undoStack()) return; auto* dialog = new HistoryDialog(this, m_project->undoStack(), m_undoViewEmptyLabel); int index = m_project->undoStack()->index(); if (dialog->exec() != QDialog::Accepted) { if (m_project->undoStack()->count() != 0) m_project->undoStack()->setIndex(index); } //disable undo/redo-actions if the history was cleared //(in both cases, when accepted or rejected in the dialog) if (m_project->undoStack()->count() == 0) { m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } } /*! Opens the dialog to import data to the selected workbook, spreadsheet or matrix */ void MainWin::importFileDialog(const QString& fileName) { DEBUG("MainWin::importFileDialog()"); auto* dlg = new ImportFileDialog(this, false, fileName); // select existing container if (m_currentAspect->inherits("Spreadsheet") || m_currentAspect->inherits("Matrix") || m_currentAspect->inherits("Workbook")) dlg->setCurrentIndex( m_projectExplorer->currentIndex()); else if (m_currentAspect->inherits("Column")) { if (m_currentAspect->parentAspect()->inherits("Spreadsheet")) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); } if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importFileDialog() DONE"); } void MainWin::importSqlDialog() { DEBUG("MainWin::importSqlDialog()"); auto* dlg = new ImportSQLDatabaseDialog(this); // select existing container if (m_currentAspect->inherits("Spreadsheet") || m_currentAspect->inherits("Matrix") || m_currentAspect->inherits("Workbook")) dlg->setCurrentIndex( m_projectExplorer->currentIndex()); else if (m_currentAspect->inherits("Column")) { if (m_currentAspect->parentAspect()->inherits("Spreadsheet")) dlg->setCurrentIndex( m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); } if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importSqlDialog() DONE"); } void MainWin::importProjectDialog() { DEBUG("MainWin::importProjectDialog()"); ImportProjectDialog::ProjectType type; if (QObject::sender() == m_importOpjAction) type = ImportProjectDialog::ProjectOrigin; else type = ImportProjectDialog::ProjectLabPlot; auto* dlg = new ImportProjectDialog(this, type); // set current folder dlg->setCurrentFolder(m_currentFolder); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importProjectDialog() DONE"); } /*! opens the dialog for the export of the currently active worksheet, spreadsheet or matrix. */ void MainWin::exportDialog() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); if (part->exportView()) statusBar()->showMessage(i18n("%1 exported", part->name())); } void MainWin::editFitsFileDialog() { auto* editDialog = new FITSHeaderEditDialog(this); if (editDialog->exec() == QDialog::Accepted) { if (editDialog->saved()) statusBar()->showMessage(i18n("FITS files saved")); } } /*! adds a new file data source to the current project. */ void MainWin::newLiveDataSourceActionTriggered() { ImportFileDialog* dlg = new ImportFileDialog(this, true); if (dlg->exec() == QDialog::Accepted) { if(static_cast(dlg->sourceType()) == LiveDataSource::MQTT) { #ifdef HAVE_MQTT MQTTClient* mqttClient = new MQTTClient(i18n("MQTT Client%1", 1)); dlg->importToMQTT(mqttClient); mqttClient->setName(mqttClient->clientHostName()); QVector existingClients = m_project->children(AbstractAspect::Recursive); //doesn't make sense to have more MQTTClients connected to the same broker bool found = false; - for(int i = 0; i < existingClients.size(); ++i) { - if(existingClients[i]->clientHostName() == mqttClient->clientHostName()) { + for (const auto* client : existingClients) { + if(client->clientHostName() == mqttClient->clientHostName()) { found = true; break; } } if(!found) this->addAspectToProject(mqttClient); else { delete mqttClient; QMessageBox::warning(this, "Warning", "There already is a MQTTClient with this host name!"); } #endif } else { LiveDataSource* dataSource = new LiveDataSource(i18n("Live data source%1", 1), false); dlg->importToLiveDataSource(dataSource, statusBar()); this->addAspectToProject(dataSource); } } delete dlg; } void MainWin::addAspectToProject(AbstractAspect* aspect) { const QModelIndex& index = m_projectExplorer->currentIndex(); if (index.isValid()) { auto* parent = static_cast(index.internalPointer()); #ifdef HAVE_MQTT //doesn't make sense to add a new MQTTClient to an existing MQTTClient or to any of its successors QString className = parent->metaObject()->className(); MQTTClient* clientAncestor = parent->ancestor(); if(className == "MQTTClient") parent = parent->parentAspect(); else if (clientAncestor != nullptr) parent = clientAncestor->parentAspect(); #endif parent->folder()->addChild(aspect); } else m_project->addChild(aspect); } void MainWin::settingsDialog() { auto* dlg = new SettingsDialog(this); connect (dlg, SIGNAL(settingsChanged()), this, SLOT(handleSettingsChanges())); dlg->exec(); } diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp index bfb7ecd39..a3478b5aa 100644 --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -1,1695 +1,1693 @@ /*************************************************************************** File : LiveDataDock.cpp Project : LabPlot Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 by Kovacs Ferencz (kferike98@gmail.com) Copyright : (C) 2018 by 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 "LiveDataDock.h" #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include #include #include #endif LiveDataDock::LiveDataDock(QWidget* parent) : QWidget(parent), m_paused(false) #ifdef HAVE_MQTT , m_searching(true), - m_searchTimer(new QTimer()), + m_searchTimer(new QTimer(this)), m_interpretMessage(true), m_previousMQTTClient(nullptr) #endif { ui.setupUi(this); ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading); connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow); connect(ui.sbUpdateInterval, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged); connect(ui.sbKeepNValues, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::keepNValuesChanged); connect(ui.sbSampleSize, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::sampleSizeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged); #ifdef HAVE_MQTT m_searchTimer->setInterval(10000); connect(this, &LiveDataDock::newTopic, this, &LiveDataDock::setTopicCompleter); connect(m_searchTimer, &QTimer::timeout, this, &LiveDataDock::topicTimeout); connect(ui.bSubscribe, &QPushButton::clicked, this, &LiveDataDock::addSubscription); connect(ui.bUnsubscribe, &QPushButton::clicked, this, &LiveDataDock::removeSubscription); connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); connect(ui.leTopics, &QLineEdit::textChanged, this, &LiveDataDock::scrollToTopicTreeItem); connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &LiveDataDock::scrollToSubsriptionTreeItem); connect(ui.bWillSettings, &QPushButton::clicked, this, &LiveDataDock::showWillSettings); ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); ui.bSubscribe->setToolTip(i18n("Subscribe selected topics")); ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_ArrowLeft)); ui.bUnsubscribe->setToolTip(i18n("Unsubscribe selected topics")); ui.bWillSettings->setToolTip(i18n("Manage MQTT connection's will settings")); ui.bWillSettings->setIcon(ui.bWillSettings->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif } LiveDataDock::~LiveDataDock() { #ifdef HAVE_MQTT delete m_searchTimer; QMapIterator clients(m_clients); while(clients.hasNext()) { clients.next(); delete clients.value(); } #endif } #ifdef HAVE_MQTT /*! * \brief Sets the MQTTClients of this dock widget * \param clients */ -void LiveDataDock::setMQTTClients(const QList &clients) { +void LiveDataDock::setMQTTClients(const QList& clients) { m_liveDataSources.clear(); m_mqttClients.clear(); m_mqttClients = clients; const MQTTClient* const fmc = clients.at(0); ui.sbUpdateInterval->setValue(fmc->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(fmc->updateType())); ui.cbReadingType->setCurrentIndex(static_cast(fmc->readingType())); if (fmc->updateType() == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fmc->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fmc->keepNValues()); ui.sbKeepNValues->setEnabled(true); if (fmc->readingType() == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else ui.sbSampleSize->setValue(fmc->sampleSize()); // disable "whole file" option const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); //show MQTT connected options ui.gbManageSubscriptions->show(); ui.bSubscribe->show(); ui.bUnsubscribe->show(); ui.twTopics->show(); ui.leTopics->show(); ui.lTopicSearch->show(); ui.twSubscriptions->show(); ui.lQoS->show(); ui.cbQoS->show(); ui.lWillSettings->show(); ui.bWillSettings->show(); //if there isn't a client with this hostname we instantiate a new one if(m_clients[fmc->clientHostName()] == nullptr) { m_clients[fmc->clientHostName()] = new QMqttClient(); connect(fmc, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); connect(m_clients[fmc->clientHostName()], &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); connect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); m_clients[fmc->clientHostName()]->setHostname(fmc->clientHostName()); m_clients[fmc->clientHostName()]->setPort(fmc->clientPort()); if(fmc->MQTTUseAuthentication()) { m_clients[fmc->clientHostName()]->setUsername(fmc->clientUserName()); m_clients[fmc->clientHostName()]->setPassword(fmc->clientPassword()); } if(fmc->MQTTUseID()) { m_clients[fmc->clientHostName()]->setClientId(fmc->clientID()); } m_clients[fmc->clientHostName()]->connectToHost(); } if(m_previousMQTTClient == nullptr) { connect(fmc, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); //Fill the subscription tree(useful if the MQTTClient was loaded) QVector topics = fmc->topicNames(); - for(int i = 0; i < topics.size(); ++i) { - addTopicToTree(topics[i]); + for (const auto& topic : topics) { + addTopicToTree(topic); } fillSubscriptions(); } //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots //and clear the tree widgets else if(m_previousMQTTClient->clientHostName() != fmc->clientHostName()) { disconnect(m_previousMQTTClient, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); ui.twTopics->clear(); //repopulating the tree widget with the already known topics of the client for(int i = 0; i < m_addedTopics[fmc->clientHostName()].size(); ++i) { addTopicToTree(m_addedTopics[fmc->clientHostName()].at(i)); } //fill subscriptions tree widget ui.twSubscriptions->clear(); fillSubscriptions(); connect(fmc, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); connect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); } if(fmc->willUpdateType() == MQTTClient::WillUpdateType::OnClick && fmc->MQTTWillUse()) ui.bWillUpdateNow->show(); m_previousMQTTClient = fmc; } #endif /*! * \brief Sets the live data sources of this dock widget * \param sources */ void LiveDataDock::setLiveDataSources(const QList& sources) { #ifdef HAVE_MQTT m_mqttClients.clear(); #endif m_liveDataSources = sources; const LiveDataSource* const fds = sources.at(0); const LiveDataSource::SourceType sourceType = fds->sourceType(); const LiveDataSource::ReadingType readingType = fds->readingType(); const LiveDataSource::UpdateType updateType = fds->updateType(); ui.sbUpdateInterval->setValue(fds->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(updateType)); ui.cbReadingType->setCurrentIndex(static_cast(readingType)); if (updateType == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fds->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fds->keepNValues()); // disable "whole file" when having no file (i.e. socket or port) auto* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); if (sourceType == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.sbSampleSize->setValue(fds->sampleSize()); } // disable "on new data"-option if not available model = qobject_cast(ui.cbUpdateType->model()); item = model->item(LiveDataSource::UpdateType::NewData); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::NetworkUdpSocket || sourceType == LiveDataSource::SourceType::SerialPort) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); } else { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } ui.bWillSettings->hide(); ui.lWillSettings->hide(); ui.bWillUpdateNow->hide(); ui.bSubscribe->hide(); ui.bUnsubscribe->hide(); ui.twTopics->hide(); ui.leTopics->hide(); ui.lTopicSearch->hide(); ui.twSubscriptions->hide(); ui.gbManageSubscriptions->hide(); ui.lQoS->hide(); ui.cbQoS->hide(); } /*! * \brief Modifies the sample size of the live data sources or MQTTClient objects * \param sampleSize */ void LiveDataDock::sampleSizeChanged(int sampleSize) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setSampleSize(sampleSize); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setSampleSize(sampleSize); } #endif } /*! * \brief Updates the live data sources now */ void LiveDataDock::updateNow() { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->updateNow(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->updateNow(); } #endif } /*! * \brief LiveDataDock::updateTypeChanged * \param idx */ void LiveDataDock::updateTypeChanged(int idx) { if(!m_liveDataSources.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); const auto type = static_cast(idx); switch (type) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); for (auto* source : m_liveDataSources) { source->setUpdateType(type); source->setUpdateInterval(ui.sbUpdateInterval->value()); source->setFileWatched(false); } break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); for (auto* source : m_liveDataSources) { source->setFileWatched(true); source->setUpdateType(type); } } } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); const MQTTClient::UpdateType type = static_cast(idx); if (type == MQTTClient::UpdateType::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); for (auto* client : m_mqttClients) { client->setUpdateType(type); client->setUpdateInterval(ui.sbUpdateInterval->value()); } } else if (type == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); for (auto* client : m_mqttClients) { client->setUpdateType(type); } } } #endif } /*! * \brief Handles the change of the reading type in the dock widget * \param idx */ void LiveDataDock::readingTypeChanged(int idx) { if(!m_liveDataSources.isEmpty()) { const auto type = static_cast(idx); const LiveDataSource* const fds = m_liveDataSources.at(0); const LiveDataSource::SourceType sourceType = fds->sourceType(); const LiveDataSource::UpdateType updateType = fds->updateType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile || updateType == LiveDataSource::UpdateType::NewData) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* source : m_liveDataSources) source->setReadingType(type); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { MQTTClient::ReadingType type = static_cast(idx); if (type == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* client : m_mqttClients) client->setReadingType(type); } #endif } /*! * \brief Modifies the update interval of the live data sources * \param updateInterval */ void LiveDataDock::updateIntervalChanged(int updateInterval) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setUpdateInterval(updateInterval); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setUpdateInterval(updateInterval); } #endif } /*! * \brief Modifies the number of samples to keep in each of the live data sources * \param keepNValues */ void LiveDataDock::keepNValuesChanged(const int keepNValues) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setKeepNValues(keepNValues); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setKeepNValues(keepNValues); } #endif } /*! * \brief Pauses the reading of the live data source */ void LiveDataDock::pauseReading() { if(!m_liveDataSources.isEmpty()) { for (auto* source: m_liveDataSources) source->pauseReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->pauseReading(); } #endif } /*! * \brief Continues the reading of the live data source */ void LiveDataDock::continueReading() { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->continueReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->continueReading(); } #endif } /*! * \brief Handles the pausing/continuing of reading of the live data source */ void LiveDataDock::pauseContinueReading() { m_paused = !m_paused; if (m_paused) { pauseReading(); ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { continueReading(); ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } } #ifdef HAVE_MQTT /*! *\brief called when use will message checkbox's state is changed in the will settings widget, * Sets the mqttUseWill according to state for every client in m_mqttClients * * \param state the state of the checbox */ void LiveDataDock::useWillMessage(int state) { qDebug()<<"Use will message: " <setMQTTWillUse(true); if (m_mqttClients.first()->willUpdateType() == MQTTClient::WillUpdateType::OnClick) ui.bWillUpdateNow->show(); } else if (state == Qt::Unchecked) { for (auto* source : m_mqttClients) source->setMQTTWillUse(false); ui.bWillUpdateNow->hide(); } } /*! *\brief called when will message's QoS is changed in the will settings widget * sets the will QoS level for every client in m_mqttClients * * \param QoS the QoS level of the will message */ void LiveDataDock::willQoSChanged(int QoS) { for (auto* source : m_mqttClients) source->setWillQoS(QoS); } /*! *\brief called when will message's retain flag is changed in the will settings widget * sets the retain flag for the will message in every client in m_mqttClients * * \param state the state of the will retain chechbox */ void LiveDataDock::willRetainChanged(int state) { if(state == Qt::Checked) { for (auto* source : m_mqttClients) source->setWillRetain(true); } else if (state == Qt::Unchecked) { for (auto* source : m_mqttClients) source->setWillRetain(false); } } /*! *\brief called when will topic combobox's current item is changed in the will settings widget * sets the will topic for every client in m_mqttClients * * \param topic the current text of cbWillTopic */ void LiveDataDock::willTopicChanged(const QString& topic) { for (auto* source : m_mqttClients) { if(source->willTopic() != topic) source->clearLastMessage(); source->setWillTopic(topic); } } /*! *\brief called when the selected will message type is changed in the will settings widget * sets the will message type for every client in m_mqttClients * * \param type the selected will message type */ void LiveDataDock::willMessageTypeChanged(int type) { for (auto* source : m_mqttClients) source->setWillMessageType(static_cast (type)); } /*! *\brief called when the will own message is changed in the will settings widget * sets the will own message for every client in m_mqttClients * * \param message the will message given by the user */ void LiveDataDock::willOwnMessageChanged(const QString& message) { for (auto* source : m_mqttClients) source->setWillOwnMessage(message); } /*! *\brief called when the selected update type for the will message is changed in the will settings widget * sets the will update type for every client in m_mqttClients * * \param type the selected will update type */ void LiveDataDock::willUpdateTypeChanged(int updateType) { for (auto* source : m_mqttClients) source->setWillUpdateType(static_cast(updateType)); if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { ui.bWillUpdateNow->hide(); - - for (auto* source : m_mqttClients) { + for (auto* source : m_mqttClients) source->startWillTimer(); - } } else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { ui.bWillUpdateNow->show(); //if update type is on click we stop the will timer for (auto* source : m_mqttClients) source->stopWillTimer(); } } /*! *\brief called when the will update now button is pressed * updates the will message of every client in m_mqttClients */ void LiveDataDock::willUpdateNow() { for (auto* source : m_mqttClients) source->updateWillMessage(); } /*! *\brief called when the update interval for will message is changed in the will settings widget * sets the will update interval for every client in m_mqttClients, then starts the will timer for each one * * \param interval the new will update interval */ void LiveDataDock::willUpdateIntervalChanged(int interval) { for (auto* source : m_mqttClients) { source->setWillTimeInterval(interval); source->startWillTimer(); } } /*! *\brief called when the will statistics are changed in the will settings widget * adds or removes the statistic represented by the index from every client in m_mqttClients */ void LiveDataDock::statisticsChanged(int index) { - bool useStatistic = m_mqttClients.first()->willStatistics()[index]; + const bool useStatistic = m_mqttClients.first()->willStatistics()[index]; //if it's checked we add it if(!useStatistic) { if(index >= 0) { for (auto* source : m_mqttClients) source->addWillStatistics(static_cast(index) ); } } //otherwise remove it else { if(index >= 0){ for (auto* source : m_mqttClients) source->removeWillStatistics(static_cast(index) ); } } } /*! *\brief called when the client connects to the broker successfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void LiveDataDock::onMQTTConnect() { - QMqttTopicFilter globalFilter{"#"}; - QMqttSubscription * subscription = m_clients[m_mqttClients.first()->clientHostName()]->subscribe(globalFilter, 1); + const QMqttTopicFilter globalFilter{"#"}; + QMqttSubscription* subscription { m_clients[m_mqttClients.first()->clientHostName()]->subscribe(globalFilter, 1) }; if(!subscription) qDebug()<<"Couldn't make global subscription in LiveDataDock"; } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) if(!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); addTopicToTree(topic.name()); } } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics in every client from m_mqttClients */ void LiveDataDock::addSubscription() { QString name; - QTreeWidgetItem *item = ui.twTopics->currentItem(); + QTreeWidgetItem* item = ui.twTopics->currentItem(); if(item != nullptr) { - QTreeWidgetItem *tempItem = item; + QTreeWidgetItem* tempItem = item; //determine the topic name that the current item represents name.prepend(item->text(0)); if(item->childCount() != 0) name.append("/#"); while(tempItem->parent() != nullptr) { tempItem = tempItem->parent(); name.prepend(tempItem->text(0) + '/'); } //check if the subscription already exists - QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); + const QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { qDebug() << "LiveDataDock: start to add new subscription: " << name; bool foundSuperior = false; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { //if the new subscriptions contains an already existing one, we remove the inferior one if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); - i--; + --i; continue; } //if there is a subscription containing the new one we set foundSuperior true if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { foundSuperior = true; qDebug()<<"Can't add "<topLevelItem(i)->text(0); break; } } //if there wasn't a superior subscription we can subscribe to the new topic if(!foundSuperior) { QStringList toplevelName; toplevelName.push_back(name); QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); ui.twSubscriptions->addTopLevelItem(newTopLevelItem); if(name.endsWith('#')) { //adding every topic that the subscription contains to twSubscriptions addSubscriptionChildren(item, newTopLevelItem); } //subscribe in every MQTTClient for (auto* source : m_mqttClients) { source->addMQTTSubscription(name, ui.cbQoS->currentIndex()); } if(name.endsWith('#')) { //if an already existing subscription contains a topic that the new subscription also contains //we decompose the already existing subscription //by unsubscribing from its topics, that are present in the new subscription as well - QStringList nameList = name.split('/', QString::SkipEmptyParts); - QString root = nameList.first(); + const QStringList& nameList = name.split('/', QString::SkipEmptyParts); + const QString root = nameList.first(); QVector children; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { children.clear(); //get the "leaf" children of the inspected subscription findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); for(int j = 0; j < children.size(); ++j) { if(checkTopicContains(name, children[j]->text(0))) { //if the new subscription contains a topic, we unsubscribe from it QTreeWidgetItem* unsubscribeItem = children[j]; while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source : m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } //also add it to twSubscriptions ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); - i--; + --i; } else { //before we remove the topic, we reparent it to the new subscription //so no data is lost for (auto* source : m_mqttClients) { source->reparentTopic(unsubscribeItem->text(0), name); } } } unsubscribeItem = unsubscribeItem->parent(); } qDebug()<<"Remove: "<text(0); //Remove topic/subscription for (auto* source : m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } } } } } manageCommonLevelSubscriptions(); updateSubscriptionCompleter(); if(!ui.bWillSettings->isEnabled()) ui.bWillSettings->setEnabled(true); } else { QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); } } else QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the unsubscribe button is pressed * unsubscribes from the topic represented by the current item of twSubscription in every client from m_mqttClients */ void LiveDataDock::removeSubscription() { QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); if(unsubscribeItem != nullptr) { qDebug() << "LiveDataDock: unsubscribe from " << unsubscribeItem->text(0); //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) //we can simply unsubscribe from it if(unsubscribeItem->parent() == nullptr) { for (auto* source : m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } //otherwise we remove the selected item, but subscribe to every other topic, that was contained by //the selected item's parent subscription(top level item of twSubscriptions) else{ while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source : m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); - i--; + --i; } } unsubscribeItem = unsubscribeItem->parent(); } //remove topic/subscription from every client for (auto* source : m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); //check if any common topics were subscribed, if possible merge them manageCommonLevelSubscriptions(); } if(ui.twSubscriptions->topLevelItemCount() <= 0) ui.bWillSettings->setEnabled(false); updateSubscriptionCompleter(); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when a new topic is added to the tree(twTopics) * appends the topic's root to the topicList if it isn't in the list already * then sets the completer for leTopics */ void LiveDataDock::setTopicCompleter(const QString& topicName) { if(!m_searching) { - QStringList list = topicName.split('/', QString::SkipEmptyParts); + const QStringList& list = topicName.split('/', QString::SkipEmptyParts); QString topic; if(!list.isEmpty()) { topic = list.at(0); } else topic = topicName; if(!m_topicList[m_mqttClients.first()->clientHostName()].contains(topic)) { m_topicList[m_mqttClients.first()->clientHostName()].append(topic); m_topicCompleter = new QCompleter(m_topicList[m_mqttClients.first()->clientHostName()], this); m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leTopics->setCompleter(m_topicCompleter); } } } /*! *\brief Updates the completer for leSubscriptions */ void LiveDataDock::updateSubscriptionCompleter() { QStringList subscriptionList; - QVector subscriptions = m_mqttClients.first()->MQTTSubscriptions(); + const QVector& subscriptions = m_mqttClients.first()->MQTTSubscriptions(); if(!subscriptions.isEmpty()) { - for(int i = 0; i < subscriptions.size(); ++i) { - subscriptionList.append(subscriptions[i]); + for (const auto& subscription : subscriptions) { + subscriptionList << subscription; } m_subscriptionCompleter = new QCompleter(subscriptionList, this); m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSubscriptions->setCompleter(m_subscriptionCompleter); } else { ui.leSubscriptions->setCompleter(nullptr); } } /*! *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics * enables updating the completer for le */ void LiveDataDock::topicTimeout() { m_searching = false; m_searchTimer->stop(); } /*! *\brief called when a new the host name of the MQTTClients from m _mqttClients changes * or when the MQTTClients initialize their subscriptions * Fills twSubscriptions with the subscriptions of the MQTTClient */ void LiveDataDock::fillSubscriptions() { const MQTTClient* const fmc = m_mqttClients.at(0); ui.twSubscriptions->clear(); QVector subscriptions = fmc->MQTTSubscriptions(); for (int i = 0; i < subscriptions.count(); ++i) { QStringList name; name.append(subscriptions[i]); bool found = false; for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { if(ui.twSubscriptions->topLevelItem(j)->text(0) == subscriptions[i]) { found = true; break; } } if(!found) { //Add the subscription to the tree widget QTreeWidgetItem* newItem = new QTreeWidgetItem(name); ui.twSubscriptions->addTopLevelItem(newItem); name.clear(); name = subscriptions[i].split('/', QString::SkipEmptyParts); //find the corresponding "root" item in twTopics QTreeWidgetItem* topic = nullptr; for(int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { if(ui.twTopics->topLevelItem(j)->text(0) == name[0]) { topic = ui.twTopics->topLevelItem(j); break; } } //restore the children of the subscription if(topic != nullptr && topic->childCount() > 0) { restoreSubscriptionChildren(topic, newItem, name, 1); } } } m_searching = false; } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ -bool LiveDataDock::checkTopicContains(const QString &superior, const QString &inferior) { +bool LiveDataDock::checkTopicContains(const QString& superior, const QString& inferior) { if (superior == inferior) return true; else { if(superior.contains('/')) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one if(superiorList.size() > inferiorList.size()) return false; bool ok = true; for(int i = 0; i < superiorList.size(); ++i) { if(superiorList.at(i) != inferiorList.at(i)) { if((superiorList.at(i) != "+") && !(superiorList.at(i) == '#' && i == superiorList.size() - 1)) { //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) //then superior can't contain inferior ok = false; break; } else if(i == superiorList.size() - 1 && (superiorList.at(i) == "+" && inferiorList.at(i) == '#') ) { //if the two topics differ at the last level //and the superior's current level is + while the inferior's is #(which can be only in the last position) //then superior can't contain inferior ok = false; break; } } } + return ok; } return false; } } /*! *\brief called when leTopics' text is changed * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget * * \param rootName the current text of leTopics */ void LiveDataDock::scrollToTopicTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; for(int i = 0; i< ui.twTopics->topLevelItemCount(); ++i) if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when leSubscriptions' text is changed * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget * * \param rootName the current text of leSubscriptions */ void LiveDataDock::scrollToSubsriptionTreeItem(const QString& rootName) { int topItemIdx = -1; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) if(ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief Returns the "+" wildcard containing topic name, which includes the given topic names * * \param first the name of a topic * \param second the name of a topic * \return The name of the common topic, if it exists, otherwise "" */ QString LiveDataDock::checkCommonLevel(const QString& first, const QString& second) { - QStringList firstList = first.split('/', QString::SkipEmptyParts); - QStringList secondtList = second.split('/', QString::SkipEmptyParts); + const QStringList firstList = first.split('/', QString::SkipEmptyParts); + const QStringList secondtList = second.split('/', QString::SkipEmptyParts); QString commonTopic = ""; if(!firstList.isEmpty()) { //the two topics have to be the same size and can't be identic if(firstList.size() == secondtList.size() && (first != second)) { //the index where they differ int differIndex = -1; for(int i = 0; i < firstList.size(); ++i) { if(firstList.at(i) != secondtList.at(i)) { differIndex = i; break; } } //they can differ at only one level bool differ = false; - if(differIndex > 0 && differIndex < firstList.size() -1) { + if(differIndex > 0 && differIndex < firstList.size() - 1) { for(int j = differIndex +1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put "+" wildcard at the level where they differ commonTopic.append('+'); } if(i != firstList.size() - 1) commonTopic.append('/'); } } } } qDebug() << "Common topic for " << first << " and "<childCount() > 0) { for(int i = 0; i < topic->childCount(); ++i) { QTreeWidgetItem* temp = topic->child(i); QString name; //if it has children, then we add it as a # wildcrad containing topic if(topic->child(i)->childCount() > 0) { - name.append(temp->text(0) + "/#"); + name.append(temp->text(0) + QLatin1String("/#")); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + '/'); } } //if not then we simply add the topic itself else { name.append(temp->text(0)); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + '/'); } } - QStringList nameList; - nameList.append(name); + QStringList nameList { QStringList() << name}; QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); subscription->addChild(childItem); //we use the function recursively on the given item addSubscriptionChildren(topic->child(i), childItem); } } } /*! *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards * * \param topic pointer to a top level item in twTopics which represents the root of the subscription topic * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored * \param list QStringList containing the levels of the subscription topic * \param level the level's number which is being investigated */ void LiveDataDock::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { if(level < list.size()) { if((level < list.size() - 1) && (list[level] != "+") && (list[level] != '#')) { for(int i = 0; i < topic->childCount(); ++i) { //if the current level isn't + or # wildcard we recursively continue with the next level if(topic->child(i)->text(0) == list[level]) { restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); break; } } } else if (list[level] == "+") { for(int i = 0; i < topic->childCount(); ++i) { //determine the name of the topic, contained by the subscription QString name; name.append(topic->child(i)->text(0)); for(int j = level + 1; j < list.size(); ++j) { name.append('/' + list[j]); } QTreeWidgetItem* temp = topic->child(i); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + '/'); } //Add the topic as child of the subscription QStringList nameList; nameList.append(name); QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); subscription->addChild(newItem); //Continue adding children recursively to the new item restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); } } else if (list[level] == '#') { //add the children of the # wildcard containing subscription addSubscriptionChildren(topic, subscription); } } } /*! *\brief Returns the index of level where the two topic names differ, if there is a common topic for them * * \param first the name of a topic * \param second the name of a topic * \return The index of the unequal level, if there is a common topic, otherwise -1 */ int LiveDataDock::commonLevelIndex(const QString& first, const QString& second) { QStringList firstList = first.split('/', QString::SkipEmptyParts); QStringList secondtList = second.split('/', QString::SkipEmptyParts); QString commonTopic = ""; int differIndex = -1; if(!firstList.isEmpty()) { //the two topics have to be the same size and can't be identic if(firstList.size() == secondtList.size() && (first != second)) { //the index where they differ for(int i = 0; i < firstList.size(); ++i) { if(firstList.at(i) != secondtList.at(i)) { differIndex = i; break; } } //they can differ at only one level bool differ = false; if(differIndex > 0) { for(int j = differIndex +1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) commonTopic.append(firstList.at(i)); else commonTopic.append('+'); if(i != firstList.size() - 1) commonTopic.append('/'); } } } } //if there is a common topic we return the differIndex if(!commonTopic.isEmpty()) return differIndex; else return -1; } /*! *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) * * \param children vector of TreeWidgetItem pointers * \param root pointer to a TreeWidgetItem of twSubscriptions */ void LiveDataDock::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { if(root->childCount() == 0) { children.push_back(root); } else { for(int i = 0; i < root->childCount(); ++i) { findSubscriptionLeafChildren(children, root->child(i)); } } } /*! *\brief Returns the amount of topics that the "+" wildcard will replace in the level position * * \param levelIdx the level currently being investigated * \param level the level where the new + wildcard will be placed * \param commonList the topic name split into levels * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level * represented by levelIdx * \return returns the childCount, or -1 if some topics already represented by + wildcard have different * amount of children */ int LiveDataDock::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { //we recursively check the number of children, until we get to level-1 if(levelIdx < level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children, recursively for(int j = 0; j < currentItem->childCount(); ++j) { int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); if((j > 0) && (temp != childCount)) { ok = false; break; } childCount = temp; } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (levelIdx == level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item return currentItem->child(j)->childCount(); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children for(int j = 0; j < currentItem->childCount(); ++j) { if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { ok = false; break; } childCount = currentItem->child(j)->childCount(); } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (level == 1 && levelIdx == 1) return currentItem->childCount(); return -1; } /*! *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. * We do this until there are no topics to merge */ void LiveDataDock::manageCommonLevelSubscriptions() { bool foundEqual = false; do{ foundEqual = false; QMap> equalTopicsMap; QVector equalTopics; //compare the subscriptions present in the TreeWidget for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) if(!commonTopic.isEmpty()) { if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); } if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); } } } } if(!equalTopicsMap.isEmpty()) { qDebug()<<"Manage equal topics"; QVector commonTopics; QMapIterator> topics(equalTopicsMap); //check for every map entry, if the found topics can be merged or not while(topics.hasNext()) { topics.next(); int level = commonLevelIndex(topics.value().last(), topics.value().first()); QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; //search the corresponding item to the common topics first level(root) for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { currentItem = ui.twTopics->topLevelItem(i); break; } } //calculate the number of topics the new + wildcard could replace int childCount = checkCommonChildCount(1, level, commonList, currentItem); if(childCount > 0) { //if the number of topics found and the calculated number of topics is equal, the topics can be merged if(topics.value().size() == childCount) { qDebug() << "Found common topic to manage: " << topics.key(); foundEqual = true; commonTopics.push_back(topics.key()); } } } if(foundEqual) { //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new "+" wildcard int highestLevel = INT_MAX; int topicIdx = -1; for(int i = 0; i < commonTopics.size(); ++i) { int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); if(level < highestLevel) { topicIdx = i; highestLevel = level; } } qDebug() << "Start to manage: " << commonTopics[topicIdx]; equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); //Add the common topic ("merging") QString commonTopic; commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); QStringList nameList; nameList.append(commonTopic); QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); ui.twSubscriptions->addTopLevelItem(newTopic); //remove the "merged" topics for(int i = 0; i < equalTopics.size(); ++i) { for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); break; } } } //remove any subscription that the new subscription contains for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); i--; } } //make the subscription on commonTopic in every MQTTClient from m_mqttClients for (auto* source : m_mqttClients) { source->addMQTTSubscription(commonTopic, ui.cbQoS->currentIndex()); } } } } while(foundEqual); } /*! *\brief Adds topicName to twTopics * * \param topicName the name of the topic, which will be added to the tree widget */ void LiveDataDock::addTopicToTree(const QString &topicName) { QStringList name; QChar sep = '/'; QString rootName; if(topicName.contains(sep)) { QStringList list = topicName.split(sep, QString::SkipEmptyParts); if(!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); QTreeWidgetItem* currentItem; //check whether the first level of the topic can be found in twTopics int topItemIdx = -1; for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if( topItemIdx < 0) { currentItem = new QTreeWidgetItem(name); ui.twTopics->addTopLevelItem(currentItem); for(int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { currentItem = ui.twTopics->topLevelItem(topItemIdx); int listIdx = 1; for(; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for(int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if(childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if(!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for(; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topicName; name.append(topicName); ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); if (rootName == subscriptionName[0]) { fillSubscriptions(); break; } } //signals that a newTopic was added, in order to fill the completer of leTopics //we have to pass the whole topic name, not just the root name, for testing purposes emit newTopic(topicName); } /*! *\brief called when a client receives a message, if the clients hostname isn't identic with the host name of MQTTClients * if the message arrived from a new topic, the topic is put in m_addedTopics */ void LiveDataDock::mqttMessageReceivedInBackground(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) if(!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); } } /*! *\brief called when an MQTTClient is about to be deleted * removes every data connected to the MQTTClient, and disconnects the corresponding client from m_clients * * \param name the host name of the MQTTClient that will be deleted */ void LiveDataDock::removeClient(const QString& name) { m_clients[name]->disconnectFromHost(); m_addedTopics.remove(name); m_topicList.remove(name); if(m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == name) { disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); m_previousMQTTClient = nullptr; } if(m_mqttClients.first()->clientHostName() == name) { ui.twSubscriptions->clear(); ui.twTopics->clear(); m_mqttClients.clear(); } delete m_clients[name]; m_clients.remove(name); } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testSubscribe(const QString& topic){ QStringList topicList = topic.split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; for(int i = 0; i topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == topicList[0]) { currentItem = ui.twTopics->topLevelItem(i); break; } } if (currentItem) { for(int i = 1 ; i < topicList.size(); ++i) { if(topicList[i] == '#') break; for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == topicList[i]) { currentItem = currentItem->child(j); break; } else if (j == currentItem->childCount() - 1) return false; } } } else return false; ui.twTopics->setCurrentItem(currentItem); addSubscription(); return true; } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testUnsubscribe(const QString& topic) { QTreeWidgetItem* currentItem = nullptr; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), topic)) { currentItem = ui.twSubscriptions->topLevelItem(i); break; } } if(currentItem) { do { if(topic == currentItem->text(0)) { ui.twSubscriptions->setCurrentItem(currentItem); removeSubscription(); return true; } else { for(int i = 0; i < currentItem->childCount(); ++i) { qDebug()<child(i)->text(0)<<" "<child(i)->text(0), topic)) { currentItem = currentItem->child(i); break; } else if (i == currentItem->childCount() - 1) return false; } } } while(currentItem); } else return false; return false; } void LiveDataDock::showWillSettings() { QMenu menu; const MQTTClient* const fmc = m_mqttClients.at(0); QVector topics = fmc->topicNames(); MQTTWillSettingsWidget willSettings(&menu, fmc->willSettings(), topics); connect(&willSettings, &MQTTWillSettingsWidget::useChanged, this, &LiveDataDock::useWillMessage); connect(&willSettings, &MQTTWillSettingsWidget::messageTypeChanged, this, &LiveDataDock::willMessageTypeChanged); connect(&willSettings, &MQTTWillSettingsWidget::updateTypeChanged, this, &LiveDataDock::willUpdateTypeChanged); connect(&willSettings, &MQTTWillSettingsWidget::retainChanged, this, &LiveDataDock::willRetainChanged); connect(&willSettings, &MQTTWillSettingsWidget::intervalChanged, this, &LiveDataDock::willUpdateIntervalChanged); connect(&willSettings, &MQTTWillSettingsWidget::ownMessageChanged, this, &LiveDataDock::willOwnMessageChanged); connect(&willSettings, &MQTTWillSettingsWidget::topicChanged, this, &LiveDataDock::willTopicChanged); connect(&willSettings, &MQTTWillSettingsWidget::statisticsChanged, this, &LiveDataDock::statisticsChanged); connect(&willSettings, &MQTTWillSettingsWidget::QoSChanged, this, &LiveDataDock::willQoSChanged); connect(&willSettings, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettings); menu.addAction(widgetAction); QPoint pos(ui.bWillSettings->sizeHint().width(),ui.bWillSettings->sizeHint().height()); menu.exec(ui.bWillSettings->mapToGlobal(pos)); } #endif