diff --git a/ChangeLog b/ChangeLog index fcd05bb56..39022b7d9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,275 +1,276 @@ -----2.7----- New features: * Allow to move objects (drag&drop) to different folders in the project explorer * [spreadsheet] allow to insert multiple rows and columns in one step * [spreadsheet] improved the handling of calculated columns (values calculated via a formula): * Allow to automatically update the calculated column on data changes in the variable columns * Invalidate the calculated column if one of the variable columns was deleted * In "Functions Values" dialog highlight the variable column combobox red if the variable column was deleted in the project * In "Functions Values" dialog don't allow to select columns to be calculated as variable columns (avoid circular dependencies) * In "Used in" context menu of a column show all other calculated columns where this column is used as a variable * [spreadsheet] when pasting data into empty columns, automatically convert their type to the type of the data to be pasted * BUG 406805 - support für directory structures in ROOT files * Automatically update curves if a data column deleted before was re-added again (during the import or manually) * When auto-scaling in the plot take also the error bars into account, if available * For live data sources allow to save the relative path to the data source UX improvements: * in the "Import Data" dialog show the history of the recently imported files * Improved the selection of curves in the plot that are near to or behind each other * BUG 408529 - Allow to use DEL to delete cells in spreadsheet and matrix * BUG 408537 - Allow to insert multiple rows and columns in the spreadsheet in one step * Added translations to Windows and macOS packages * Allow to stop the current selection in the plot with ESC * BUG 410855 - Seed the random number generator with the current time in order not to generate repetitive numbers in the spreadsheet. Performance: * Increased the plotting speed for curves with many data points by removing points which are shown on the same pixel in the scene Bug fixes: * Disable autoscale when zooming, otherwise wrong (= auto scaled) data region is shown after project save&open * [macOS] fix determining number of lines of data files * Bug 408530 - correctly show error bars for data source columns having NANs * Bug 407847 - fixed the broken project import + * Bug 411122 - fixed missing option for the resolution of PNG files in the export dialog * [data fitting] handle weighted fitting with zero errors correctly * [data fitting] allow weighted fitting with data source from curve * [data fitting] fix function for calculating bounded values * [nsl] fix memory leak in line simplification (Douglas Peuker variant) * [nsl] fix bug in calculating Bessel polynomials on Windows (used in Bessel filter) -----2.6----- New features: * Histogram * Import from MQTT sources * Import of ROOT (CERN) TH1 histograms * Import of Ngspice raw files (ASCII and binary) * Import of data in JSON format (JSON arrays and objects) * Improved import of NetCDF files * Added file type specific summary and content for special file types in info box of import dialog * Convolution/Deconvolution of data sets (sampling interval, linear/circular, normalization, wrap, standard kernel) * Cross-/Autocorrelation of data sets (sampling interval, linear/circular, normalization) * Allow to specify the number format when exporting spreadsheet and matrix * Improved user interface for data fitting (add fit function preview, show parameters directly, make options foldable) * [spreadsheet] when filling a float column with row numbers, automatically convert its type to integer * [spreadsheet] when filling an integer column with function values, automatically convert its type to float * [spreadsheet] data manipulation: add/subtract/multiply/divide for column values * [spreadsheet] export to SQLite * [matrix] data manipulation: add/subtract/multiply/divide for matrix values * [worksheet] Allow to specify different border shapes for labels (rectangle, eclipse, etc.) * [worksheet] Allow to rotate plot legends * [worksheet] Better positioning of rotated axis tick labels * [worksheet] Allow to make plots not-interactive (ignore mouse drag and wheel events) to avoid unwanted occasional panning and zooming * Allow to connect to SQL databases via ODBC * Show the amount of consumed memory in the status bar (optional) * Allow to change the settings for different computer algebra systems (Maxima, etc.) directly in LabPlot (embedd Cantor's settings widgets) Bug fixes: * Fixed several problems in live data support * [spreadsheet] properly calculate function values out of integer x-values * [matrix] fix editing integer values * [import] fix preview update and add missing close of netcdf files * Don't crash when a scaling factor equal to zero was set for axis -----2.5----- New features: * Support for reading and plotting of live-data * Improved data fitting * Automatically guess parameter of custom models * Better result presentation * Support different weight types * Consider given x- and y-error when fitting (can be switched off) * Show t statistics, P > |t| and confidence interval * Calculate p-value for chi-square and F test in nonlinear fitting * added fit models for most statistical distributions * Improved theming * Apply themes to worksheet and to all its children * Respect theme settings also in plot legends and labels * Allow to disable theming in worksheets and plots after a theme was selected * Show currently active theme in the "Apply theme" button * New application option in the settings for the default theme used for new worksheets. * Support different data types * auto detect integer and datetime data in import * support number locale and datetime formats * improved data type support in spreadsheets * Import from SQL databases (tables or custom queries) * Import Origin OPJ projects * Much better support for Windows and macOS * Syntax highlighting for LaTeX in the text label * Allow to set the background color for LaTeX labels * Support Hermite polynomials from GSL 2.4 * Support error functions and related functions from libcerf * "Used in" sub-menu in column contex menu for faster navigation to the curves consuming the column * Direct application of analysis functions (smoothing, interpolating, etc.) on the ploted data via curve's context menu * Direct application of analysis functions on the data in the spreadsheet and plotting of the results via spreadsheet's context menu * Drag columns in the project explorer and drop them on plots (either in a worksheet view or in the project explorer) to create curves * "Show last N points" and "Show first N points" data ranges in cartesian plot * Added CLI option --presenter to start LabPlot directly in the presenter mode * Added CLI parameter to directly open project files (LabPlot or Origin) * Allow drag&drop of projects files (LabPlot and Origin) on the main window to load the project * Allow drag&drop of data files on the main window to import the data * Show tooltips for the supported mathematical functions and constants in the expression text field * Automatically switch to the scientific representation for numbers bigger than 10^4 on the axis tick labels * Automatically allow the latex typesetting in the application after the latex environment was installed later without making the user to go to the settings dialog * Allow to change the color scheme for the application * Smooth and animated zooming in the worksheet view * Allow to add text labels to plots * Improved building with MSVC, Intel and PGI compiler Performance improvements: * Faster copy&paste in the spreadsheet Bug fixes: * Bug 379877 - masked rows in spreadsheet not restored in project * Calculation of fit results corrected * Axes now support values larger than FLT_MAX (~10^38) and smaller than FLT_MIN (~10^-38) * When a LabPlot project is being droped in the main window, load the project directly instead of showing the import file dialog * Correctly save and restore masked cells * Don't crash if the rc-file was not found during the startup -----2.4----- New features: * Support themes for plots * Import and editing of FITS data files * Data reduction by removing data points using multiple algorithms * Numerical differentiation and integration with several options * Many new pre-defined fit models (Gompertz, Weibull, Log-Normal, Gumbel, etc.) sorted in categories * Fit parameter now support fixed values, lower/upper limits and use Unicode * Fit model and random number distribution formulas are now rendered with LaTeX * Support user specified x range in all analysis functions * Allow to enter complete LaTeX documents in text labels * Configuration parameter to use different LaTex engines (LuaLaTex, XeLatex, pdfLaTex, LaTex) * Disable LaTeX typesetting if no LaTex installation (and other required tools) were found at runtime * Presenter mode for worksheets * Support for Mac OS * Support for Julia's vectors and tuples in CAS worksheets (requires Cantor v. 16.12 or higher) * Allow to jump directly to the data source spreadsheet via XYCurve's context menu * Select and delete multiple objects in project explorer * Improved and extended internal parser for mathematical expressions * Copy of worksheet elements as image to the clipboard via CTRL+C Bug fixes: * BUG: 361326 - Allow to select curves with overlapping bounding boxes * Correctly load worksheet sizes from saved templates * Fixed crash when removing columns in spreadsheet * Fixed crash when fitting using GSL >= 2 * List of available functions corrected * Constants are now available with full accuracy * Windows: Import of files and open recent files fixed -----2.3----- New features: * Integration of Cantor - Support for different open-source computer algebra systems * Statistics on spreadsheets and matrices * Export of spreadsheets and matrices to LaTeX tables * Interpolation of data including different splines, cosine, exponential, cubic Hermite (Catmull-Rom, cardinal, Kochanek-Bartels) and rational functions * Data smoothing using moving average (centered or lagged), percentile filter or Savitzky-Golay algorithm * Fourier filter (low pass, high pass, band pass, band reject) with ideal, Butterworth, Chebychev I+II, Legendre or Bessel-Thomson filter * Fourier transform with many window functions (Welch, Hann, Hamming, etc.) calculating magnitude, amplitude, power, phase, dB, etc. and supporting one/two sided spectrum with or without shift and x scaling to frequency, index or period * Filter and search capabilities in the drop down box for the selection of data sources * Sigmoid function as a new pre-defined fit model * Support for compiling on Microsoft Windows Performance improvements: * Faster generation of random values * Global option to enable/disable the double-buffering for faster painting of curves (enabled on default) Bug fixes: * Save and restore last used setting in RandomValuesDialog * Update axis title shape on title rotations correctly * Save and restore custom column widths in the spreadsheet * Fixed sporadic crashes during project close -----2.2----- New features: * Datapicker - tool for extracting curves and data points from imported images * Custom point on the plot with user-defined position and appearance * Accept drag&drop events * Support GSL 2.x * Import and export dialogs save and restore their settings and sizes Performance improvements: * Faster rendering of the image view of the matrix Bug fixes: * BUG: 354744 - make setting of range auto scaling in CartesianPlot undo/redo-able * Removed couple of hard coded sizes given in pixels for better user-experience on hidpi-displays * Fixes the bug with disabled undo/redo-actions in after the undo-history was cleared * Keep the information about the columns to be shown in the project explorer after project close * Fixed some bugs in painting of the selection band on the worksheet * Allow to open gz- and bz2-compressed LabPlot project files on the command line interface -----2.1----- New features: * New Matrix view for handling matrix data. * Workbook - a new container for grouping several objects of type Spreadsheet and/or Matrix. * Import of binary, image, NetCDF and HDF data into spreadsheet or matrix. * Visual HDF and NetCDF parser to view content of files and import data sets. * Preview of all supported file types in import dialog. * Transparently import compressed data files. * In xy-curve the points may not be connected by the line if there are NANs in-between. This behaviour is controlled by the parameter "skip gaps". * Multiplier of Pi format of the plot axis for periodical functions. * New operations on columns in Spreadsheet - reverse, drop values and mask values. * Formula used to generate the values in a column is stored and can be changed/adjusted in the formula dialog afterwards. * Curve filling: the area below, under, left to or right to the curve can be filled. * Support for multiple variables in "Function Values"-dialog - new data in the spreadsheet can be calculated out of multiple columns. Performance improvements: * Speeded up the creation of new columns during the import Bug fixes: * Fixed wrong behaviour when doing "zoom&select" in a plot and then deselecting the plot - it was not possible anymore to select the plot again on the worksheet. -----2.0.2----- New features: * Plot 2D-curves defined by mathematical equations in cartesian and polar coordinates or via a parametric equation. * Linear and non-linear regression analysis. Several predefined fit-models are provided. User-defined models are also possible. * Besides fixed worksheet sizes (predefined sizes like A4 etc. or user-defined), complete view size can be used. All sizes are automatically adjusted on resize. * Different axis arrow types. * "select region and zoom in", "select x-region and zoom in", "select y-region and zoom in" functions for cartesian plot. * Rounded border for several worksheet objects (plot area, legend etc.) * Hover effect for axis, curve and text label. * Added a MessageBox - ask befor deleting worksheet objects. * Added three new types for drop lines - "zero baseline", "min baseline" and "max baseline" * Fill the selection in Spreadsheet with a constant value provided by the user * Fill columns with uniform and non-uniform random numbers, several distributions are available. * Fill columns with function values * Allow custom resolutions for PNG-export * Export of the spreadsheet to a text file. * Simultaneous zooming and navigation accross multiple plots. * Implemented "Powers of 10/2/e" for the axis tick labels Bug fixes: * Don't crash when trying to create a plot in case no rc-file was installed. * Don't allow to select unwanted objects in TreeViewComboBox in ImportDialog and XYCurveDock. * Corrected painting of background images in plot area and legend. * BUG: 330723 - fixed weird selection in spreadsheet. * BUG: 330774 - fixed wrong positioning of axis on orientation changes. * Update main window title when project name is changed -----2.0.1----- Bug fix release. Solved issues: * Fixed wrong scaling of legend's text labels in pdf-export * Fixed memory corruption in CartesianPlotDock that can lead to crashes -----2.0.0----- First stable release of LabPlot2. LabPlot2 is a complete rewrite of LabPlot1 and lacks in this release a lot of features available in the predecessor. On the other hand, the GUI and the usability is more superior as compared to LabPlot1 and there are several new features that were not available in LabPlot1. Brief summary of features and capabilities of LabPlot2 implemented in the first release: * project-based management of data * created objects are organized in a tree and are visualized and accessible in the project explorer * for a better management of objects, additional folders and sub-folders can be created within a project * spreadsheet with very basic functionality is available for manual data entry * "file data source" can be used to import a file and to let LabPlot2 watch for changes in that file * external data from an ascii file can be also directly imported to a spreadsheet for further editing * worksheet is the main object where plots, labels etc. are placed on * several zooming functions for the worksheet * only cartesian plot is implemented in the first release * arbitrary number of freely positionable axes is possible * xy-curve is implemented. As the data source for the x- and y-values columns either from a spreadsheet or from a file data source can be used * several zooming and "movement" functions are available for the plots which help to navigate through data * legend for xy-plots * a lot of properties of the worksheet elements can be edited in a very easy way in the corresponding dock widgets * plots on the worksheet can be put into a horizontal, vertical or grid layouts * export of worksheet (entire worksheet or current seleciton) to pdf, eps, png and svg diff --git a/src/backend/core/Project.cpp b/src/backend/core/Project.cpp index bc0dc1c9d..ab57d117d 100644 --- a/src/backend/core/Project.cpp +++ b/src/backend/core/Project.cpp @@ -1,659 +1,668 @@ /*************************************************************************** File : Project.cpp Project : LabPlot Description : Represents a LabPlot project. -------------------------------------------------------------------- Copyright : (C) 2011-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 Knut Franke (knut.franke@gmx.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datasources/LiveDataSource.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/plots/cartesian/XYEquationCurve.h" #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/datapicker/DatapickerCurve.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include #include #include #include #include #include #include #include #include #include #include /** * \class Project * \brief Represents a project. * \ingroup core * Project represents the root node of all objects created during the runtime of the program. * Manages also the undo stack. */ /** * \enum Project::MdiWindowVisibility * \brief MDI subwindow visibility setting */ /** * \var Project::folderOnly * \brief only show MDI windows corresponding to Parts in the current folder */ /** * \var Project::foldAndSubfolders * \brief show MDI windows corresponding to Parts in the current folder and its subfolders */ /** * \var Project::allMdiWindows * \brief show MDI windows for all Parts in the project simultaneously */ class Project::Private { public: Private() : version(LVERSION), author(QString(qgetenv("USER"))), modificationTime(QDateTime::currentDateTime()) { } QUndoStack undo_stack; MdiWindowVisibility mdiWindowVisibility{Project::folderOnly}; QString fileName; QString version; QString author; QDateTime modificationTime; bool changed{false}; + bool aspectAddedSignalSuppressed{false}; }; Project::Project() : Folder(i18n("Project"), AspectType::Project), d(new Private()) { //load default values for name, comment and author from config KConfig config; KConfigGroup group = config.group("Project"); d->author = group.readEntry("Author", QString()); //we don't have direct access to the members name and comment //->temporaly disable the undo stack and call the setters setUndoAware(false); setIsLoading(true); setName(group.readEntry("Name", i18n("Project"))); setComment(group.readEntry("Comment", QString())); setUndoAware(true); setIsLoading(false); d->changed = false; connect(this, &Project::aspectDescriptionChanged,this, &Project::descriptionChanged); connect(this, &Project::aspectAdded,this, &Project::aspectAddedSlot); } Project::~Project() { //if the project is being closed and the live data sources still continue reading the data, //the dependent objects (columns, etc.), which are already deleted maybe here, are still being notified about the changes. //->stop reading the live data sources prior to deleting all objects. for (auto* lds : children()) lds->pauseReading(); #ifdef HAVE_MQTT for (auto* client : children()) client->pauseReading(); #endif //if the project is being closed, in Worksheet the scene items are being removed and the selection in the view can change. //don't react on these changes since this can lead crashes (worksheet object is already in the destructor). //->notify all worksheets about the project being closed. for (auto* w : children(AbstractAspect::Recursive)) w->setIsClosing(); d->undo_stack.clear(); delete d; } QUndoStack* Project::undoStack() const { return &d->undo_stack; } QMenu* Project::createContextMenu() { QMenu* menu = AbstractAspect::createContextMenu(); //add close action menu->addSeparator(); menu->addAction(QIcon::fromTheme(QLatin1String("document-close")), i18n("Close"), this, SIGNAL(closeRequested())); //add the actions from MainWin emit requestProjectContextMenu(menu); return menu; } QMenu* Project::createFolderContextMenu(const Folder* folder) { QMenu* menu = const_cast(folder)->AbstractAspect::createContextMenu(); emit requestFolderContextMenu(folder, menu); return menu; } void Project::setMdiWindowVisibility(MdiWindowVisibility visibility) { d->mdiWindowVisibility = visibility; emit mdiWindowVisibilityChanged(); } Project::MdiWindowVisibility Project::mdiWindowVisibility() const { return d->mdiWindowVisibility; } CLASS_D_ACCESSOR_IMPL(Project, QString, fileName, FileName, fileName) BASIC_D_ACCESSOR_IMPL(Project, QString, version, Version, version) CLASS_D_ACCESSOR_IMPL(Project, QString, author, Author, author) CLASS_D_ACCESSOR_IMPL(Project, QDateTime, modificationTime, ModificationTime, modificationTime) void Project::setChanged(const bool value) { if (isLoading()) return; if (value) emit changed(); d->changed = value; } +void Project::setSuppressAspectAddedSignal(bool value) { + d->aspectAddedSignalSuppressed = value; +} + +bool Project::aspectAddedSignalSuppressed() const { + return d->aspectAddedSignalSuppressed; +} + bool Project::hasChanged() const { return d->changed ; } /*! * \brief Project::descriptionChanged * \param column */ void Project::descriptionChanged(const AbstractAspect* aspect) { if (isLoading()) return; if (this != aspect) { const AbstractColumn* column = dynamic_cast(aspect); if (!column) return; // When the column is created, it gets a random name and is eventually not connected to any curve. // When changing the name it can match a curve and should than be connected to the curve. QVector curves = children(AbstractAspect::ChildIndexFlag::Recursive); QString columnPath = column->path(); // setXColumnPath must not be set, because if curve->column matches column, there already exist a // signal/slot connection between the curve and the column to update this. If they are not same, // xColumnPath is set in setXColumn. Same for the yColumn. for (auto* curve : curves) { curve->setUndoAware(false); auto* analysisCurve = dynamic_cast(curve); if (analysisCurve) { if (analysisCurve->xDataColumnPath() == columnPath) analysisCurve->setXDataColumn(column); if (analysisCurve->yDataColumnPath() == columnPath) analysisCurve->setYDataColumn(column); if (analysisCurve->y2DataColumnPath() == columnPath) analysisCurve->setY2DataColumn(column); auto* fitCurve = dynamic_cast(curve); if (fitCurve) { if (fitCurve->xErrorColumnPath() == columnPath) fitCurve->setXErrorColumn(column); if (fitCurve->yErrorColumnPath() == columnPath) fitCurve->setYErrorColumn(column); } } else { if (curve->xColumnPath() == columnPath) curve->setXColumn(column); if (curve->yColumnPath() == columnPath) curve->setYColumn(column); if (curve->valuesColumnPath() == columnPath) curve->setValuesColumn(column); if (curve->xErrorPlusColumnPath() == columnPath) curve->setXErrorPlusColumn(column); if (curve->xErrorMinusColumnPath() == columnPath) curve->setXErrorMinusColumn(column); if (curve->yErrorPlusColumnPath() == columnPath) curve->setYErrorPlusColumn(column); if (curve->yErrorMinusColumnPath() == columnPath) curve->setYErrorMinusColumn(column); } curve->setUndoAware(true); } QVector columns = children(AbstractAspect::ChildIndexFlag::Recursive); for (auto* tempColumn : columns) { const QVector formulaVariableColumns = tempColumn->formulaVariableColumns(); for (int i = 0; i < formulaVariableColumns.count(); i++) { if (formulaVariableColumns[i] == column) tempColumn->setformulVariableColumnsPath(i, columnPath); } } return; } d->changed = true; emit changed(); } /*! * \brief Project::aspectAddedSlot * When adding new columns, these should be connected to the corresponding curves * \param aspect */ void Project::aspectAddedSlot(const AbstractAspect* aspect) { const AbstractColumn* column = dynamic_cast(aspect); if (!column) return; QVector curves = children(AbstractAspect::ChildIndexFlag::Recursive); QString columnPath = column->path(); for (auto* curve : curves) { curve->setUndoAware(false); auto* analysisCurve = dynamic_cast(curve); if (analysisCurve) { if (analysisCurve->xDataColumnPath() == columnPath) analysisCurve->setXDataColumn(column); if (analysisCurve->yDataColumnPath() == columnPath) analysisCurve->setYDataColumn(column); if (analysisCurve->y2DataColumnPath() == columnPath) analysisCurve->setY2DataColumn(column); auto* fitCurve = dynamic_cast(curve); if (fitCurve) { if (fitCurve->xErrorColumnPath() == columnPath) fitCurve->setXErrorColumn(column); if (fitCurve->yErrorColumnPath() == columnPath) fitCurve->setYErrorColumn(column); } } else { if (curve->xColumnPath() == columnPath) curve->setXColumn(column); if (curve->yColumnPath() == columnPath) curve->setYColumn(column); if (curve->valuesColumnPath() == columnPath) curve->setValuesColumn(column); if (curve->xErrorPlusColumnPath() == columnPath) curve->setXErrorPlusColumn(column); if (curve->xErrorMinusColumnPath() == columnPath) curve->setXErrorMinusColumn(column); if (curve->yErrorPlusColumnPath() == columnPath) curve->setYErrorPlusColumn(column); if (curve->yErrorMinusColumnPath() == columnPath) curve->setYErrorMinusColumn(column); } curve->setUndoAware(true); } QVector columns = children(AbstractAspect::ChildIndexFlag::Recursive); for (auto* tempColumn : columns) { const QStringList formulaVariableColumnPaths = tempColumn->formulaVariableColumnPaths(); for (int i = 0; i < formulaVariableColumnPaths.count(); i++) { if (formulaVariableColumnPaths[i] == column->path()) tempColumn->setformulVariableColumn(i, const_cast(static_cast(column))); } } } void Project::navigateTo(const QString& path) { emit requestNavigateTo(path); } bool Project::isLabPlotProject(const QString& fileName) { return fileName.endsWith(QStringLiteral(".lml"), Qt::CaseInsensitive) || fileName.endsWith(QStringLiteral(".lml.gz"), Qt::CaseInsensitive) || fileName.endsWith(QStringLiteral(".lml.bz2"), Qt::CaseInsensitive) || fileName.endsWith(QStringLiteral(".lml.xz"), Qt::CaseInsensitive); } QString Project::supportedExtensions() { static const QString extensions = "*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ"; return extensions; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## void Project::save(const QPixmap& thumbnail, QXmlStreamWriter* writer) const { //set the version and the modification time to the current values d->version = LVERSION; d->modificationTime = QDateTime::currentDateTime(); writer->setAutoFormatting(true); writer->writeStartDocument(); writer->writeDTD(""); writer->writeStartElement("project"); writer->writeAttribute("version", version()); writer->writeAttribute("fileName", fileName()); writer->writeAttribute("modificationTime", modificationTime().toString("yyyy-dd-MM hh:mm:ss:zzz")); writer->writeAttribute("author", author()); QByteArray bArray; QBuffer buffer(&bArray); buffer.open(QIODevice::WriteOnly); QPixmap scaledThumbnail = thumbnail.scaled(512,512, Qt::KeepAspectRatio); scaledThumbnail.save(&buffer, "JPEG"); QString image = QString::fromLatin1(bArray.toBase64().data()); writer->writeAttribute("thumbnail", image); writeBasicAttributes(writer); writeCommentElement(writer); save(writer); } /** * \brief Save as XML */ void Project::save(QXmlStreamWriter* writer) const { //save all children for (auto* child : children(IncludeHidden)) { writer->writeStartElement("child_aspect"); child->save(writer); writer->writeEndElement(); } //save the state of the views (visible, maximized/minimized/geometry) //and the state of the project explorer (expanded items, currently selected item) emit requestSaveState(writer); writer->writeEndElement(); writer->writeEndDocument(); } bool Project::load(const QString& filename, bool preview) { QIODevice* file; // first try gzip compression, because projects can be gzipped and end with .lml if (filename.endsWith(QLatin1String(".lml"), Qt::CaseInsensitive)) file = new KCompressionDevice(filename,KFilterDev::compressionTypeForMimeType("application/x-gzip")); else // opens filename using file ending file = new KFilterDev(filename); if (!file) file = new QFile(filename); if (!file->open(QIODevice::ReadOnly)) { KMessageBox::error(nullptr, i18n("Sorry. Could not open file for reading.")); return false; } char c; bool rc = file->getChar(&c); if (!rc) { KMessageBox::error(nullptr, i18n("The project file is empty."), i18n("Error opening project")); file->close(); delete file; return false; } file->seek(0); //parse XML XmlStreamReader reader(file); setIsLoading(true); rc = this->load(&reader, preview); setIsLoading(false); if (rc == false) { RESET_CURSOR; QString msg = reader.errorString(); if (msg.isEmpty()) msg = i18n("Unknown error when opening the project %1.", filename); KMessageBox::error(nullptr, msg, i18n("Error when opening the project")); return false; } if (reader.hasWarnings()) { qWarning("The following problems occurred when loading the project file:"); const QStringList& warnings = reader.warningStrings(); for (const auto& str : warnings) qWarning() << qUtf8Printable(str); //TODO: show warnings in a kind of "log window" but not in message box // KMessageBox::error(this, msg, i18n("Project loading partly failed")); } file->close(); delete file; return true; } /** * \brief Load from XML */ bool Project::load(XmlStreamReader* reader, bool preview) { while (!(reader->isStartDocument() || reader->atEnd())) reader->readNext(); if (!(reader->atEnd())) { if (!reader->skipToNextTag()) return false; if (reader->name() == "project") { QString version = reader->attributes().value("version").toString(); if (version.isEmpty()) reader->raiseWarning(i18n("Attribute 'version' is missing.")); else d->version = version; if (!readBasicAttributes(reader)) return false; if (!readProjectAttributes(reader)) return false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement()) break; if (reader->isStartElement()) { if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "child_aspect") { if (!readChildAspectElement(reader, preview)) return false; } else if (reader->name() == "state") { //load the state of the views (visible, maximized/minimized/geometry) //and the state of the project explorer (expanded items, currently selected item) emit requestLoadState(reader); } else { reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } } } else // no project element reader->raiseError(i18n("no project element found")); } else // no start document reader->raiseError(i18n("no valid XML document found")); if (!preview) { //wait until all columns are decoded from base64-encoded data QThreadPool::globalInstance()->waitForDone(); //LiveDataSource: //call finalizeLoad() to replace relative with absolute paths if required //and to create columns during the initial read QVector sources = children(AbstractAspect::Recursive); for (auto* source : sources) { if (!source) continue; source->finalizeLoad(); } //everything is read now. //restore the pointer to the data sets (columns) in xy-curves etc. QVector columns = children(AbstractAspect::Recursive); //xy-curves // cannot be removed by the column observer, because it does not react // on curve changes QVector curves = children(AbstractAspect::Recursive); for (auto* curve : curves) { if (!curve) continue; curve->suppressRetransform(true); auto* equationCurve = dynamic_cast(curve); auto* analysisCurve = dynamic_cast(curve); if (equationCurve) { //curves defined by a mathematical equations recalculate their own columns on load again. if (!preview) equationCurve->recalculate(); } else if (analysisCurve) { RESTORE_COLUMN_POINTER(analysisCurve, xDataColumn, XDataColumn); RESTORE_COLUMN_POINTER(analysisCurve, yDataColumn, YDataColumn); RESTORE_COLUMN_POINTER(analysisCurve, y2DataColumn, Y2DataColumn); auto* fitCurve = dynamic_cast(curve); if (fitCurve) { RESTORE_COLUMN_POINTER(fitCurve, xErrorColumn, XErrorColumn); RESTORE_COLUMN_POINTER(fitCurve, yErrorColumn, YErrorColumn); } } else { RESTORE_COLUMN_POINTER(curve, xColumn, XColumn); RESTORE_COLUMN_POINTER(curve, yColumn, YColumn); RESTORE_COLUMN_POINTER(curve, valuesColumn, ValuesColumn); RESTORE_COLUMN_POINTER(curve, xErrorPlusColumn, XErrorPlusColumn); RESTORE_COLUMN_POINTER(curve, xErrorMinusColumn, XErrorMinusColumn); RESTORE_COLUMN_POINTER(curve, yErrorPlusColumn, YErrorPlusColumn); RESTORE_COLUMN_POINTER(curve, yErrorMinusColumn, YErrorMinusColumn); qDebug()<<"curve columns " << curve->xColumn() << " " << curve->yColumn(); } if (dynamic_cast(curve)) RESTORE_POINTER(dynamic_cast(curve), dataSourceCurve, DataSourceCurve, XYCurve, curves); curve->suppressRetransform(false); } //axes QVector axes = children(AbstractAspect::Recursive); for (auto* axis : axes) { if (!axis) continue; RESTORE_COLUMN_POINTER(axis, majorTicksColumn, MajorTicksColumn); RESTORE_COLUMN_POINTER(axis, minorTicksColumn, MinorTicksColumn); } //histograms QVector hists = children(AbstractAspect::Recursive); for (auto* hist : hists) { if (!hist) continue; RESTORE_COLUMN_POINTER(hist, dataColumn, DataColumn); } //data picker curves QVector dataPickerCurves = children(AbstractAspect::Recursive); for (auto* dataPickerCurve : dataPickerCurves) { if (!dataPickerCurve) continue; RESTORE_COLUMN_POINTER(dataPickerCurve, posXColumn, PosXColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, posYColumn, PosYColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaXColumn, PlusDeltaXColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaXColumn, MinusDeltaXColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, plusDeltaYColumn, PlusDeltaYColumn); RESTORE_COLUMN_POINTER(dataPickerCurve, minusDeltaYColumn, MinusDeltaYColumn); } //if a column was calculated via a formula, restore the pointers to the variable columns defining the formula for (auto* col : columns) { if (!col->formulaVariableColumnPaths().isEmpty()) { QVector& formulaVariableColumns = const_cast&>(col->formulaVariableColumns()); for (auto path : col->formulaVariableColumnPaths()) { bool found = false; for (Column* c : columns) { if (!c) continue; if (c->path() == path) { formulaVariableColumns << c; col->finalizeLoad(); found = true; break; } } if (!found) formulaVariableColumns << nullptr; } } } //all data was read in spreadsheets: //call CartesianPlot::retransform() to retransform the plots for (auto* plot : children(AbstractAspect::Recursive)) { plot->setIsLoading(false); plot->retransform(); } //all data was read in live-data sources: //call CartesianPlot::dataChanged() to notify affected plots about the new data. //this needs to be done here since in LiveDataSource::finalizeImport() called above //where the data is read the column pointers are not restored yes in curves. QVector plots; for (auto* source : sources) { for (int n = 0; n < source->columnCount(); ++n) { Column* column = source->column(n); //determine the plots where the column is consumed for (const auto* curve : curves) { if (curve->xColumn() == column || curve->yColumn() == column) { auto* plot = dynamic_cast(curve->parentAspect()); if (plots.indexOf(plot) == -1) { plots << plot; plot->setSuppressDataChangedSignal(true); } } } column->setChanged(); } } //loop over all affected plots and retransform them for (auto* plot : plots) { plot->setSuppressDataChangedSignal(true); plot->dataChanged(); } } emit loaded(); return !reader->hasError(); } bool Project::readProjectAttributes(XmlStreamReader* reader) { QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value(reader->namespaceUri().toString(), "modificationTime").toString(); QDateTime modificationTime = QDateTime::fromString(str, "yyyy-dd-MM hh:mm:ss:zzz"); if (str.isEmpty() || !modificationTime.isValid()) { reader->raiseWarning(i18n("Invalid project modification time. Using current time.")); d->modificationTime = QDateTime::currentDateTime(); } else d->modificationTime = modificationTime; d->author = attribs.value(reader->namespaceUri().toString(), "author").toString(); return true; } diff --git a/src/backend/core/Project.h b/src/backend/core/Project.h index 05c2dd1b5..fc2b4d853 100644 --- a/src/backend/core/Project.h +++ b/src/backend/core/Project.h @@ -1,105 +1,108 @@ /*************************************************************************** File : Project.h Project : LabPlot Description : Represents a LabPlot project. -------------------------------------------------------------------- Copyright : (C) 2011-2014 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 Knut Franke (knut.franke@gmx.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef PROJECT_H #define PROJECT_H #include "backend/core/Folder.h" #include "backend/lib/macros.h" class QString; class Project : public Folder { Q_OBJECT public: enum MdiWindowVisibility { folderOnly, folderAndSubfolders, allMdiWindows }; public: Project(); ~Project() override; virtual const Project* project() const { return this; } Project* project() override { return this; } QUndoStack* undoStack() const override; QString path() const override { return name(); } QMenu* createContextMenu() override; virtual QMenu* createFolderContextMenu(const Folder*); void setMdiWindowVisibility(MdiWindowVisibility visibility); MdiWindowVisibility mdiWindowVisibility() const; CLASS_D_ACCESSOR_DECL(QString, fileName, FileName) BASIC_D_ACCESSOR_DECL(QString, version, Version) CLASS_D_ACCESSOR_DECL(QString, author, Author) CLASS_D_ACCESSOR_DECL(QDateTime, modificationTime, ModificationTime) void setChanged(const bool value=true); bool hasChanged() const; void navigateTo(const QString& path); + void setSuppressAspectAddedSignal(bool); + bool aspectAddedSignalSuppressed() const; + void save(const QPixmap&, QXmlStreamWriter*) const; bool load(XmlStreamReader*, bool preview) override; bool load(const QString&, bool preview = false); static bool isLabPlotProject(const QString& fileName); static QString supportedExtensions(); public slots: void descriptionChanged(const AbstractAspect*); void aspectAddedSlot(const AbstractAspect*); signals: void requestSaveState(QXmlStreamWriter*) const; void requestLoadState(XmlStreamReader*); void requestProjectContextMenu(QMenu*); void requestFolderContextMenu(const Folder*, QMenu*); void mdiWindowVisibilityChanged(); void changed(); void requestNavigateTo(const QString& path); void loaded(); void closeRequested(); private: class Private; Private* d; bool readProjectAttributes(XmlStreamReader*); void save(QXmlStreamWriter*) const override; }; #endif // ifndef PROJECT_H diff --git a/src/commonfrontend/ProjectExplorer.cpp b/src/commonfrontend/ProjectExplorer.cpp index f87aa2db4..57ada7620 100644 --- a/src/commonfrontend/ProjectExplorer.cpp +++ b/src/commonfrontend/ProjectExplorer.cpp @@ -1,902 +1,902 @@ /*************************************************************************** File : ProjectExplorer.cpp Project : LabPlot Description : A tree view for displaying and editing an AspectTreeModel. -------------------------------------------------------------------- Copyright : (C) 2007-2008 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2010-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ProjectExplorer.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/AbstractPart.h" #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/core/PartMdiView.h" #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ProjectExplorer \brief A tree view for displaying and editing an AspectTreeModel. In addition to the functionality of QTreeView, ProjectExplorer allows the usage of the context menus provided by AspectTreeModel and propagates the item selection in the view to the model. Furthermore, features for searching and filtering in the model are provided. \ingroup commonfrontend */ ProjectExplorer::ProjectExplorer(QWidget* parent) : m_treeView(new QTreeView(parent)), m_frameFilter(new QFrame(this)) { auto* layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); auto* layoutFilter = new QHBoxLayout(m_frameFilter); layoutFilter->setSpacing(0); layoutFilter->setContentsMargins(0, 0, 0, 0); QLabel* lFilter = new QLabel(i18n("Search/Filter:")); layoutFilter->addWidget(lFilter); m_leFilter = new QLineEdit(m_frameFilter); m_leFilter->setClearButtonEnabled(true); m_leFilter->setPlaceholderText(i18n("Search/Filter text")); layoutFilter->addWidget(m_leFilter); bFilterOptions = new QPushButton(m_frameFilter); bFilterOptions->setIcon(QIcon::fromTheme("configure")); bFilterOptions->setCheckable(true); layoutFilter->addWidget(bFilterOptions); layout->addWidget(m_frameFilter); m_treeView->setAnimated(true); m_treeView->setAlternatingRowColors(true); m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_treeView->setUniformRowHeights(true); m_treeView->viewport()->installEventFilter(this); m_treeView->header()->setStretchLastSection(true); m_treeView->header()->installEventFilter(this); m_treeView->setDragEnabled(true); m_treeView->setAcceptDrops(true); m_treeView->setDropIndicatorShown(true); m_treeView->setDragDropMode(QAbstractItemView::InternalMove); layout->addWidget(m_treeView); this->createActions(); connect(m_leFilter, &QLineEdit::textChanged, this, &ProjectExplorer::filterTextChanged); connect(bFilterOptions, &QPushButton::toggled, this, &ProjectExplorer::toggleFilterOptionsMenu); } void ProjectExplorer::createActions() { caseSensitiveAction = new QAction(i18n("Case Sensitive"), this); caseSensitiveAction->setCheckable(true); caseSensitiveAction->setChecked(false); connect(caseSensitiveAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterCaseSensitivity); matchCompleteWordAction = new QAction(i18n("Match Complete Word"), this); matchCompleteWordAction->setCheckable(true); matchCompleteWordAction->setChecked(false); connect(matchCompleteWordAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterMatchCompleteWord); expandTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand All"), this); connect(expandTreeAction, &QAction::triggered, m_treeView, &QTreeView::expandAll); expandSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand Selected"), this); connect(expandSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::expandSelected); collapseTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse All"), this); connect(collapseTreeAction, &QAction::triggered, m_treeView, &QTreeView::collapseAll); collapseSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse Selected"), this); connect(collapseSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::collapseSelected); deleteSelectedTreeAction = new QAction(QIcon::fromTheme("edit-delete"), i18n("Delete Selected"), this); connect(deleteSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::deleteSelected); toggleFilterAction = new QAction(QIcon::fromTheme(QLatin1String("view-filter")), i18n("Hide Search/Filter Options"), this); connect(toggleFilterAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterWidgets); showAllColumnsAction = new QAction(i18n("Show All"),this); showAllColumnsAction->setCheckable(true); showAllColumnsAction->setChecked(true); showAllColumnsAction->setEnabled(false); connect(showAllColumnsAction, &QAction::triggered, this, &ProjectExplorer::showAllColumns); } /*! shows the context menu in the tree. In addition to the context menu of the currently selected aspect, treeview specific options are added. */ void ProjectExplorer::contextMenuEvent(QContextMenuEvent *event) { if (!m_treeView->model()) return; const QModelIndex& index = m_treeView->indexAt(m_treeView->viewport()->mapFrom(this, event->pos())); if (!index.isValid()) m_treeView->clearSelection(); const QModelIndexList& items = m_treeView->selectionModel()->selectedIndexes(); QMenu* menu = nullptr; if (items.size()/4 == 1) { auto* aspect = static_cast(index.internalPointer()); menu = aspect->createContextMenu(); } else { menu = new QMenu(); QMenu* projectMenu = m_project->createContextMenu(); projectMenu->setTitle(m_project->name()); menu->addMenu(projectMenu); menu->addSeparator(); if (items.size()/4 > 1) { menu->addAction(expandSelectedTreeAction); menu->addAction(collapseSelectedTreeAction); menu->addSeparator(); menu->addAction(deleteSelectedTreeAction); menu->addSeparator(); } else { menu->addAction(expandTreeAction); menu->addAction(collapseTreeAction); menu->addSeparator(); menu->addAction(toggleFilterAction); //Menu for showing/hiding the columns in the tree view QMenu* columnsMenu = menu->addMenu(i18n("Show/Hide columns")); columnsMenu->addAction(showAllColumnsAction); columnsMenu->addSeparator(); for (auto* action : list_showColumnActions) columnsMenu->addAction(action); //TODO //Menu for showing/hiding the top-level aspects (Worksheet, Spreadhsheet, etc) in the tree view // QMenu* objectsMenu = menu->addMenu(i18n("Show/Hide objects")); } } if (menu) menu->exec(event->globalPos()); delete menu; } void ProjectExplorer::setCurrentAspect(const AbstractAspect* aspect) { const AspectTreeModel* tree_model = qobject_cast(m_treeView->model()); if (tree_model) m_treeView->setCurrentIndex(tree_model->modelIndexOfAspect(aspect)); } /*! Sets the \c model for the tree view to present. */ void ProjectExplorer::setModel(AspectTreeModel* treeModel) { m_treeView->setModel(treeModel); connect(treeModel, &AspectTreeModel::renameRequested, m_treeView, static_cast(&QAbstractItemView::edit)); connect(treeModel, &AspectTreeModel::indexSelected, this, &ProjectExplorer::selectIndex); connect(treeModel, &AspectTreeModel::indexDeselected, this, &ProjectExplorer::deselectIndex); connect(treeModel, &AspectTreeModel::hiddenAspectSelected, this, &ProjectExplorer::hiddenAspectSelected); connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ProjectExplorer::currentChanged); connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectExplorer::selectionChanged); //create action for showing/hiding the columns in the tree. //this is done here since the number of columns is not available in createActions() yet. if (list_showColumnActions.size() == 0) { for (int i = 0; i < m_treeView->model()->columnCount(); i++) { QAction* showColumnAction = new QAction(treeModel->headerData(i, Qt::Horizontal).toString(), this); showColumnAction->setCheckable(true); showColumnAction->setChecked(true); list_showColumnActions.append(showColumnAction); connect(showColumnAction, &QAction::triggered, this, [=] { ProjectExplorer::toggleColumn(i); }); } } else { for (int i = 0; i < list_showColumnActions.size(); ++i) { if (!list_showColumnActions.at(i)->isChecked()) m_treeView->hideColumn(i); } } } void ProjectExplorer::setProject(Project* project) { connect(project, &Project::aspectAdded, this, &ProjectExplorer::aspectAdded); connect(project, &Project::requestSaveState, this, &ProjectExplorer::save); connect(project, &Project::requestLoadState, this, &ProjectExplorer::load); connect(project, &Project::requestNavigateTo, this, &ProjectExplorer::navigateTo); connect(project, &Project::loaded, this, &ProjectExplorer::projectLoaded); m_project = project; //for newly created projects, resize the header to fit the size of the header section names. //for projects loaded from a file, this function will be called laterto fit the sizes //of the content once the project is loaded resizeHeader(); } QModelIndex ProjectExplorer::currentIndex() const { return m_treeView->currentIndex(); } /*! handles the contextmenu-event of the horizontal header in the tree view. Provides a menu for selective showing and hiding of columns. */ bool ProjectExplorer::eventFilter(QObject* obj, QEvent* event) { if (obj == m_treeView->header() && event->type() == QEvent::ContextMenu) { //Menu for showing/hiding the columns in the tree view QMenu* columnsMenu = new QMenu(m_treeView->header()); columnsMenu->addSection(i18n("Columns")); columnsMenu->addAction(showAllColumnsAction); columnsMenu->addSeparator(); for (auto* action : list_showColumnActions) columnsMenu->addAction(action); auto* e = static_cast(event); columnsMenu->exec(e->globalPos()); delete columnsMenu; return true; } else if (obj == m_treeView->viewport()) { auto* e = static_cast(event); if (event->type() == QEvent::MouseButtonPress) { if (e->button() == Qt::LeftButton) { QModelIndex index = m_treeView->indexAt(e->pos()); if (!index.isValid()) return false; auto* aspect = static_cast(index.internalPointer()); if (aspect->isDraggable()) { m_dragStartPos = e->globalPos(); m_dragStarted = false; } } } else if (event->type() == QEvent::MouseMove) { if ( !m_dragStarted && m_treeView->selectionModel()->selectedIndexes().size() > 0 && (e->globalPos() - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { m_dragStarted = true; auto* drag = new QDrag(this); auto* mimeData = new QMimeData; //determine the selected objects and serialize the pointers to QMimeData QVector vec; QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); //there are four model indices in each row -> divide by 4 to obtain the number of selected rows (=aspects) for (int i = 0; i < items.size()/4; ++i) { const QModelIndex& index = items.at(i*4); auto* aspect = static_cast(index.internalPointer()); vec << (quintptr)aspect; } QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << vec; mimeData->setData("labplot-dnd", data); drag->setMimeData(mimeData); drag->exec(); } } else if (event->type() == QEvent::DragEnter) { //ignore events not related to internal drags of columns etc., e.g. dropping of external files onto LabPlot auto* dragEnterEvent = static_cast(event); const QMimeData* mimeData = dragEnterEvent->mimeData(); if (!mimeData) { event->ignore(); return false; } if (mimeData->formats().at(0) != QLatin1String("labplot-dnd")) { event->ignore(); return false; } event->setAccepted(true); } else if (event->type() == QEvent::DragMove) { auto* dragMoveEvent = static_cast(event); const QMimeData* mimeData = dragMoveEvent->mimeData(); //determine the first aspect being dragged QByteArray data = mimeData->data(QLatin1String("labplot-dnd")); QVector vec; QDataStream stream(&data, QIODevice::ReadOnly); stream >> vec; AbstractAspect* sourceAspect{nullptr}; if (!vec.isEmpty()) sourceAspect = (AbstractAspect*)vec.at(0); if (!sourceAspect) return false; //determine the aspect under the cursor QModelIndex index = m_treeView->indexAt(dragMoveEvent->pos()); if (!index.isValid()) return false; //accept only the events when the aspect being dragged is dropable onto the aspect under the cursor //and the aspect under the cursor is not already the parent of the dragged aspect AbstractAspect* destinationAspect = static_cast(index.internalPointer()); bool accept = sourceAspect->dropableOn().indexOf(destinationAspect->type()) != -1 && sourceAspect->parentAspect() != destinationAspect; event->setAccepted(accept); } else if (event->type() == QEvent::Drop) { auto* dropEvent = static_cast(event); QModelIndex index = m_treeView->indexAt(dropEvent->pos()); if (!index.isValid()) return false; auto* aspect = static_cast(index.internalPointer()); aspect->processDropEvent(dropEvent); } } return QObject::eventFilter(obj, event); } //############################################################################## //################################# SLOTS #################################### //############################################################################## /*! * called after the project was loaded. * resize the header of the view to adjust to the content * and re-select the currently selected object to show * its final properties in the dock widget after in Project::load() all * pointers were restored, relative paths replaced by absolute, etc. */ void ProjectExplorer::projectLoaded() { resizeHeader(); selectionChanged(m_treeView->selectionModel()->selection(), QItemSelection()); } /*! expand the aspect \c aspect (the tree index corresponding to it) in the tree view and makes it visible and selected. Called when a new aspect is added to the project. */ void ProjectExplorer::aspectAdded(const AbstractAspect* aspect) { - if (m_project->isLoading()) + if (m_project->isLoading() ||m_project->aspectAddedSignalSuppressed()) return; //don't do anything if hidden aspects were added if (aspect->hidden()) return; //don't do anything for newly added data spreadsheets of data picker curves if (aspect->inherits(AspectType::Spreadsheet) && aspect->parentAspect()->inherits(AspectType::DatapickerCurve)) return; const AspectTreeModel* tree_model = qobject_cast(m_treeView->model()); const QModelIndex& index = tree_model->modelIndexOfAspect(aspect); //expand and make the aspect visible m_treeView->setExpanded(index, true); // newly added columns are only expanded but not selected, return here if (aspect->inherits(AspectType::Column)) { m_treeView->setExpanded(tree_model->modelIndexOfAspect(aspect->parentAspect()), true); return; } m_treeView->scrollTo(index); m_treeView->setCurrentIndex(index); m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2); } void ProjectExplorer::navigateTo(const QString& path) { const AspectTreeModel* tree_model = qobject_cast(m_treeView->model()); if (tree_model) m_treeView->setCurrentIndex(tree_model->modelIndexOfAspect(path)); } void ProjectExplorer::currentChanged(const QModelIndex & current, const QModelIndex & previous) { if (m_project->isLoading()) return; Q_UNUSED(previous); emit currentAspectChanged(static_cast(current.internalPointer())); } void ProjectExplorer::toggleColumn(int index) { //determine the total number of checked column actions int checked = 0; for (const auto* action : list_showColumnActions) { if (action->isChecked()) checked++; } if (list_showColumnActions.at(index)->isChecked()) { m_treeView->showColumn(index); m_treeView->header()->resizeSection(0,0 ); m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); for (auto* action : list_showColumnActions) action->setEnabled(true); //deactivate the "show all column"-action, if all actions are checked if ( checked == list_showColumnActions.size() ) { showAllColumnsAction->setEnabled(false); showAllColumnsAction->setChecked(true); } } else { m_treeView->hideColumn(index); showAllColumnsAction->setEnabled(true); showAllColumnsAction->setChecked(false); //if there is only one checked column-action, deactivated it. //It should't be possible to hide all columns if ( checked == 1 ) { int i = 0; while ( !list_showColumnActions.at(i)->isChecked() ) i++; list_showColumnActions.at(i)->setEnabled(false); } } } void ProjectExplorer::showAllColumns() { for (int i = 0; i < m_treeView->model()->columnCount(); i++) { m_treeView->showColumn(i); m_treeView->header()->resizeSection(0,0 ); m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); } showAllColumnsAction->setEnabled(false); for (auto* action : list_showColumnActions) { action->setEnabled(true); action->setChecked(true); } } /*! shows/hides the frame with the search/filter widgets */ void ProjectExplorer::toggleFilterWidgets() { if (m_frameFilter->isVisible()) { m_frameFilter->hide(); toggleFilterAction->setText(i18n("Show Search/Filter Options")); } else { m_frameFilter->show(); toggleFilterAction->setText(i18n("Hide Search/Filter Options")); } } /*! toggles the menu for the filter/search options */ void ProjectExplorer::toggleFilterOptionsMenu(bool checked) { if (checked) { QMenu menu; menu.addAction(caseSensitiveAction); menu.addAction(matchCompleteWordAction); connect(&menu, &QMenu::aboutToHide, bFilterOptions, &QPushButton::toggle); menu.exec(bFilterOptions->mapToGlobal(QPoint(0,bFilterOptions->height()))); } } void ProjectExplorer::resizeHeader() { m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2); //make the column "Name" somewhat bigger } /*! called when the filter/search text was changend. */ void ProjectExplorer::filterTextChanged(const QString& text) { QModelIndex root = m_treeView->model()->index(0,0); filter(root, text); } bool ProjectExplorer::filter(const QModelIndex& index, const QString& text) { Qt::CaseSensitivity sensitivity = caseSensitiveAction->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive; bool matchCompleteWord = matchCompleteWordAction->isChecked(); bool childVisible = false; const int rows = index.model()->rowCount(index); for (int i = 0; i < rows; i++) { QModelIndex child = index.model()->index(i, 0, index); auto* aspect = static_cast(child.internalPointer()); bool visible; if (text.isEmpty()) visible = true; else if (matchCompleteWord) visible = aspect->name().startsWith(text, sensitivity); else visible = aspect->name().contains(text, sensitivity); if (visible) { //current item is visible -> make all its children visible without applying the filter for (int j = 0; j < child.model()->rowCount(child); ++j) { m_treeView->setRowHidden(j, child, false); if (text.isEmpty()) filter(child, text); } childVisible = true; } else { //check children items. if one of the children is visible, make the parent (current) item visible too. visible = filter(child, text); if (visible) childVisible = true; } m_treeView->setRowHidden(i, index, !visible); } return childVisible; } void ProjectExplorer::toggleFilterCaseSensitivity() { filterTextChanged(m_leFilter->text()); } void ProjectExplorer::toggleFilterMatchCompleteWord() { filterTextChanged(m_leFilter->text()); } void ProjectExplorer::selectIndex(const QModelIndex& index) { if (m_project->isLoading()) return; if ( !m_treeView->selectionModel()->isSelected(index) ) { m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); m_treeView->setExpanded(index, true); m_treeView->scrollTo(index); } } void ProjectExplorer::deselectIndex(const QModelIndex & index) { if (m_project->isLoading()) return; if ( m_treeView->selectionModel()->isSelected(index) ) m_treeView->selectionModel()->select(index, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); } void ProjectExplorer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndex index; QModelIndexList items; AbstractAspect* aspect = nullptr; //there are four model indices in each row //-> divide by 4 to obtain the number of selected rows (=aspects) items = selected.indexes(); for (int i = 0; i < items.size()/4; ++i) { index = items.at(i*4); aspect = static_cast(index.internalPointer()); aspect->setSelected(true); } items = deselected.indexes(); for (int i = 0; i < items.size()/4; ++i) { index = items.at(i*4); aspect = static_cast(index.internalPointer()); aspect->setSelected(false); } items = m_treeView->selectionModel()->selectedRows(); QList selectedAspects; for (const QModelIndex& index : items) { aspect = static_cast(index.internalPointer()); selectedAspects<selectionModel()->selectedRows(); QList selectedAspects; for (const QModelIndex& index : items) { selectedAspects << static_cast(index.internalPointer()); } emit selectedAspectsChanged(selectedAspects); } void ProjectExplorer::expandSelected() { const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); for (const auto& index : items) m_treeView->setExpanded(index, true); } void ProjectExplorer::collapseSelected() { const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); for (const auto& index : items) m_treeView->setExpanded(index, false); } void ProjectExplorer::deleteSelected() { const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); if (!items.size()) return; int rc = KMessageBox::warningYesNo( this, i18np("Do you really want to delete the selected object?", "Do you really want to delete the selected %1 objects?", items.size()/4), i18np("Delete selected object", "Delete selected objects", items.size()/4)); if (rc == KMessageBox::No) return; m_project->beginMacro(i18np("Project Explorer: delete %1 selected object", "Project Explorer: delete %1 selected objects", items.size()/4)); for (int i = 0; i < items.size()/4; ++i) { const QModelIndex& index = items.at(i*4); auto* aspect = static_cast(index.internalPointer()); aspect->remove(); } m_project->endMacro(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## struct ViewState { Qt::WindowStates state; QRect geometry; }; /** * \brief Save the current state of the tree view * (expanded items and the currently selected item) as XML */ void ProjectExplorer::save(QXmlStreamWriter* writer) const { auto* model = qobject_cast(m_treeView->model()); QList selected; QList expanded; QList withView; QVector viewStates; int currentRow = -1; //row corresponding to the current index in the tree view, -1 for the root element (=project) QModelIndexList selectedRows = m_treeView->selectionModel()->selectedRows(); //check whether the project node itself is expanded if (m_treeView->isExpanded(m_treeView->model()->index(0,0))) expanded.push_back(-1); int row = 0; for (const auto* aspect : m_project->children(AspectType::AbstractAspect, AbstractAspect::Recursive)) { const QModelIndex& index = model->modelIndexOfAspect(aspect); const auto* part = dynamic_cast(aspect); if (part && part->hasMdiSubWindow()) { withView.push_back(row); ViewState s = {part->view()->windowState(), part->view()->geometry()}; viewStates.push_back(s); } if (model->rowCount(index)>0 && m_treeView->isExpanded(index)) expanded.push_back(row); if (selectedRows.indexOf(index) != -1) selected.push_back(row); if (index == m_treeView->currentIndex()) currentRow = row; row++; } writer->writeStartElement("state"); writer->writeStartElement("expanded"); for (const auto e : expanded) writer->writeTextElement("row", QString::number(e)); writer->writeEndElement(); writer->writeStartElement("selected"); for (const auto s : selected) writer->writeTextElement("row", QString::number(s)); writer->writeEndElement(); writer->writeStartElement("view"); for (int i = 0; i < withView.size(); ++i) { writer->writeStartElement("row"); const ViewState& s = viewStates.at(i); writer->writeAttribute( "state", QString::number(s.state) ); writer->writeAttribute( "x", QString::number(s.geometry.x()) ); writer->writeAttribute( "y", QString::number(s.geometry.y()) ); writer->writeAttribute( "width", QString::number(s.geometry.width()) ); writer->writeAttribute( "height", QString::number(s.geometry.height()) ); writer->writeCharacters(QString::number(withView.at(i))); writer->writeEndElement(); } writer->writeEndElement(); writer->writeStartElement("current"); writer->writeTextElement("row", QString::number(currentRow)); writer->writeEndElement(); writer->writeEndElement(); } /** * \brief Load from XML */ bool ProjectExplorer::load(XmlStreamReader* reader) { const AspectTreeModel* model = qobject_cast(m_treeView->model()); const auto aspects = m_project->children(AspectType::AbstractAspect, AbstractAspect::Recursive); bool expandedItem = false; bool selectedItem = false; bool viewItem = false; (void)viewItem; // because of a strange g++-warning about unused viewItem bool currentItem = false; QModelIndex currentIndex; QString str; int row; QVector selected; QList expanded; QXmlStreamAttributes attribs; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "state") break; if (!reader->isStartElement()) continue; if (reader->name() == "expanded") { expandedItem = true; selectedItem = false; viewItem = false; currentItem = false; } else if (reader->name() == "selected") { expandedItem = false; selectedItem = true; viewItem = false; currentItem = false; } else if (reader->name() == "view") { expandedItem = false; selectedItem = false; viewItem = true; currentItem = false; } else if (reader->name() == "current") { expandedItem = false; selectedItem = false; viewItem = false; currentItem = true; } else if (reader->name() == "row") { row = reader->readElementText().toInt(); QModelIndex index; if (row == -1) index = model->modelIndexOfAspect(m_project); //-1 corresponds to the project-item (s.a. ProjectExplorer::save()) else if (row >= aspects.size() || row < 0 /* checking for <0 to protect against wrong values in the XML */) continue; else index = model->modelIndexOfAspect(aspects.at(row)); if (expandedItem) expanded.push_back(index); else if (selectedItem) selected.push_back(index); else if (currentItem) currentIndex = index; else if (viewItem) { auto* part = dynamic_cast(aspects.at(row)); if (!part) continue; //TODO: add error/warning message here? emit currentAspectChanged(part); attribs = reader->attributes(); str = attribs.value("state").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("state").toString()); else { part->view()->setWindowState(Qt::WindowStates(str.toInt())); part->mdiSubWindow()->setWindowState(Qt::WindowStates(str.toInt())); } if (str != "0") continue; //no geometry settings required for maximized/minimized windows QRect geometry; str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else geometry.setX(str.toInt()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else geometry.setY(str.toInt()); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else geometry.setWidth(str.toInt()); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else geometry.setHeight(str.toInt()); part->mdiSubWindow()->setGeometry(geometry); } } } for (const auto& index : expanded) { m_treeView->setExpanded(index, true); collapseParents(index, expanded);//collapse all parent indices if they are not expanded } for (const auto& index : selected) m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); m_treeView->setCurrentIndex(currentIndex); m_treeView->scrollTo(currentIndex); //when setting the current index above it gets expanded, collapse all parent indices if they are were not expanded when saved collapseParents(currentIndex, expanded); return true; } void ProjectExplorer::collapseParents(const QModelIndex& index, const QList& expanded) { //root index doesn't have any parents - this case is not caught by the second if-statement below if (index.column() == 0 && index.row() == 0) return; const QModelIndex parent = index.parent(); if (parent == QModelIndex()) return; if (expanded.indexOf(parent) == -1) m_treeView->collapse(parent); } diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp index 91729d26c..59d66cc0c 100644 --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -1,491 +1,496 @@ /*************************************************************************** File : ImportDialog.cc Project : LabPlot Description : import file data dialog -------------------------------------------------------------------- Copyright : (C) 2008-2019 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/filters.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" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #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, liveDataSource, fileName)) { 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(); //Signals/Slots connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); if (!liveDataSource) setWindowTitle(i18nc("@title:window", "Import Data to Spreadsheet or Matrix")); else setWindowTitle(i18nc("@title:window", "Add New Live Data Source")); setWindowIcon(QIcon::fromTheme("document-import-database")); //restore saved settings if available create(); // ensure there's a window created QApplication::processEvents(QEventLoop::AllEvents, 0); m_importFileWidget->loadSettings(); KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); if (conf.exists()) { m_showOptions = conf.readEntry("ShowOptions", false); KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(0, 0).expandedTo(minimumSize())); m_importFileWidget->showOptions(m_showOptions); //do the signal-slot connections after all settings were loaded in import file widget and check the OK button after this connect(m_importFileWidget, &ImportFileWidget::checkedFitsTableToMatrix, this, &ImportFileDialog::checkOnFitsTableToMatrix); connect(m_importFileWidget, static_cast(&ImportFileWidget::fileNameChanged), this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, static_cast(&ImportFileWidget::sourceTypeChanged), this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, &ImportFileWidget::hostChanged, this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, &ImportFileWidget::portChanged, this, &ImportFileDialog::checkOkButton); //TODO: do we really need to check the ok button when the preview was refreshed? //If not, remove this together with the previewRefreshed signal in ImportFileWidget //connect(m_importFileWidget, &ImportFileWidget::previewRefreshed, this, &ImportFileDialog::checkOkButton); #ifdef HAVE_MQTT connect(m_importFileWidget, &ImportFileWidget::subscriptionsChanged, this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, &ImportFileWidget::checkFileType, this, &ImportFileDialog::checkOkButton); #endif m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); connect(m_optionsButton, &QPushButton::clicked, this, &ImportFileDialog::toggleOptions); checkOkButton(); } 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); } int ImportFileDialog::sourceType() const { return static_cast(m_importFileWidget->currentSourceType()); } /*! triggers data import to the live data source \c source */ void ImportFileDialog::importToLiveDataSource(LiveDataSource* source, QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importToLiveDataSource()"); m_importFileWidget->saveSettings(source); //show a progress bar in the status bar auto* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(source->filter(), &AbstractFileFilter::completed, progressBar, &QProgressBar::setValue); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QTime timer; timer.start(); DEBUG(" Initial read()"); source->read(); statusBar->showMessage( i18n("Live data source created in %1 seconds.", (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); } #ifdef HAVE_MQTT /*! triggers data import to the MQTTClient \c client */ void ImportFileDialog::importToMQTT(MQTTClient* client) const{ m_importFileWidget->saveMQTTSettings(client); client->read(); client->ready(); } #endif /*! 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(nullptr, i18n("No data to import."), i18n("No Data")); return; } QString fileName = m_importFileWidget->fileName(); auto filter = m_importFileWidget->currentFileFilter(); auto mode = AbstractFileFilter::ImportMode(cbPosition->currentIndex()); //show a progress bar in the status bar auto* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(filter, &AbstractFileFilter::completed, progressBar, &QProgressBar::setValue); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 100); QTime timer; timer.start(); if (aspect->inherits(AspectType::Matrix)) { DEBUG("ImportFileDialog::importTo(): to Matrix"); auto* matrix = qobject_cast(aspect); filter->readDataFromFile(fileName, matrix, mode); } else if (aspect->inherits(AspectType::Spreadsheet)) { DEBUG("ImportFileDialog::importTo(): to Spreadsheet"); auto* spreadsheet = qobject_cast(aspect); DEBUG(" Calling filter->readDataFromFile() with spreadsheet " << spreadsheet); filter->readDataFromFile(fileName, spreadsheet, mode); } else if (aspect->inherits(AspectType::Workbook)) { DEBUG("ImportFileDialog::importTo(): to Workbook"); auto* workbook = qobject_cast(aspect); + workbook->setUndoAware(false); QVector sheets = workbook->children(); AbstractFileFilter::FileType fileType = m_importFileWidget->currentFileType(); // multiple data sets/variables for HDF5, NetCDF and ROOT if (fileType == AbstractFileFilter::HDF5 || fileType == AbstractFileFilter::NETCDF || fileType == AbstractFileFilter::ROOT) { QStringList names; if (fileType == AbstractFileFilter::HDF5) names = m_importFileWidget->selectedHDF5Names(); else if (fileType == AbstractFileFilter::NETCDF) names = m_importFileWidget->selectedNetCDFNames(); else names = m_importFileWidget->selectedROOTNames(); int nrNames = names.size(), offset = sheets.size(); //TODO: think about importing multiple sets into one sheet int start = 0; // add nrNames sheets (0 to nrNames) if (mode == AbstractFileFilter::Replace) // add only missing sheets (from offset to nrNames) start = offset; - // add additional sheets + // add additional spreadsheets for (int i = start; i < nrNames; ++i) { - Spreadsheet *spreadsheet = new Spreadsheet(i18n("Spreadsheet")); - if (mode == AbstractFileFilter::Prepend) + QString sheetName = names.at(i); + + //HDF5 variable names containt the whole path, remove it and keep the name only + if (fileType == AbstractFileFilter::HDF5) + sheetName = names[i].mid(names[i].lastIndexOf("/") + 1); + + auto* spreadsheet = new Spreadsheet(sheetName); + if (mode == AbstractFileFilter::Prepend && !sheets.isEmpty()) workbook->insertChildBefore(spreadsheet, sheets[0]); else - workbook->addChild(spreadsheet); + workbook->addChildFast(spreadsheet); } // start at offset for append, else at 0 if (mode != AbstractFileFilter::Append) offset = 0; // import all sets to a different sheet sheets = workbook->children(); for (int i = 0; i < nrNames; ++i) { if (fileType == AbstractFileFilter::HDF5) static_cast(filter)->setCurrentDataSetName(names[i]); else if (fileType == AbstractFileFilter::NETCDF) static_cast(filter)->setCurrentVarName(names[i]); else static_cast(filter)->setCurrentObject(names[i]); int index = i + offset; - if (sheets[index]->inherits(AspectType::Matrix)) - filter->readDataFromFile(fileName, qobject_cast(sheets[index])); - else if (sheets[index]->inherits(AspectType::Spreadsheet)) - filter->readDataFromFile(fileName, qobject_cast(sheets[index])); + filter->readDataFromFile(fileName, qobject_cast(sheets[index])); } + + workbook->setUndoAware(true); } 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); + auto* sheet = workbook->currentSpreadsheet(); + if (sheet) + filter->readDataFromFile(fileName, sheet, mode); else { - spreadsheet = new Spreadsheet(i18n("Spreadsheet")); + workbook->setUndoAware(true); + auto* spreadsheet = new Spreadsheet(fileName); workbook->addChild(spreadsheet); + workbook->setUndoAware(false); filter->readDataFromFile(fileName, spreadsheet, mode); } } } statusBar->showMessage(i18n("File %1 imported in %2 seconds.", fileName, (float)timer.elapsed()/1000)); RESET_CURSOR; statusBar->removeWidget(progressBar); } 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(AspectType::Matrix)) { okButton->setEnabled(enable); if (enable) okButton->setToolTip(i18n("Close the dialog and import the data.")); else okButton->setToolTip(i18n("Cannot import into a matrix since the data contains non-numerical data.")); } } } 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); return; } else { 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 auto* matrix = dynamic_cast(aspect); m_importFileWidget->showAsciiHeaderOptions(matrix == nullptr); } } QString fileName = m_importFileWidget->fileName(); #ifndef HAVE_WINDOWS if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, m_importFileWidget->currentSourceType())); switch (m_importFileWidget->currentSourceType()) { case LiveDataSource::SourceType::FileOrPipe: { DEBUG(" fileName = " << fileName.toUtf8().constData()); 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.")); break; } case LiveDataSource::SourceType::LocalSocket: { const bool enable = QFile::exists(fileName); if (enable) { QLocalSocket lsocket{this}; DEBUG("CONNECT"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { // this is required for server that send data as soon as connected lsocket.waitForReadyRead(); DEBUG("DISCONNECT"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); } else { DEBUG("failed connect to local socket - " << lsocket.errorString().toStdString()); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided local socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Selected local socket does not exist.")); } break; } case LiveDataSource::SourceType::NetworkTcpSocket: { const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QTcpSocket socket(this); socket.connectToHost(m_importFileWidget->host(), m_importFileWidget->port().toUShort(), QTcpSocket::ReadOnly); if (socket.waitForConnected()) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); socket.disconnectFromHost(); } else { DEBUG("failed to connect to TCP socket - " << socket.errorString().toStdString()); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided TCP socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either the host name or the port number is missing.")); } break; } case LiveDataSource::SourceType::NetworkUdpSocket: { const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QUdpSocket socket(this); socket.bind(QHostAddress(m_importFileWidget->host()), m_importFileWidget->port().toUShort()); socket.connectToHost(m_importFileWidget->host(), 0, QUdpSocket::ReadOnly); if (socket.waitForConnected()) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); socket.disconnectFromHost(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else { DEBUG("failed to connect to UDP socket - " << socket.errorString().toStdString()); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided UDP socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either the host name or the port number is missing.")); } break; } case LiveDataSource::SourceType::SerialPort: { const QString sPort = m_importFileWidget->serialPort(); const int baudRate = m_importFileWidget->baudRate(); if (!sPort.isEmpty()) { QSerialPort serialPort{this}; DEBUG(" Port: " << sPort.toStdString() << ", Settings: " << baudRate << ',' << serialPort.dataBits() << ',' << serialPort.parity() << ',' << serialPort.stopBits()); serialPort.setPortName(sPort); serialPort.setBaudRate(baudRate); const bool serialPortOpened = serialPort.open(QIODevice::ReadOnly); okButton->setEnabled(serialPortOpened); if (serialPortOpened) { okButton->setToolTip(i18n("Close the dialog and import the data.")); serialPort.close(); } else { DEBUG("Could not connect to the provided serial port"); okButton->setToolTip(i18n("Could not connect to the provided serial port.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Serial port number is missing.")); } break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT const bool enable = m_importFileWidget->isMqttValid(); if (enable) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either there is no connection, or no subscriptions were made, or the file filter is not ASCII.")); } #endif break; } } } QString ImportFileDialog::selectedObject() const { return m_importFileWidget->selectedObject(); } diff --git a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp index a92b58dff..e15a9491f 100644 --- a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp +++ b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp @@ -1,709 +1,721 @@ /*************************************************************************** File : PlotDataDialog.cpp Project : LabPlot Description : Dialog for generating plots for the spreadsheet data -------------------------------------------------------------------- Copyright : (C) 2017-2019 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/datapicker/DatapickerCurve.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/XYAnalysisCurve.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" #ifdef HAVE_MQTT #include "backend/datasources/MQTTTopic.h" #endif #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, PlotType type, QWidget* parent) : QDialog(parent), ui(new Ui::PlotDataWidget()), m_spreadsheet(s), m_plotsModel(new AspectTreeModel(m_spreadsheet->project())), m_worksheetsModel(new AspectTreeModel(m_spreadsheet->project())), m_plotType(type) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(i18nc("@title:window", "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")); auto* layout = new QVBoxLayout(this); layout->addWidget(mainWidget); layout->addWidget(buttonBox); setLayout(layout); //create combox boxes for the existing plots and worksheets auto* 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{AspectType::Folder, AspectType::Worksheet, AspectType::CartesianPlot}; cbExistingPlots->setTopLevelClasses(list); list = {AspectType::CartesianPlot}; m_plotsModel->setSelectableAspects(list); cbExistingPlots->setModel(m_plotsModel); //select the first available plot, if available auto plots = m_spreadsheet->project()->children(AbstractAspect::Recursive); if (!plots.isEmpty()) { const auto* plot = plots.first(); cbExistingPlots->setCurrentModelIndex(m_plotsModel->modelIndexOfAspect(plot)); } list = {AspectType::Folder, AspectType::Worksheet}; cbExistingWorksheets->setTopLevelClasses(list); list = {AspectType::Worksheet}; m_worksheetsModel->setSelectableAspects(list); cbExistingWorksheets->setModel(m_worksheetsModel); //select the first available worksheet, if available auto worksheets = m_spreadsheet->project()->children(AbstractAspect::Recursive); if (!worksheets.isEmpty()) { const auto* worksheet = worksheets.first(); cbExistingWorksheets->setCurrentModelIndex(m_worksheetsModel->modelIndexOfAspect(worksheet)); } //in the grid layout of the scroll area we have on default one row for the x-column, //one row for the separating line and one line for the y-column. //set the height of this default conente as the minimal size of the scroll area. gridLayout = dynamic_cast(ui->scrollAreaColumns->widget()->layout()); int height = 2*ui->cbXColumn->height() + ui->line->height() + 2*gridLayout->verticalSpacing() + gridLayout->contentsMargins().top() + gridLayout->contentsMargins().bottom(); ui->scrollAreaColumns->setMinimumSize(0, height); //hide the check box for creation of original data, only shown if analysis curves are to be created ui->spacer->changeSize(0, 0); ui->chkCreateDataCurve->hide(); //SIGNALs/SLOTs connect(buttonBox, &QDialogButtonBox::accepted, this, [=]() { hide(); 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); //restore saved settings if available create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "PlotDataDialog"); if (conf.exists()) { 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); KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } 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->spacer->changeSize(0, 40); ui->chkCreateDataCurve->show(); } void PlotDataDialog::processColumns() { //columns to plot auto* view = reinterpret_cast(m_spreadsheet->view()); QVector selectedColumns = view->selectedColumns(true); //use all spreadsheet columns if no columns are selected if (selectedColumns.isEmpty()) selectedColumns = m_spreadsheet->children(); //skip error and non-plottable columns for (Column* col : selectedColumns) { if ((col->plotDesignation() == AbstractColumn::X || col->plotDesignation() == AbstractColumn::Y || col->plotDesignation() == AbstractColumn::NoDesignation) && col->isPlottable()) m_columns << col; } //disable everything if the spreadsheet doesn't have any columns to plot if (m_columns.isEmpty()) { ui->gbCurvePlacement->setEnabled(false); ui->gbPlotPlacement->setEnabled(false); return; } //determine the column names //and the name of the first column having "X" as the plot designation (relevant for xy-curves only) QStringList columnNames; QString xColumnName; for (const Column* column : m_columns) { columnNames << column->name(); if (m_plotType == PlotXYCurve && xColumnName.isEmpty() && column->plotDesignation() == AbstractColumn::X) xColumnName = column->name(); } if (m_plotType == PlotXYCurve && xColumnName.isEmpty()) { //no X-column was selected -> look for the first non-selected X-column left to the first selected column const int index = m_spreadsheet->indexOfChild(selectedColumns.first()) - 1; if (index >= 0) { for (int i = index; i >= 0; --i) { Column* column = m_spreadsheet->column(i); if (column->plotDesignation() == AbstractColumn::X) { xColumnName = column->name(); m_columns.prepend(column); columnNames.prepend(xColumnName); break; } } } } switch (m_plotType) { case PlotXYCurve: processColumnsForXYCurve(columnNames, xColumnName); break; case PlotHistogram: processColumnsForHistogram(columnNames); break; } //resize the scroll area to show five ComboBoxes at maximum without showing the scroll bars int size = m_columnComboBoxes.size() >=5 ? 5 : m_columnComboBoxes.size(); int height = size * ui->cbXColumn->height(); QGridLayout* layout = dynamic_cast(ui->scrollAreaColumns->widget()->layout()); if (layout) height += (3 + (size - 1))*layout->verticalSpacing(); ui->scrollAreaColumns->setMinimumSize(ui->scrollAreaColumns->width(), height); } void PlotDataDialog::processColumnsForXYCurve(const QStringList& columnNames, const QString& xColumnName) { 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) { auto* gridLayout = dynamic_cast(ui->scrollAreaColumns->widget()->layout()); for (int i = 2; i < m_columns.size(); ++i) { QLabel* label = new QLabel(i18n("Y-data")); auto* 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")); } //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::processColumnsForHistogram(const QStringList& columnNames) { ui->gbData->setTitle(i18n("Histogram Data")); ui->line->hide(); ui->spacer->changeSize(0, 0); ui->chkCreateDataCurve->hide(); //use the already available cbXColumn combo box ui->lXColumn->setText(i18n("Data")); m_columnComboBoxes << ui->cbXColumn; ui->cbXColumn->addItems(columnNames); ui->cbXColumn->setCurrentIndex(0); if (m_columns.size() == 1) { //one column provided, only one histogram is possible //-> hide the curve placement options and the scroll areas for further columns ui->rbCurvePlacement1->setChecked(true); ui->gbCurvePlacement->hide(); ui->gbPlotPlacement->setTitle(i18n("Add Histogram to")); ui->scrollAreaColumns->hide(); } else { ui->gbCurvePlacement->setTitle(i18n("Histogram Placement")); ui->rbCurvePlacement1->setText(i18n("All histograms in one plot")); ui->rbCurvePlacement2->setText(i18n("One plot per histogram")); ui->gbPlotPlacement->setTitle(i18n("Add Histograms to")); //use the already available cbYColumn combo box ui->lYColumn->setText(i18n("Data")); m_columnComboBoxes << ui->cbYColumn; ui->cbYColumn->addItems(columnNames); ui->cbYColumn->setCurrentIndex(1); //add a ComboBox for every further column to be plotted auto* gridLayout = dynamic_cast(ui->scrollAreaColumns->widget()->layout()); for (int i = 2; i < m_columns.size(); ++i) { auto* label = new QLabel(i18n("Data")); auto* comboBox = new QComboBox(); gridLayout->addWidget(label, i+1, 0, 1, 1); gridLayout->addWidget(comboBox, i+1, 2, 1, 1); comboBox->addItems(columnNames); comboBox->setCurrentIndex(i); m_columnComboBoxes << comboBox; } } } void PlotDataDialog::plot() { DEBUG("PlotDataDialog::plot()"); WAIT_CURSOR; + m_spreadsheet->project()->setSuppressAspectAddedSignal(true); + m_lastAddedCurve = nullptr; + if (ui->rbPlotPlacement1->isChecked()) { //add curves to an existing plot auto* aspect = static_cast(cbExistingPlots->currentModelIndex().internalPointer()); auto* 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 auto* aspect = static_cast(cbExistingWorksheets->currentModelIndex().internalPointer()); auto* 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 before 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 AbstractAspect* parent = m_spreadsheet->parentAspect(); if (dynamic_cast(parent)) parent = parent->parentAspect()->parentAspect(); #ifdef HAVE_MQTT else if (dynamic_cast(m_spreadsheet)) parent = m_spreadsheet->project(); #endif parent->beginMacro( i18n("Plot data from %1", m_spreadsheet->name()) ); Worksheet* worksheet = new Worksheet(i18n("Plot data from %1", m_spreadsheet->name())); parent->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 before 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); } parent->endMacro(); } + + //select the parent plot of the last added curve in the project explorer + m_spreadsheet->project()->setSuppressAspectAddedSignal(false); + if (m_lastAddedCurve) + m_spreadsheet->project()->requestNavigateTo(m_lastAddedCurve->parentAspect()->path()); RESET_CURSOR; } Column* PlotDataDialog::columnFromName(const QString& name) const { for (auto* column : m_columns) { if (column->name() == name) return column; } return nullptr; } /*! * * for the selected columns in this dialog, creates a curve in the already existing plot \c plot. */ -void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) const { +void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) { QApplication::processEvents(QEventLoop::AllEvents, 100); switch (m_plotType) { case PlotXYCurve: { Column* xColumn = columnFromName(ui->cbXColumn->currentText()); for (auto* comboBox : m_columnComboBoxes) { const QString& name = comboBox->currentText(); Column* yColumn = columnFromName(name); if (yColumn == xColumn) continue; addCurve(name, xColumn, yColumn, plot); } break; } case PlotHistogram: { for (auto* comboBox : m_columnComboBoxes) { const QString& name = comboBox->currentText(); Column* column = columnFromName(name); addHistogram(name, column, plot); } break; } } 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 { +void PlotDataDialog::addCurvesToPlots(Worksheet* worksheet) { QApplication::processEvents(QEventLoop::AllEvents, 100); worksheet->setSuppressLayoutUpdate(true); switch (m_plotType) { case PlotXYCurve: { const QString& xColumnName = ui->cbXColumn->currentText(); Column* xColumn = columnFromName(xColumnName); for (auto* comboBox : m_columnComboBoxes) { const QString& name = comboBox->currentText(); Column* yColumn = columnFromName(name); if (yColumn == xColumn) continue; 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(); } break; } case PlotHistogram: { for (auto* comboBox : m_columnComboBoxes) { const QString& name = comboBox->currentText(); Column* column = 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; for (auto* axis : plot->children()) { if (axis->orientation() == Axis::AxisHorizontal && !xSet) { axis->title()->setText(name); xSet = true; } } worksheet->addChild(plot); addHistogram(name, column, 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 { +void PlotDataDialog::addCurve(const QString& name, Column* xColumn, Column* yColumn, CartesianPlot* plot) { DEBUG("PlotDataDialog::addCurve()"); if (!m_analysisMode) { auto* curve = new XYCurve(name); curve->suppressRetransform(true); curve->setXColumn(xColumn); curve->setYColumn(yColumn); curve->suppressRetransform(false); plot->addChild(curve); + m_lastAddedCurve = curve; } else { bool createDataCurve = ui->chkCreateDataCurve->isChecked(); XYCurve* curve = nullptr; if (createDataCurve) { curve = new XYCurve(name); curve->suppressRetransform(true); curve->setXColumn(xColumn); curve->setYColumn(yColumn); curve->suppressRetransform(false); plot->addChild(curve); + m_lastAddedCurve = curve; } XYAnalysisCurve* analysisCurve = nullptr; switch (m_analysisAction) { case DataReduction: analysisCurve = new XYDataReductionCurve(i18n("Reduction of '%1'", name)); break; case Differentiation: analysisCurve = new XYDifferentiationCurve(i18n("Derivative of '%1'", name)); break; case Integration: analysisCurve = new XYIntegrationCurve(i18n("Integral of '%1'", name)); break; case Interpolation: analysisCurve = new XYInterpolationCurve(i18n("Interpolation of '%1'", name)); break; case Smoothing: analysisCurve = new XYSmoothCurve(i18n("Smoothing of '%1'", name)); break; case FitLinear: case FitPower: case FitExp1: case FitExp2: case FitInvExp: case FitGauss: case FitCauchyLorentz: case FitTan: case FitTanh: case FitErrFunc: case FitCustom: analysisCurve = new XYFitCurve(i18n("Fit to '%1'", name)); static_cast(analysisCurve)->initFitData(m_analysisAction); static_cast(analysisCurve)->initStartValues(curve); break; case FourierFilter: analysisCurve = new XYFourierFilterCurve(i18n("Fourier Filter of '%1'", name)); break; } if (analysisCurve != nullptr) { analysisCurve->suppressRetransform(true); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); if (m_analysisAction != FitCustom) //no custom fit-model set yet, no need to recalculate analysisCurve->recalculate(); analysisCurve->suppressRetransform(false); plot->addChild(analysisCurve); + m_lastAddedCurve = analysisCurve; } } } -void PlotDataDialog::addHistogram(const QString& name, Column* column, CartesianPlot* plot) const { +void PlotDataDialog::addHistogram(const QString& name, Column* column, CartesianPlot* plot) { auto* hist = new Histogram(name); plot->addChild(hist); // hist->suppressRetransform(true); hist->setDataColumn(column); // hist->suppressRetransform(false); + m_lastAddedCurve = hist; } //################################################################ //########################## 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 ( (m_plotType == PlotXYCurve && (ui->cbXColumn->currentIndex() == -1 || ui->cbYColumn->currentIndex() == -1)) || (m_plotType == PlotHistogram && ui->cbXColumn->currentIndex() == -1) ) msg = i18n("No data selected to plot."); else if (ui->rbPlotPlacement1->isChecked()) { AbstractAspect* aspect = static_cast(cbExistingPlots->currentModelIndex().internalPointer()); enable = (aspect != nullptr); 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 != nullptr); 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); } diff --git a/src/kdefrontend/spreadsheet/PlotDataDialog.h b/src/kdefrontend/spreadsheet/PlotDataDialog.h index 584dcd244..c9e6cc462 100644 --- a/src/kdefrontend/spreadsheet/PlotDataDialog.h +++ b/src/kdefrontend/spreadsheet/PlotDataDialog.h @@ -1,92 +1,94 @@ /*************************************************************************** File : PlotDataDialog.h Project : LabPlot Description : Dialog for generating plots for the spreadsheet data -------------------------------------------------------------------- - Copyright : (C) 2017 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2017-2019 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef PLOTDATADIALOG_H #define PLOTDATADIALOG_H namespace Ui { class PlotDataWidget; } #include class QComboBox; +class AbstractAspect; class AspectTreeModel; class CartesianPlot; class Column; class Spreadsheet; class TreeViewComboBox; class Worksheet; class PlotDataDialog : public QDialog { Q_OBJECT public: enum PlotType {PlotXYCurve, PlotHistogram}; enum AnalysisAction {DataReduction, Differentiation, Integration, Interpolation, Smoothing, FitLinear, FitPower, FitExp1, FitExp2, FitInvExp, FitGauss, FitCauchyLorentz, FitTan, FitTanh, FitErrFunc, FitCustom, FourierFilter}; explicit PlotDataDialog(Spreadsheet*, PlotType = PlotXYCurve, QWidget* parent = nullptr); ~PlotDataDialog() override; void setAnalysisAction(AnalysisAction); private: Ui::PlotDataWidget* ui; QPushButton* m_okButton; Spreadsheet* m_spreadsheet; TreeViewComboBox* cbExistingPlots; TreeViewComboBox* cbExistingWorksheets; QVector m_columns; QVector m_columnComboBoxes; AspectTreeModel* m_plotsModel; AspectTreeModel* m_worksheetsModel; PlotType m_plotType; AnalysisAction m_analysisAction{Differentiation}; bool m_analysisMode{false}; + AbstractAspect* m_lastAddedCurve{nullptr}; void processColumns(); void processColumnsForXYCurve(const QStringList& columnNames, const QString& xColumnName); void processColumnsForHistogram(const QStringList&); - void addCurvesToPlot(CartesianPlot*) const; - void addCurvesToPlots(Worksheet*) const; - void addCurve(const QString& name, Column* xColumn, Column* yColumn, CartesianPlot*) const; - void addHistogram(const QString& name, Column* column, CartesianPlot*) const; + void addCurvesToPlot(CartesianPlot*); + void addCurvesToPlots(Worksheet*); + void addCurve(const QString& name, Column* xColumn, Column* yColumn, CartesianPlot*); + void addHistogram(const QString& name, Column* column, CartesianPlot*); Column* columnFromName(const QString&) const; protected slots: virtual void checkOkButton(); private slots: void plot(); void curvePlacementChanged(); void plotPlacementChanged(); }; #endif diff --git a/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp b/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp index 3c4b58736..8ace0a2b8 100644 --- a/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp +++ b/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp @@ -1,266 +1,267 @@ /*************************************************************************** File : ExportWorksheetDialog.cpp Project : LabPlot Description : export worksheet dialog -------------------------------------------------------------------- Copyright : (C) 2011-2019 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 "ExportWorksheetDialog.h" #include "ui_exportworksheetwidget.h" #include #include #include #include #include #include #include #include #include #include /*! \class ExportWorksheetDialog \brief Dialog for exporting a worksheet to a file. \ingroup kdefrontend */ ExportWorksheetDialog::ExportWorksheetDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ExportWorksheetWidget()) { ui->setupUi(this); QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_showOptionsButton = new QPushButton; connect(btnBox, &QDialogButtonBox::clicked, this, &ExportWorksheetDialog::slotButtonClicked); btnBox->addButton(m_showOptionsButton, QDialogButtonBox::ActionRole); ui->verticalLayout->addWidget(btnBox); m_okButton = btnBox->button(QDialogButtonBox::Ok); m_cancelButton = btnBox->button(QDialogButtonBox::Cancel); ui->leFileName->setCompleter(new QCompleter(new QDirModel, this)); ui->bOpen->setIcon(QIcon::fromTheme(QLatin1String("document-open"))); ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("application-pdf")), QLatin1String("Portable Data Format (PDF)")); ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-svg+xml")), QLatin1String("Scalable Vector Graphics (SVG)")); ui->cbFormat->insertSeparator(3); ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-x-generic")), QLatin1String("Portable Network Graphics (PNG)")); ui->cbExportArea->addItem(i18n("Object's bounding box")); ui->cbExportArea->addItem(i18n("Current selection")); ui->cbExportArea->addItem(i18n("Complete worksheet")); ui->cbResolution->addItem(i18nc("%1 is the value of DPI of the current screen", "%1 (desktop)", QString::number(QApplication::desktop()->physicalDpiX()))); ui->cbResolution->addItem(QLatin1String("100")); ui->cbResolution->addItem(QLatin1String("150")); ui->cbResolution->addItem(QLatin1String("200")); ui->cbResolution->addItem(QLatin1String("300")); ui->cbResolution->addItem(QLatin1String("600")); ui->cbResolution->setValidator(new QIntValidator(ui->cbResolution)); connect(ui->cbFormat, static_cast(&KComboBox::currentIndexChanged), this, &ExportWorksheetDialog::formatChanged ); connect(ui->bOpen, &QPushButton::clicked, this, &ExportWorksheetDialog::selectFile); connect(ui->leFileName, &QLineEdit::textChanged, this, &ExportWorksheetDialog::fileNameChanged); connect(m_showOptionsButton, &QPushButton::clicked, this, &ExportWorksheetDialog::toggleOptions); ui->leFileName->setFocus(); setWindowTitle(i18nc("@title:window", "Export Worksheet")); setWindowIcon(QIcon::fromTheme(QLatin1String("document-export-database"))); //restore saved settings if available KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); ui->cbFormat->setCurrentIndex(conf.readEntry("Format", 0)); ui->cbExportArea->setCurrentIndex(conf.readEntry("Area", 0)); ui->chkExportBackground->setChecked(conf.readEntry("Background", true)); ui->cbResolution->setCurrentIndex(conf.readEntry("Resolution", 0)); m_showOptions = conf.readEntry("ShowOptions", true); m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : m_showOptionsButton->setText(i18n("Show Options")); ui->gbOptions->setVisible(m_showOptions); create(); // ensure there's a window created if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(0, 0).expandedTo(minimumSize())); } ExportWorksheetDialog::~ExportWorksheetDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); conf.writeEntry("Format", ui->cbFormat->currentIndex()); conf.writeEntry("Area", ui->cbExportArea->currentIndex()); conf.writeEntry("Background", ui->chkExportBackground->isChecked()); conf.writeEntry("Resolution", ui->cbResolution->currentIndex()); conf.writeEntry("ShowOptions", m_showOptions); KWindowConfig::saveWindowSize(windowHandle(), conf); } void ExportWorksheetDialog::setFileName(const QString& name) { KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); QString dir = conf.readEntry("LastDir", ""); if (dir.isEmpty()) dir = QDir::homePath(); ui->leFileName->setText(dir + QDir::separator() + name); this->formatChanged(ui->cbFormat->currentIndex()); } QString ExportWorksheetDialog::path() const { return ui->leFileName->text(); } WorksheetView::ExportFormat ExportWorksheetDialog::exportFormat() const { int index = ui->cbFormat->currentIndex(); //we have a separator in the format combobox at the 3th position -> skip it if (index > 2) index--; return WorksheetView::ExportFormat(index); } WorksheetView::ExportArea ExportWorksheetDialog::exportArea() const { return WorksheetView::ExportArea(ui->cbExportArea->currentIndex()); } bool ExportWorksheetDialog::exportBackground() const { return ui->chkExportBackground->isChecked(); } int ExportWorksheetDialog::exportResolution() const { if (ui->cbResolution->currentIndex() == 0) return QApplication::desktop()->physicalDpiX(); else return ui->cbResolution->currentText().toInt(); } void ExportWorksheetDialog::slotButtonClicked(QAbstractButton* button) { if (button == m_okButton) okClicked(); else if (button == m_cancelButton) { reject(); } } //SLOTS void ExportWorksheetDialog::okClicked() { if ( QFile::exists(ui->leFileName->text()) ) { int r = KMessageBox::questionYesNo(this, i18n("The file already exists. Do you really want to overwrite it?"), i18n("Export")); if (r == KMessageBox::No) return; } KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); conf.writeEntry("Format", ui->cbFormat->currentIndex()); conf.writeEntry("Area", ui->cbExportArea->currentIndex()); conf.writeEntry("Resolution", ui->cbResolution->currentIndex()); QString path = ui->leFileName->text(); if (!path.isEmpty()) { QString dir = conf.readEntry("LastDir", ""); ui->leFileName->setText(path); int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastDir", newDir); } } accept(); } /*! Shows/hides the GroupBox with export options in this dialog. */ void ExportWorksheetDialog::toggleOptions() { m_showOptions = !m_showOptions; ui->gbOptions->setVisible(m_showOptions); m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : m_showOptionsButton->setText(i18n("Show Options")); //resize the dialog resize(layout()->minimumSize()); layout()->activate(); resize( QSize(this->width(),0).expandedTo(minimumSize()) ); } /*! opens a file dialog and lets the user select the file. */ void ExportWorksheetDialog::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); const QString dir = conf.readEntry("LastDir", ""); QString format; if (ui->cbFormat->currentIndex() == 0) format = i18n("Portable Data Format (PDF) (*.pdf *.PDF)"); else if (ui->cbFormat->currentIndex() == 1) format = i18n("Scalable Vector Graphics (SVG) (*.svg *.SVG)"); else format = i18n("Portable Network Graphics (PNG) (*.png *.PNG)"); const QString path = QFileDialog::getSaveFileName(this, i18n("Export to file"), dir, format); if (!path.isEmpty()) { ui->leFileName->setText(path); int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastDir", newDir); } } } /*! called when the output format was changed. Adjusts the extension for the specified file. */ void ExportWorksheetDialog::formatChanged(int index) { //we have a separator in the format combobox at the 3rd position -> skip it if (index > 2) index --; QStringList extensions; extensions << QLatin1String(".pdf") << QLatin1String(".svg") << QLatin1String(".png"); QString path = ui->leFileName->text(); int i = path.indexOf(QLatin1Char('.')); if (i == -1) path = path + extensions.at(index); else path = path.left(i) + extensions.at(index); ui->leFileName->setText(path); // show resolution option for png format - ui->lResolution->setVisible(index == 3); - ui->cbResolution->setVisible(index == 3); + bool visible = (index == 2); + ui->lResolution->setVisible(visible); + ui->cbResolution->setVisible(visible); } void ExportWorksheetDialog::fileNameChanged(const QString& name) { m_okButton->setEnabled(!name.simplified().isEmpty()); }