diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp index d138ac3c7..9d2b9be37 100644 --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -1,407 +1,439 @@ /*************************************************************************** File : ImportDialog.cc Project : LabPlot Description : import file data dialog -------------------------------------------------------------------- Copyright : (C) 2008-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2015 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 "ImportFileDialog.h" #include "ImportFileWidget.h" #include "backend/core/AspectTreeModel.h" #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/datasources/filters/HDFFilter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/core/Workbook.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/MainWin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportFileDialog \brief Dialog for importing data from a file. Embeds \c ImportFileWidget and provides the standard buttons. \ingroup kdefrontend */ ImportFileDialog::ImportFileDialog(MainWin* parent, bool liveDataSource, const QString& fileName) : ImportDialog(parent), m_importFileWidget(new ImportFileWidget(this, fileName)), m_showOptions(false) { vLayout->addWidget(m_importFileWidget); //dialog buttons QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Reset |QDialogButtonBox::Cancel); okButton = buttonBox->button(QDialogButtonBox::Ok); m_optionsButton = buttonBox->button(QDialogButtonBox::Reset); //we highjack the default "Reset" button and use if for showing/hiding the options okButton->setEnabled(false); //ok is only available if a valid container was selected vLayout->addWidget(buttonBox); //hide the data-source related widgets if (!liveDataSource) { setModel(); //TODO: disable for file data sources m_importFileWidget->hideDataSource(); } else m_importFileWidget->initializeAndFillPortsAndBaudRates(); //Signals/Slots connect(m_importFileWidget, SIGNAL(checkedFitsTableToMatrix(bool)), this, SLOT(checkOnFitsTableToMatrix(bool))); connect(m_importFileWidget, SIGNAL(fileNameChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(sourceTypeChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(hostChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(portChanged()), this, SLOT(checkOkButton())); connect(m_optionsButton, SIGNAL(clicked()), this, SLOT(toggleOptions())); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); if (!liveDataSource) { setWindowTitle(i18n("Import Data to Spreadsheet or Matrix")); m_importFileWidget->hideDataSource(); } else setWindowTitle(i18n("Add new live data source")); setWindowIcon(QIcon::fromTheme("document-import-database")); QTimer::singleShot(0, this, &ImportFileDialog::loadSettings); } void ImportFileDialog::loadSettings() { //restore saved settings QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); m_showOptions = conf.readEntry("ShowOptions", false); m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); m_importFileWidget->showOptions(m_showOptions); KWindowConfig::restoreWindowSize(windowHandle(), conf); } ImportFileDialog::~ImportFileDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); conf.writeEntry("ShowOptions", m_showOptions); if (cbPosition) conf.writeEntry("Position", cbPosition->currentIndex()); KWindowConfig::saveWindowSize(windowHandle(), conf); } /*! triggers data import to the live data source \c source */ void ImportFileDialog::importToLiveDataSource(LiveDataSource* source, QStatusBar* statusBar) const { m_importFileWidget->saveSettings(source); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(source->filter(), SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QTime timer; timer.start(); source->read(); statusBar->showMessage( i18n("Live data source created in %1 seconds.", (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); source->ready(); } /*! triggers data import to the currently selected data container */ void ImportFileDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importTo()"); QDEBUG("cbAddTo->currentModelIndex() =" << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR in importTo(): No aspect available"); DEBUG("cbAddTo->currentModelIndex().isValid() = " << cbAddTo->currentModelIndex().isValid()); DEBUG("cbAddTo->currentModelIndex() row/column = " << cbAddTo->currentModelIndex().row() << ' ' << cbAddTo->currentModelIndex().column()); return; } if (m_importFileWidget->isFileEmpty()) { KMessageBox::information(0, i18n("No data to import."), i18n("No Data")); return; } QString fileName = m_importFileWidget->fileName(); AbstractFileFilter* filter = m_importFileWidget->currentFileFilter(); AbstractFileFilter::ImportMode mode = AbstractFileFilter::ImportMode(cbPosition->currentIndex()); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(filter, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 100); QTime timer; timer.start(); if (aspect->inherits("Matrix")) { Matrix* matrix = qobject_cast(aspect); filter->readDataFromFile(fileName, matrix, mode); } else if (aspect->inherits("Spreadsheet")) { Spreadsheet* spreadsheet = qobject_cast(aspect); filter->readDataFromFile(fileName, spreadsheet, mode); } else if (aspect->inherits("Workbook")) { Workbook* workbook = qobject_cast(aspect); QVector sheets = workbook->children(); QStringList names; LiveDataSource::FileType fileType = m_importFileWidget->currentFileType(); if (fileType == LiveDataSource::HDF) names = m_importFileWidget->selectedHDFNames(); else if (fileType == LiveDataSource::NETCDF) names = m_importFileWidget->selectedNetCDFNames(); //multiple extensions selected // multiple data sets/variables for HDF/NetCDF if (fileType == LiveDataSource::HDF || fileType == LiveDataSource::NETCDF) { int nrNames = names.size(), offset = sheets.size(); int start=0; if (mode == AbstractFileFilter::Replace) start=offset; // add additional sheets for (int i = start; i < nrNames; ++i) { Spreadsheet *spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); if (mode == AbstractFileFilter::Prepend) workbook->insertChildBefore(spreadsheet,sheets[0]); else workbook->addChild(spreadsheet); } if (mode != AbstractFileFilter::Append) offset = 0; // import to sheets sheets = workbook->children(); for (int i = 0; i < nrNames; ++i) { if (fileType == LiveDataSource::HDF) ((HDFFilter*) filter)->setCurrentDataSetName(names[i]); else ((NetCDFFilter*) filter)->setCurrentVarName(names[i]); if (sheets[i+offset]->inherits("Matrix")) filter->readDataFromFile(fileName, qobject_cast(sheets[i+offset])); else if (sheets[i+offset]->inherits("Spreadsheet")) filter->readDataFromFile(fileName, qobject_cast(sheets[i+offset])); } } else { // single import file types // use active spreadsheet/matrix if present, else new spreadsheet Spreadsheet* spreadsheet = workbook->currentSpreadsheet(); Matrix* matrix = workbook->currentMatrix(); if (spreadsheet) filter->readDataFromFile(fileName, spreadsheet, mode); else if (matrix) filter->readDataFromFile(fileName, matrix, mode); else { spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); workbook->addChild(spreadsheet); filter->readDataFromFile(fileName, spreadsheet, mode); } } } statusBar->showMessage( i18n("File %1 imported in %2 seconds.", fileName, (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); delete filter; } void ImportFileDialog::toggleOptions() { m_importFileWidget->showOptions(!m_showOptions); m_showOptions = !m_showOptions; m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); //resize the dialog layout()->activate(); resize( QSize(this->width(), 0).expandedTo(minimumSize()) ); } void ImportFileDialog::checkOnFitsTableToMatrix(const bool enable) { if (cbAddTo) { QDEBUG("cbAddTo->currentModelIndex() = " << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR: no aspect available."); return; } if(aspect->inherits("Matrix")) okButton->setEnabled(enable); } } void ImportFileDialog::checkOkButton() { DEBUG("ImportFileDialog::checkOkButton()"); if (cbAddTo) { //only check for the target container when no file data source is being added QDEBUG(" cbAddTo->currentModelIndex() = " << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { okButton->setEnabled(false); + okButton->setToolTip(i18n("Select a data container where the data has to be imported into.")); lPosition->setEnabled(false); cbPosition->setEnabled(false); - DEBUG("WARNING: no aspect available."); return; } else { - DEBUG("Aspect available."); lPosition->setEnabled(true); cbPosition->setEnabled(true); //when doing ASCII import to a matrix, hide the options for using the file header (first line) //to name the columns since the column names are fixed in a matrix const Matrix* matrix = dynamic_cast(aspect); m_importFileWidget->showAsciiHeaderOptions(matrix == NULL); } } QString fileName = m_importFileWidget->fileName(); #ifndef HAVE_WINDOWS if (!fileName.isEmpty() && fileName.left(1) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif DEBUG(" fileName = " << fileName.toUtf8().constData()); - bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); - switch (m_importFileWidget->currentSourceType()) { - case LiveDataSource::SourceType::FileOrPipe: - okButton->setEnabled( QFile::exists(fileName) ); - break; - case LiveDataSource::SourceType::LocalSocket: + case LiveDataSource::SourceType::FileOrPipe: { + const bool enable = QFile::exists(fileName); + okButton->setEnabled(enable); + if (enable) + okButton->setToolTip(i18n("Close the dialog and import the data.")); + else + okButton->setToolTip(i18n("Provide an existing file.")); - if (QFile::exists(fileName)) { + break; + } + case LiveDataSource::SourceType::LocalSocket: { + const bool enable = QFile::exists(fileName); + if (enable) { QLocalSocket* socket = new QLocalSocket(this); socket->connectToServer(fileName, QLocalSocket::ReadOnly); bool localSocketConnected = socket->waitForConnected(2000); okButton->setEnabled(localSocketConnected); + if (localSocketConnected) + okButton->setToolTip(i18n("Close the dialog and import the data.")); + else + okButton->setToolTip(i18n("Couldn't connect to the provided local socket.")); if (socket->state() == QLocalSocket::ConnectedState) { socket->disconnectFromServer(); socket->waitForDisconnected(1000); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); } else delete socket; - } else + } else { okButton->setEnabled(false); + okButton->setToolTip(i18n("Selected local socket doesn't exist.")); + } break; - case LiveDataSource::SourceType::NetworkTcpSocket: + } + case LiveDataSource::SourceType::NetworkTcpSocket: { + const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QTcpSocket* socket = new QTcpSocket(this); socket = new QTcpSocket(this); socket->connectToHost(m_importFileWidget->host(), m_importFileWidget->port().toInt(), QTcpSocket::ReadOnly); bool tcpSocketConnected = socket->waitForConnected(2000); okButton->setEnabled(tcpSocketConnected); + if (tcpSocketConnected) + okButton->setToolTip(i18n("Close the dialog and import the data.")); + else + okButton->setToolTip(i18n("Couldn't connect to the provided TCP socket.")); if (socket->state() == QTcpSocket::ConnectedState) { socket->disconnectFromHost(); socket->waitForDisconnected(1000); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); } else delete socket; - } else + } else { okButton->setEnabled(false); + okButton->setToolTip(i18n("Either the host name or the port number is missing.")); + } break; - case LiveDataSource::SourceType::NetworkUdpSocket: + } + case LiveDataSource::SourceType::NetworkUdpSocket: { + const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QUdpSocket* socket = new QUdpSocket(this); socket->connectToHost(m_importFileWidget->host(), m_importFileWidget->port().toInt(), QUdpSocket::ReadOnly); bool udpSocketConnected = socket->waitForConnected(2000); okButton->setEnabled(udpSocketConnected); + if (udpSocketConnected) + okButton->setToolTip(i18n("Close the dialog and import the data.")); + else + okButton->setToolTip(i18n("Couldn't connect to the provided UDP socket.")); if (socket->state() == QUdpSocket::ConnectedState) { socket->disconnectFromHost(); socket->waitForDisconnected(1000); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); } else delete socket; - } else + } else { okButton->setEnabled(false); + okButton->setToolTip(i18n("Either the host name or the port number is missing.")); + } break; - case LiveDataSource::SourceType::SerialPort: - if (!m_importFileWidget->serialPort().isEmpty()) { + } + case LiveDataSource::SourceType::SerialPort: { + const bool enable = !m_importFileWidget->serialPort().isEmpty(); + if (enable) { QSerialPort* serialPort = new QSerialPort(this); serialPort->setBaudRate(m_importFileWidget->baudRate()); serialPort->setPortName(m_importFileWidget->serialPort()); bool serialPortOpened = serialPort->open(QIODevice::ReadOnly); okButton->setEnabled(serialPortOpened); - } else + if (serialPortOpened) + okButton->setToolTip(i18n("Close the dialog and import the data.")); + else + okButton->setToolTip(i18n("Couldn't connect to the provided UDP socket.")); + } else { okButton->setEnabled(false); - break; - default: - break; + okButton->setToolTip(i18n("Serial port number is missing.")); + } + } } } QString ImportFileDialog::selectedObject() const { QString path = m_importFileWidget->fileName(); //determine the file name only QString name = path.right( path.length()-path.lastIndexOf(QDir::separator())-1 ); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); return name; } diff --git a/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp b/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp index 5b728c1c5..fe538c364 100644 --- a/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp +++ b/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp @@ -1,171 +1,175 @@ /*************************************************************************** File : ImportSQLDatabaseDialog.cpp Project : LabPlot Description : import SQL dataase dialog -------------------------------------------------------------------- Copyright : (C) 2016 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportSQLDatabaseDialog.h" #include "ImportSQLDatabaseWidget.h" #include "backend/core/AspectTreeModel.h" #include "backend/lib/macros.h" #include "kdefrontend/MainWin.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/core/Workbook.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include #include #include /*! \class ImportSQLDatabaseDialog \brief Dialog for importing data from a SQL database. Embeds \c ImportSQLDatabaseWidget and provides the standard buttons. \ingroup kdefrontend */ ImportSQLDatabaseDialog::ImportSQLDatabaseDialog(MainWin* parent) : ImportDialog(parent), importSQLDatabaseWidget(new ImportSQLDatabaseWidget(this)) { vLayout->addWidget(importSQLDatabaseWidget); setWindowTitle(i18n("Import Data to Spreadsheet or Matrix")); setWindowIcon(QIcon::fromTheme("document-import-database")); setModel(); //dialog buttons QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(false); //ok is only available if a valid container was selected vLayout->addWidget(buttonBox); //Signals/Slots connect(importSQLDatabaseWidget, SIGNAL(stateChanged()), this, SLOT(checkOkButton())); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QTimer::singleShot(0, this, &ImportSQLDatabaseDialog::loadSettings); } void ImportSQLDatabaseDialog::loadSettings() { //restore saved settings QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "ImportSQLDatabaseDialog"); KWindowConfig::restoreWindowSize(windowHandle(), conf); } ImportSQLDatabaseDialog::~ImportSQLDatabaseDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportSQLDatabaseDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); } void ImportSQLDatabaseDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportSQLDatabaseDialog::import()"); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR: No aspect available!"); return; } AbstractFileFilter::ImportMode mode = AbstractFileFilter::ImportMode(cbPosition->currentIndex()); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setMinimum(0); progressBar->setMaximum(100); connect(importSQLDatabaseWidget, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 100); QTime timer; timer.start(); if (aspect->inherits("Matrix")) { Matrix* matrix = qobject_cast(aspect); importSQLDatabaseWidget->read(matrix, mode); } else if (aspect->inherits("Spreadsheet")) { Spreadsheet* spreadsheet = qobject_cast(aspect); importSQLDatabaseWidget->read(spreadsheet, mode); } else if (aspect->inherits("Workbook")) { // use active spreadsheet or matrix (only if numeric data is going to be improted) if present, // create a new spreadsheet in the selected workbook otherwise Workbook* workbook = qobject_cast(aspect); Spreadsheet* spreadsheet = workbook->currentSpreadsheet(); Matrix* matrix = workbook->currentMatrix(); if (spreadsheet) importSQLDatabaseWidget->read(spreadsheet, mode); else if (matrix && importSQLDatabaseWidget->isNumericData()) importSQLDatabaseWidget->read(matrix, mode); else { spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); workbook->addChild(spreadsheet); importSQLDatabaseWidget->read(spreadsheet, mode); } } statusBar->showMessage( i18n("Data imported in %1 seconds.", (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); } QString ImportSQLDatabaseDialog::selectedObject() const { return importSQLDatabaseWidget->selectedTable(); } void ImportSQLDatabaseDialog::checkOkButton() { DEBUG("ImportSQLDatabaseDialog::checkOkButton()"); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { okButton->setEnabled(false); + okButton->setToolTip(i18n("Select a data container where the data has to be imported into.")); cbPosition->setEnabled(false); return; } //check whether a valid connection and an object to import were selected if (!importSQLDatabaseWidget->isValid()) { okButton->setEnabled(false); + okButton->setToolTip(i18n("Select a valid database object (table or query result set) that has to be imported.")); cbPosition->setEnabled(false); return; } //for matrix containers allow to import only numerical data if (dynamic_cast(aspect) && !importSQLDatabaseWidget->isNumericData()) { okButton->setEnabled(false); + okButton->setToolTip(i18n("Cannot import into a matrix since the data contains non-numerical data.")); cbPosition->setEnabled(false); return; } okButton->setEnabled(true); + okButton->setToolTip(i18n("Close the dialog and import the data.")); cbPosition->setEnabled(true); } diff --git a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp index 942154c88..5324d27c4 100644 --- a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp +++ b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp @@ -1,551 +1,560 @@ /*************************************************************************** File : PlotDataDialog.cpp Project : LabPlot Description : Dialog for generating plots for the spreadsheet data -------------------------------------------------------------------- Copyright : (C) 2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "PlotDataDialog.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/core/column/Column.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYDataReductionCurve.h" #include "backend/worksheet/plots/cartesian/XYDifferentiationCurve.h" #include "backend/worksheet/plots/cartesian/XYIntegrationCurve.h" #include "backend/worksheet/plots/cartesian/XYInterpolationCurve.h" #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h" #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "backend/worksheet/plots/cartesian/XYFourierFilterCurve.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/TextLabel.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include #include #include #include #include "ui_plotdatawidget.h" /*! \class PlotDataDialog \brief Dialog for generating plots for the spreadsheet data. \ingroup kdefrontend */ PlotDataDialog::PlotDataDialog(Spreadsheet* s, QWidget* parent, Qt::WFlags fl) : QDialog(parent, fl), ui(new Ui::PlotDataWidget()), m_spreadsheet(s), m_plotsModel(new AspectTreeModel(m_spreadsheet->project())), m_worksheetsModel(new AspectTreeModel(m_spreadsheet->project())), m_analysisAction(Differentiation), m_analysisMode(false) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(i18n("Plot spreadsheet data")); setWindowIcon(QIcon::fromTheme("office-chart-line")); QWidget* mainWidget = new QWidget(this); ui->setupUi(mainWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_okButton = buttonBox->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); m_okButton->setToolTip(i18n("Plot the selected data")); m_okButton->setText(i18n("&Plot")); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(mainWidget); layout->addWidget(buttonBox); setLayout(layout); //create combox boxes for the existing plots and worksheets QGridLayout* gridLayout = dynamic_cast(ui->gbPlotPlacement->layout()); cbExistingPlots = new TreeViewComboBox(ui->gbPlotPlacement); cbExistingPlots->setMinimumWidth(250);//TODO: use proper sizeHint in TreeViewComboBox gridLayout->addWidget(cbExistingPlots, 0, 1, 1, 1); cbExistingWorksheets = new TreeViewComboBox(ui->gbPlotPlacement); cbExistingWorksheets->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); gridLayout->addWidget(cbExistingWorksheets, 1, 1, 1, 1); QList list; list<<"Folder"<<"Worksheet"<<"CartesianPlot"; cbExistingPlots->setTopLevelClasses(list); list.clear(); list<<"CartesianPlot"; m_plotsModel->setSelectableAspects(list); cbExistingPlots->setModel(m_plotsModel); list.clear(); list<<"Folder"<<"Worksheet"; cbExistingWorksheets->setTopLevelClasses(list); list.clear(); list<<"Worksheet"; m_worksheetsModel->setSelectableAspects(list); cbExistingWorksheets->setModel(m_worksheetsModel); //hide the check box for creation of original data, only shown if analysis curves are to be created ui->chkCreateDataCurve->setVisible(false); //SIGNALs/SLOTs connect(buttonBox, &QDialogButtonBox::accepted, this, &PlotDataDialog::plot); connect(buttonBox, &QDialogButtonBox::rejected, this, &PlotDataDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &PlotDataDialog::accept); connect(ui->rbCurvePlacement1, &QRadioButton::toggled, this, &PlotDataDialog::curvePlacementChanged); connect(ui->rbCurvePlacement2, &QRadioButton::toggled, this, &PlotDataDialog::curvePlacementChanged); connect(ui->rbPlotPlacement1, &QRadioButton::toggled, this, &PlotDataDialog::plotPlacementChanged); connect(ui->rbPlotPlacement2, &QRadioButton::toggled, this, &PlotDataDialog::plotPlacementChanged); connect(ui->rbPlotPlacement3, &QRadioButton::toggled, this, &PlotDataDialog::plotPlacementChanged); connect(cbExistingPlots, &TreeViewComboBox::currentModelIndexChanged, this, &PlotDataDialog::checkOkButton); connect(cbExistingWorksheets, &TreeViewComboBox::currentModelIndexChanged, this, &PlotDataDialog::checkOkButton); QTimer::singleShot(0, this, &PlotDataDialog::loadSettings); } void PlotDataDialog::loadSettings() { //restore saved settings if available QApplication::processEvents(QEventLoop::AllEvents, 0); const KConfigGroup conf(KSharedConfig::openConfig(), "PlotDataDialog"); if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); int index = conf.readEntry("CurvePlacement", 0); if (index == 2) ui->rbCurvePlacement2->setChecked(true); index = conf.readEntry("PlotPlacement", 0); if (index == 2) ui->rbPlotPlacement2->setChecked(true); if (index == 3) ui->rbPlotPlacement3->setChecked(true); } else resize( QSize(0,0).expandedTo(minimumSize()) ); processColumns(); plotPlacementChanged(); } PlotDataDialog::~PlotDataDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "PlotDataDialog"); int index = 0; if (ui->rbCurvePlacement1->isChecked()) index = 1; if (ui->rbCurvePlacement2->isChecked()) index = 2; conf.writeEntry("CurvePlacement", index); if (ui->rbPlotPlacement1->isChecked()) index = 1; if (ui->rbPlotPlacement2->isChecked()) index = 2; if (ui->rbPlotPlacement3->isChecked()) index = 3; conf.writeEntry("PlotPlacement", index); KWindowConfig::saveWindowSize(windowHandle(), conf); delete m_plotsModel; delete m_worksheetsModel; } void PlotDataDialog::setAnalysisAction(AnalysisAction action) { m_analysisAction = action; m_analysisMode = true; ui->chkCreateDataCurve->setVisible(true); } void PlotDataDialog::processColumns() { //columns to plot SpreadsheetView* view = reinterpret_cast(m_spreadsheet->view()); m_columns = view->selectedColumns(true); //use all spreadsheet columns if no columns are selected if (!m_columns.size()) { m_columns = m_spreadsheet->children(); //disable everything if the spreadsheet doesn't have any columns if (!m_columns.size()) { ui->gbData->setEnabled(false); ui->gbCurvePlacement->setEnabled(false); ui->gbPlotPlacement->setEnabled(false); return; } } m_columnComboBoxes << ui->cbXColumn; m_columnComboBoxes << ui->cbYColumn; //ui-widget only has one combobox for the y-data -> add additional comboboxes dynamically if required if (m_columns.size()>2) { QGridLayout* gridLayout = dynamic_cast(ui->scrollAreaYColumns->widget()->layout()); for (int i = 2; i < m_columns.size(); ++i) { QLabel* label = new QLabel(i18n("Y-data")); QComboBox* comboBox = new QComboBox(); gridLayout->addWidget(label, i+1, 0, 1, 1); gridLayout->addWidget(comboBox, i+1, 2, 1, 1); m_columnComboBoxes << comboBox; } } else { //two columns provided, only one curve is possible -> hide the curve placement options ui->rbCurvePlacement1->setChecked(true); ui->gbCurvePlacement->hide(); ui->gbPlotPlacement->setTitle(i18n("Add curve to")); } //determine the column names and the name of the first column having "X" as the plot designation QList columnNames; QString xColumnName; for(const Column* column : m_columns) { columnNames << column->name(); if (xColumnName.isEmpty() && column->plotDesignation() == AbstractColumn::X) xColumnName = column->name(); } //show all selected/available column names in the data comboboxes for(QComboBox* const comboBox : m_columnComboBoxes) comboBox->addItems(columnNames); if (!xColumnName.isEmpty()) { //show in the X-data combobox the first column having X as the plot designation ui->cbXColumn->setCurrentIndex(ui->cbXColumn->findText(xColumnName)); //for the remaining columns, show the names in the comboboxes for the Y-data //TODO: handle columns with error-designations int yColumnIndex = 1; //the index of the first Y-data comboBox in m_columnComboBoxes for(const QString& name : columnNames) { if (name != xColumnName) { QComboBox* comboBox = m_columnComboBoxes[yColumnIndex]; comboBox->setCurrentIndex(comboBox->findText(name)); yColumnIndex++; } } } else { //no column with "x plot designation" is selected, simply show all columns in the order they were selected. //first selected column will serve as the x-column. int yColumnIndex = 0; for(const QString& name : columnNames) { QComboBox* comboBox = m_columnComboBoxes[yColumnIndex]; comboBox->setCurrentIndex(comboBox->findText(name)); yColumnIndex++; } } } void PlotDataDialog::plot() { DEBUG("PlotDataDialog::plot()"); WAIT_CURSOR; if (ui->rbPlotPlacement1->isChecked()) { //add curves to an existing plot AbstractAspect* aspect = static_cast(cbExistingPlots->currentModelIndex().internalPointer()); CartesianPlot* plot = dynamic_cast(aspect); plot->beginMacro( i18n("Plot data from %1", m_spreadsheet->name()) ); addCurvesToPlot(plot); plot->endMacro(); } else if (ui->rbPlotPlacement2->isChecked()) { //add curves to a new plot in an existing worksheet AbstractAspect* aspect = static_cast(cbExistingWorksheets->currentModelIndex().internalPointer()); Worksheet* worksheet = dynamic_cast(aspect); worksheet->beginMacro( i18n("Plot data from %1", m_spreadsheet->name()) ); if (ui->rbCurvePlacement1->isChecked()) { //all curves in one plot CartesianPlot* plot = new CartesianPlot( i18n("Plot data from %1", m_spreadsheet->name()) ); plot->initDefault(CartesianPlot::FourAxes); //set the axis titles befor we add the plot to the worksheet //set the x-axis names const QString& xColumnName = ui->cbXColumn->currentText(); for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisHorizontal) { axis->title()->setText(xColumnName); break; } } //if we only have one single y-column to plot, we can set the title of the y-axes if (m_columnComboBoxes.size() == 2) { const QString& yColumnName = m_columnComboBoxes[1]->currentText(); for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisVertical) { axis->title()->setText(yColumnName); break; } } } worksheet->addChild(plot); addCurvesToPlot(plot); } else { //one plot per curve addCurvesToPlots(worksheet); } worksheet->endMacro(); } else { //add curves to a new plot(s) in a new worksheet Project* project = m_spreadsheet->project(); project->beginMacro( i18n("Plot data from %1", m_spreadsheet->name()) ); Worksheet* worksheet = new Worksheet(0, i18n("Plot data from %1", m_spreadsheet->name())); project->addChild(worksheet); if (ui->rbCurvePlacement1->isChecked()) { //all curves in one plot CartesianPlot* plot = new CartesianPlot( i18n("Plot data from %1", m_spreadsheet->name()) ); plot->initDefault(CartesianPlot::FourAxes); //set the axis titles befor we add the plot to the worksheet //set the x-axis names const QString& xColumnName = ui->cbXColumn->currentText(); for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisHorizontal) { axis->title()->setText(xColumnName); break; } } //if we only have one single y-column to plot, we can set the title of the y-axes if (m_columnComboBoxes.size() == 2) { const QString& yColumnName = m_columnComboBoxes[1]->currentText(); for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisVertical) { axis->title()->setText(yColumnName); break; } } } worksheet->addChild(plot); addCurvesToPlot(plot); } else { //one plot per curve addCurvesToPlots(worksheet); } project->endMacro(); } RESET_CURSOR; } Column* PlotDataDialog::columnFromName(const QString& name) const { for(auto* column : m_columns) { if (column->name() == name) return column; } return 0; } /*! * * for the selected columns in this dialog, creates a curve in the already existing plot \c plot. */ void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) const { QApplication::processEvents(QEventLoop::AllEvents, 100); Column* xColumn = columnFromName(ui->cbXColumn->currentText()); for (int i = 1; i < m_columnComboBoxes.size(); ++i) { QComboBox* comboBox = m_columnComboBoxes[i]; const QString& name = comboBox->currentText(); Column* yColumn = columnFromName(name); addCurve(name, xColumn, yColumn, plot); } plot->scaleAuto(); } /*! * for the selected columns in this dialog, creates a plot and a curve in the already existing worksheet \c worksheet. */ void PlotDataDialog::addCurvesToPlots(Worksheet* worksheet) const { QApplication::processEvents(QEventLoop::AllEvents, 100); worksheet->setSuppressLayoutUpdate(true); const QString& xColumnName = ui->cbXColumn->currentText(); Column* xColumn = columnFromName(xColumnName); for (int i = 1; i < m_columnComboBoxes.size(); ++i) { QComboBox* comboBox = m_columnComboBoxes[i]; const QString& name = comboBox->currentText(); Column* yColumn = columnFromName(name); CartesianPlot* plot = new CartesianPlot(i18n("Plot %1", name)); plot->initDefault(CartesianPlot::FourAxes); //set the axis names in the new plot bool xSet = false; bool ySet = false; for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisHorizontal && !xSet) { axis->title()->setText(xColumnName); xSet = true; } else if (axis->orientation() == Axis::AxisVertical && !ySet) { axis->title()->setText(name); ySet = true; } } worksheet->addChild(plot); addCurve(name, xColumn, yColumn, plot); plot->scaleAuto(); } worksheet->setSuppressLayoutUpdate(false); worksheet->updateLayout(); } /*! * helper function that does the actual creation of the curve and adding it as child to the \c plot. */ void PlotDataDialog::addCurve(const QString& name, Column* xColumn, Column* yColumn, CartesianPlot* plot) const { DEBUG("PlotDataDialog::addCurve()"); if (!m_analysisMode) { XYCurve* curve = new XYCurve(name); curve->setXColumn(xColumn); curve->setYColumn(yColumn); plot->addChild(curve); } else { bool createDataCurve = ui->chkCreateDataCurve->isChecked(); if (createDataCurve) { XYCurve* curve = new XYCurve(name); curve->setXColumn(xColumn); curve->setYColumn(yColumn); plot->addChild(curve); } //TODO: introduce a base class for all analysis curves and refactor this part switch (m_analysisAction) { case DataReduction: { XYDataReductionCurve* analysisCurve = new XYDataReductionCurve(i18n("Reduction of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Differentiation: { XYDifferentiationCurve* analysisCurve = new XYDifferentiationCurve(i18n("Derivative of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Integration: { XYIntegrationCurve* analysisCurve = new XYIntegrationCurve(i18n("Integral of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Interpolation: { XYInterpolationCurve* analysisCurve = new XYInterpolationCurve(i18n("Interpolation of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Smoothing: { XYSmoothCurve* analysisCurve = new XYSmoothCurve(i18n("Smoothing of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case FitLinear: case FitPower: case FitExp1: case FitExp2: case FitInvExp: case FitGauss: case FitCauchyLorentz: case FitTan: case FitTanh: case FitErrFunc: case FitCustom: { XYFitCurve* analysisCurve = new XYFitCurve(i18n("Fit to '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->initFitData(m_analysisAction); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case FourierFilter: { XYFourierFilterCurve* analysisCurve = new XYFourierFilterCurve(i18n("Fourier Filter of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } } } } //################################################################ //########################## Slots ############################### //################################################################ void PlotDataDialog::curvePlacementChanged() { if (ui->rbCurvePlacement1->isChecked()) { ui->rbPlotPlacement1->setEnabled(true); ui->rbPlotPlacement2->setText(i18n("new plot in an existing worksheet")); ui->rbPlotPlacement3->setText(i18n("new plot in a new worksheet")); } else { ui->rbPlotPlacement1->setEnabled(false); if (ui->rbPlotPlacement1->isChecked()) ui->rbPlotPlacement2->setChecked(true); ui->rbPlotPlacement2->setText(i18n("new plots in an existing worksheet")); ui->rbPlotPlacement3->setText(i18n("new plots in a new worksheet")); } } void PlotDataDialog::plotPlacementChanged() { if (ui->rbPlotPlacement1->isChecked()) { cbExistingPlots->setEnabled(true); cbExistingWorksheets->setEnabled(false); } else if (ui->rbPlotPlacement2->isChecked()) { cbExistingPlots->setEnabled(false); cbExistingWorksheets->setEnabled(true); } else { cbExistingPlots->setEnabled(false); cbExistingWorksheets->setEnabled(false); } checkOkButton(); } void PlotDataDialog::checkOkButton() { bool enable = false; + QString msg; if (ui->rbPlotPlacement1->isChecked()) { AbstractAspect* aspect = static_cast(cbExistingPlots->currentModelIndex().internalPointer()); enable = (aspect!=NULL); + if (!enable) + msg = i18n("An already existing plot has to be selected."); } else if (ui->rbPlotPlacement2->isChecked()) { AbstractAspect* aspect = static_cast(cbExistingWorksheets->currentModelIndex().internalPointer()); enable = (aspect!=NULL); + if (!enable) + msg = i18n("An already existing worksheet has to be selected."); } else enable = true; m_okButton->setEnabled(enable); + if (enable) + m_okButton->setToolTip(i18n("Close the dialog and plot the data.")); + else + m_okButton->setToolTip(msg); }