diff --git a/ChangeLog b/ChangeLog index f5624338c..ea92631cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,269 +1,271 @@ -----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) * 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 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 * [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/admin/create-dmg.sh b/admin/create-dmg.sh index 15f18e1e4..bb0674e12 100755 --- a/admin/create-dmg.sh +++ b/admin/create-dmg.sh @@ -1,99 +1,101 @@ # Build it on Mac howto in script form # but be aware, some frameworks need patching to have this working # reference: https://cgit.kde.org/kate.git/tree/mac/emerge-deploy.sh # run in kde/.. # errors are fatal set -e NAME=labplot2 PNAME=LabPlot2 VERSION=2.7.0 PREFIX=kde/Applications INPREFIX=$PREFIX/$PNAME.app/Contents TMPDIR=LabPlot2 SIGNATURE="Stefan Gerlach" GCP=/opt/local/libexec/gnubin/cp ######################################### echo "CLEAN UP" rm -rf $PREFIX/$PNAME.app mkdir -pv $INPREFIX/{Frameworks,Resources,MacOS,PlugIns/iconengines,share/appdata,share/applications} # application cp -v labplot/build/src/$NAME.app/Contents/MacOS/$NAME $INPREFIX/MacOS echo "Running macdeployqt ..." # -verbose=3 macdeployqt $PREFIX/$PNAME.app -verbose=2 ######################################### echo "install files" # splash cp -v kde/share/$NAME/splash.png $INPREFIX/Resources/ # rc-file # Standardlocation (QSP): ~/Library/Application\ Support/kxmlgui5/labplot2/labplot2ui.rc # using hardcoded path: cp -v kde/share/kxmlgui5/$NAME/${NAME}ui.rc $INPREFIX/Resources/ # themes cp -vr kde/share/$NAME/themes $INPREFIX/Resources/ # gsl_distros, fit_models, colorchooser cp -vr kde/share/$NAME/pics $INPREFIX/Resources/ # color schemes (needs patched kcolorschememanager.cpp) cp -vr kde/share/$NAME/color-schemes $INPREFIX/Resources/color-schemes # appdata cp -v kde/share/metainfo/org.kde.labplot2.appdata.xml $INPREFIX/share/appdata/ cp -v kde/share/applications/org.kde.$NAME.desktop $INPREFIX/share/applications/ # cantor cp -v kde/Applications/cantor.app/Contents/MacOS/cantor $INPREFIX/MacOS cp -v kde/Applications/cantor_scripteditor.app/Contents/MacOS/cantor_scripteditor $INPREFIX/MacOS cp -vr kde/plugins/cantor $INPREFIX/PlugIns cp -v kde/lib/libcantor_config.dylib $INPREFIX/Frameworks/ cp -v kde/lib/libcantor_pythonbackend.dylib $INPREFIX/Frameworks/ # icons cp -vf kde/share/icontheme.rcc $INPREFIX/Resources/icontheme.rcc # misc cp -v labplot/admin/Info.plist $INPREFIX cp -v /Applications/KDE/labplot2.app/Contents/Resources/{LABPLOT_ICONS.icns,LML_ICONS.icns} $INPREFIX/Resources # translation cd kde $GCP -vf --parents share/locale/*/LC_MESSAGES/labplot2.mo ../$INPREFIX/ +$GCP -vf --parents share/locale/*/LC_MESSAGES/kconfigwidgets5.mo ../$INPREFIX/ +$GCP -vf --parents share/locale/*/LC_MESSAGES/kxmlgui5.mo ../$INPREFIX/ cd .. ### TODO # package icon # share/doc ########################################## # fix for hdf5 lib # install_name_tool -change /usr/local/Cellar/hdf5/1.8.17/lib/libhdf5.10.dylib /usr/local/opt/hdf5/1.8.17/lib/libhdf5.10.dylib /usr/local/opt/hdf5/1.8.17/lib/libhdf5_hl.10.dylib ############################################### if [ -d ./$TMPDIR ]; then rm -rf ./$TMPDIR/* else mkdir ./$TMPDIR fi mv $PREFIX/$PNAME.app ./$TMPDIR ln -s /Applications ./$TMPDIR/Applications ## remove stuff we don't need or like #rm -rf $TMPDIR/$PNAME.app/Contents/Plugins/bearer ############################################### # create the final disk image echo "BUILDING PACKAGE" rm -f ./labplot-$VERSION.dmg hdiutil create -srcfolder ./$TMPDIR -format UDBZ -fs HFS+ -imagekey zlib-level=9 ./labplot-$VERSION.dmg diff --git a/admin/labplot-64bit-setup.iss b/admin/labplot-64bit-setup.iss index 876225f15..037f1e607 100644 --- a/admin/labplot-64bit-setup.iss +++ b/admin/labplot-64bit-setup.iss @@ -1,189 +1,191 @@ #define MyAppName "LabPlot2" #define MyAppVersion "2.7.0" #define MyAppPublisher "Stefan Gerlach" #define MyAppURL "https://labplot.kde.org" #define MyAppExeName "labplot2.exe" #define ImageMagickVersion "ImageMagick-7.0.7-Q16" #define CraftRoot "C:\CraftRoot" [Setup] ; NOTE: The value of AppId uniquely identifies this application. ; Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) AppId={{EAFA7C2D-F2C4-4337-A4D3-3912BEA4F535} AppName={#MyAppName} AppVersion={#MyAppVersion} ;AppVerName={#MyAppName} {#MyAppVersion} AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={pf64}\{#MyAppName} DisableProgramGroupPage=yes OutputBaseFilename=labplot-{#MyAppVersion}-64bit-setup ArchitecturesAllowed=x64 ;use "none" for testing (much faster) Compression=lzma SolidCompression=yes Uninstallable=yes ;we install a file association for lml projects ChangesAssociations=yes [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] Source: "{#CraftRoot}\bin\labplot2.exe"; DestDir: "{app}"; Flags: ignoreversion ; use: windeployqt.exe --release bin\labplot2.exe --dir DEPLOY Source: "{#CraftRoot}\DEPLOY\*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion ; fix https://stackoverflow.com/questions/20495620/qt-5-1-1-application-failed-to-start-because-platform-plugin-windows-is-missi Source: "{#CraftRoot}\mingw64\bin\libssp-0.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libz.dll"; DestDir: "{app}";Flags: ignoreversion ;Source: "{#CraftRoot}\bin\liblzma.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\liblzma-5.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libintl-8.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\iconv.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libeay32.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libfreetype-6.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libpng15.dll"; DestDir: "{app}";Flags: ignoreversion ;Source: "{#CraftRoot}\bin\ssleay32.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libssl-1_1-x64.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libcrypto-1_1-x64.dll"; DestDir: "{app}";Flags: ignoreversion ;Source: "{#CraftRoot}\bin\libdbus-1-3.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\Qt5DBus.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libkdewin.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Archive.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Attica.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Auth.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5AuthCore.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Bookmarks.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Codecs.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Completion.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5ConfigCore.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5ConfigGui.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5ConfigWidgets.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5CoreAddons.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Crash.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5DBusAddons.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5GlobalAccel.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5GuiAddons.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5I18n.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5IconThemes.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5ItemViews.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5JobWidgets.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5KIOCore.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5KIOFileWidgets.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5KIOWidgets.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5NewStuff.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5NewStuffCore.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Parts.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Service.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5Solid.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5SonnetCore.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5SonnetUi.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5SyntaxHighlighting.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5TextEditor.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5TextWidgets.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5WidgetsAddons.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5WindowSystem.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libKF5XmlGui.dll"; DestDir: "{app}";Flags: ignoreversion ;Source: "{#CraftRoot}\bin\libKF5Notifications.dll"; DestDir: "{app}";Flags: ignoreversion ; missing lib on minimal Windows 10 Source: "C:\Python36\vcruntime140.dll"; DestDir: "{app}"; Flags: ignoreversion ;Source: "{#CraftRoot}\dev-utils\bin\msvcr120.dll"; DestDir: "{app}"; Flags: ignoreversion ; Cantor Source: "{#CraftRoot}\bin\cantor.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\cantor_scripteditor.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\cantor_python3server.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\cantor_juliaserver.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\libcantorlibs.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\libcantor_config.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\libcantor_pythonbackend.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\plugins\libcantorpart.dll"; DestDir: "{app}\plugins"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\plugins\cantor\*"; DestDir: "{app}\plugins\cantor"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\kxmlgui5\cantor\*"; DestDir: "{app}\data"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\cantor\*"; DestDir: "{app}\data"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\doc\HTML\en\cantor\*"; DestDir: "{app}\doc\HTML\en\cantor";Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\etc\xdg\cantor*"; DestDir: "{app}\etc\xdg"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\metainfo\org.kde.cantor.appdata.xml"; DestDir: "{app}\data\metainfo"; Flags: ignoreversion Source: "{#CraftRoot}\bin\data\applications\org.kde.cantor.desktop"; DestDir: "{app}\data\applications"; Flags: ignoreversion Source: "{#CraftRoot}\bin\data\config.kcfg\*"; DestDir: "{app}\data\config.kcfg"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\icons\hicolor\48x48\apps\*"; DestDir: "{app}\data\icons\hicolor\48x48\apps\"; Flags: recursesubdirs ignoreversion ; misc Source: "{#CraftRoot}\bin\libfftw3.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\netcdf.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\hdf5_hl.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\hdf5.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\zlib.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\szip.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\liblz4.so.1.8.3.dll"; DestDir: "{app}"; DestName: "liblz4.dll"; Flags: ignoreversion Source: "{#CraftRoot}\bin\libmysql.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\libpq.dll"; DestDir: "{app}"; Flags: ignoreversion ; TODO craft does not install own version (check) ;Source: "C:\Program Files\cfitsio\cfitsio.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\data\labplot2\*"; Excludes: "splash.png,\pics,\themes,\colorschemes"; DestDir: "{app}\labplot2"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\kxmlgui5\labplot2\labplot2ui.rc"; DestDir: "{app}\labplot2"; Flags: ignoreversion ; TODO check if needed: Source: "{#CraftRoot}\bin\data\kxmlgui5\labplot2\labplot2ui.rc"; DestDir: "{app}\kxmlgui5\labplot2"; Flags: ignoreversion Source: "{#CraftRoot}\bin\data\labplot2\pics\*"; DestDir: "{app}\data\pics"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\labplot2\themes\*"; DestDir: "{app}\data\themes"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\labplot2\color-schemes\*"; DestDir: "{app}\data\color-schemes"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\labplot2\splash.png"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\data\metainfo\org.kde.labplot2.appdata.xml"; DestDir: "{app}\data\metainfo"; Flags: ignoreversion Source: "{#CraftRoot}\bin\data\applications\org.kde.labplot2.desktop"; DestDir: "{app}\data\applications"; Flags: ignoreversion ; Source: "{#CraftRoot}\labplot\labplot2.cmd"; DestDir: "{app}";Flags: ignoreversion -; locale (data\locale\*\LC_MESSAGES\labplot2.mo) +; locale (data\locale\*\LC_MESSAGES\*.mo) Source: "{#CraftRoot}\bin\data\locale\labplot2.mo"; DestDir: "{app}\data\locale"; Flags: recursesubdirs ignoreversion +Source: "{#CraftRoot}\bin\data\locale\kconfigwidgets5.mo"; DestDir: "{app}\data\locale"; Flags: recursesubdirs ignoreversion +Source: "{#CraftRoot}\bin\data\locale\kxmlgui5.mo"; DestDir: "{app}\data\locale"; Flags: recursesubdirs ignoreversion ; icon theme Source: "{#CraftRoot}\bin\data\icontheme.rcc"; DestDir: "{app}\data";Flags: ignoreversion ; oxygen icons ;Source: "{#CraftRoot}\bin\data\icons\hicolor\*"; DestDir: "{app}\icons\hicolor"; Flags: recursesubdirs ignoreversion ; handbook Source: "{#CraftRoot}\bin\data\doc\HTML\en\labplot2\*"; DestDir: "{app}\doc\HTML\en\labplot2";Flags: recursesubdirs ignoreversion ; for SVG icons Source: "{#CraftRoot}\plugins\iconengines\qsvgicon.dll"; DestDir: "{app}\iconengines";Flags: ignoreversion Source: "{#CraftRoot}\bin\libexpat.dll"; DestDir: "{app}";Flags: ignoreversion ; convert ; TODO Source: "C:\Program Files\{#ImageMagickVersion}\convert.exe"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_MagickCore_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_MagickWand_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_bzlib_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_glib_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_lcms_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_lqr_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_png_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_ttf_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\CORE_RL_zlib_.dll"; DestDir: "{app}";Flags: ignoreversion ; Source: "C:\Program Files\{#ImageMagickVersion}\msvcr120.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\vcomp120.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\delegates.xml"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\magic.xml"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\modules\coders\IM_MOD_RL_pdf_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\modules\coders\IM_MOD_RL_png_.dll"; DestDir: "{app}";Flags: ignoreversion Source: "C:\Program Files\{#ImageMagickVersion}\modules\coders\IM_MOD_RL_ps_.dll"; DestDir: "{app}";Flags: ignoreversion ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}" Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; IconFilename: "{app}\labplot2\labplot2.ico" [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent [Registry] ; project file association Root: HKCR; Subkey: ".lml"; ValueType: string; ValueName: ""; ValueData: "{#MyAppName}"; Flags: uninsdeletevalue Root: HKCR; Subkey: "{#MyAppName}"; ValueType: string; ValueName: ""; ValueData: "MyView"; Flags: uninsdeletekey Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\labplot2\application-x-labplot2.ico,0" Root: HKCR; Subkey: "{#MyAppName}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" ; Root: HKCU; Subkey: "Environment"; ValueType:string; ValueName:"KDEROOT"; ValueData:"{app}" ; Flags: preservestringtype ; diff --git a/src/backend/core/Project.cpp b/src/backend/core/Project.cpp index 2b700e896..16a070c17 100644 --- a/src/backend/core/Project.cpp +++ b/src/backend/core/Project.cpp @@ -1,631 +1,630 @@ /*************************************************************************** File : Project.cpp Project : LabPlot Description : Represents a LabPlot project. -------------------------------------------------------------------- Copyright : (C) 2011-2014 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 Knut Franke (knut.franke@gmx.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datasources/LiveDataSource.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/plots/cartesian/XYEquationCurve.h" #include "backend/worksheet/plots/cartesian/XYDataReductionCurve.h" #include "backend/worksheet/plots/cartesian/XYDifferentiationCurve.h" #include "backend/worksheet/plots/cartesian/XYIntegrationCurve.h" #include "backend/worksheet/plots/cartesian/XYInterpolationCurve.h" #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h" #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "backend/worksheet/plots/cartesian/XYFourierFilterCurve.h" #include "backend/worksheet/plots/cartesian/XYFourierTransformCurve.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/datapicker/DatapickerCurve.h" #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}; }; 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; } 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")); - //wait until all columns are decoded from base64-encoded data - QThreadPool::globalInstance()->waitForDone(); - - //everything is read now. - //restore the pointer to the data sets (columns) in xy-curves etc. - QVector 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); - } - if (dynamic_cast(curve)) - RESTORE_POINTER(dynamic_cast(curve), dataSourceCurve, DataSourceCurve, XYCurve, curves); + if (!preview) { + //wait until all columns are decoded from base64-encoded data + QThreadPool::globalInstance()->waitForDone(); - curve->suppressRetransform(false); - } + //everything is read now. + //restore the pointer to the data sets (columns) in xy-curves etc. + QVector columns = children(AbstractAspect::Recursive); - //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); - } + //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); - //histograms - QVector hists = children(AbstractAspect::Recursive); - for (auto* hist : hists) { - if (!hist) continue; - RESTORE_COLUMN_POINTER(hist, dataColumn, DataColumn); + 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); } + if (dynamic_cast(curve)) + RESTORE_POINTER(dynamic_cast(curve), dataSourceCurve, DataSourceCurve, XYCurve, curves); - //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); - } + curve->suppressRetransform(false); + } - //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; - } - } + //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 (!found) - formulaVariableColumns << nullptr; + //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; } } - } else // no project element - reader->raiseError(i18n("no project element found")); - } else // no start document - reader->raiseError(i18n("no valid XML document found")); + } + + //LiveDataSource - call finalizeLoad() to replace relative with absolute paths if required + QVector sources = children(AbstractAspect::Recursive); + for (auto* source : sources) { + if (!source) continue; + source->finalizeLoad(); + } - if (!preview) { for (auto* plot : children(AbstractAspect::Recursive)) { plot->setIsLoading(false); plot->retransform(); } } emit loaded(); return !reader->hasError(); } bool Project::readProjectAttributes(XmlStreamReader* reader) { QXmlStreamAttributes attribs = reader->attributes(); - QString str = attribs.value(reader->namespaceUri().toString(), "fileName").toString(); - if (str.isEmpty()) { - reader->raiseError(i18n("Project file name missing.")); - return false; - } - d->fileName = str; - - str = attribs.value(reader->namespaceUri().toString(), "modificationTime").toString(); + 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; - str = attribs.value(reader->namespaceUri().toString(), "author").toString(); - d->author = str; + d->author = attribs.value(reader->namespaceUri().toString(), "author").toString(); return true; } diff --git a/src/backend/datapicker/DatapickerImage.cpp b/src/backend/datapicker/DatapickerImage.cpp index 1e5933515..a4e62d2c2 100644 --- a/src/backend/datapicker/DatapickerImage.cpp +++ b/src/backend/datapicker/DatapickerImage.cpp @@ -1,816 +1,712 @@ /*************************************************************************** File : DatapickerImage.cpp Project : LabPlot Description : Worksheet for Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) - Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2015-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 "DatapickerImage.h" #include "DatapickerImagePrivate.h" #include "backend/datapicker/ImageEditor.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/datapicker/Segments.h" #include "backend/worksheet/Worksheet.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "kdefrontend/worksheet/ExportWorksheetDialog.h" #include #include #include #include #include #include #include #include #include /** * \class DatapickerImage * \brief container to open image/plot. * * Top-level container for DatapickerPoint. * * * \ingroup datapicker */ DatapickerImage::DatapickerImage(const QString& name, bool loading) : AbstractPart(name, AspectType::DatapickerImage), foregroundBins( new int[ImageEditor::colorAttributeMax(Foreground) + 1]), hueBins( new int[ImageEditor::colorAttributeMax(Hue) + 1]), saturationBins( new int[ImageEditor::colorAttributeMax(Saturation) + 1]), valueBins( new int[ImageEditor::colorAttributeMax(Value) + 1]), intensityBins( new int[ImageEditor::colorAttributeMax(Intensity) + 1]), d(new DatapickerImagePrivate(this)), m_segments(new Segments(this)) { if (!loading) init(); } DatapickerImage::~DatapickerImage() { delete [] hueBins; delete [] saturationBins; delete [] valueBins; delete [] intensityBins; delete [] foregroundBins; delete m_segments; delete d; } void DatapickerImage::init() { KConfig config; KConfigGroup group = config.group( "DatapickerImage" ); d->fileName = group.readEntry("FileName", QString()); d->rotationAngle = group.readEntry("RotationAngle", 0.0); d->minSegmentLength = group.readEntry("MinSegmentLength", 30); d->pointSeparation = group.readEntry("PointSeparation", 30); d->axisPoints.type = (DatapickerImage::GraphType) group.readEntry("GraphType", (int) DatapickerImage::Cartesian); d->axisPoints.ternaryScale = group.readEntry("TernaryScale", 1); d->settings.foregroundThresholdHigh = group.readEntry("ForegroundThresholdHigh", 90); d->settings.foregroundThresholdLow = group.readEntry("ForegroundThresholdLow", 30); d->settings.hueThresholdHigh = group.readEntry("HueThresholdHigh", 360); d->settings.hueThresholdLow = group.readEntry("HueThresholdLow", 0); d->settings.intensityThresholdHigh = group.readEntry("IntensityThresholdHigh", 100); d->settings.intensityThresholdLow = group.readEntry("IntensityThresholdLow", 20); d->settings.saturationThresholdHigh = group.readEntry("SaturationThresholdHigh", 100); d->settings.saturationThresholdLow = group.readEntry("SaturationThresholdLow", 30); d->settings.valueThresholdHigh = group.readEntry("ValueThresholdHigh", 90); d->settings.valueThresholdLow = group.readEntry("ValueThresholdLow", 30); d->plotPointsType = (DatapickerImage::PointsType) group.readEntry("PlotPointsType", (int) DatapickerImage::AxisPoints); d->plotImageType = DatapickerImage::OriginalImage; // point properties d->pointStyle = (Symbol::Style)group.readEntry("PointStyle", (int)Symbol::Cross); d->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Point)); d->pointRotationAngle = group.readEntry("Rotation", 0.0); d->pointOpacity = group.readEntry("Opacity", 1.0); d->pointBrush.setStyle( (Qt::BrushStyle)group.readEntry("FillingStyle", (int)Qt::NoBrush) ); d->pointBrush.setColor( group.readEntry("FillingColor", QColor(Qt::black)) ); d->pointPen.setStyle( (Qt::PenStyle)group.readEntry("BorderStyle", (int)Qt::SolidLine) ); d->pointPen.setColor( group.readEntry("BorderColor", QColor(Qt::red)) ); d->pointPen.setWidthF( group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Point)) ); d->pointVisibility = group.readEntry("PointVisibility", true); } /*! Returns an icon to be used in the project explorer. */ QIcon DatapickerImage::icon() const { return QIcon::fromTheme("image-x-generic"); } /*! Return a new context menu */ QMenu* DatapickerImage::createContextMenu() { QMenu* menu = new QMenu(nullptr); emit requestProjectContextMenu(menu); return menu; } void DatapickerImage::createContextMenu(QMenu* menu) { emit requestProjectContextMenu(menu); } //! Construct a primary view on me. /** * This method may be called multiple times during the life time of an Aspect, or it might not get * called at all. Aspects must not depend on the existence of a view for their operation. */ QWidget* DatapickerImage::view() const { if (!m_partView) { m_view = new DatapickerImageView(const_cast(this)); m_partView = m_view; connect(m_view, &DatapickerImageView::statusInfo, this, &DatapickerImage::statusInfo); } return m_partView; } bool DatapickerImage::exportView() const { auto* dlg = new ExportWorksheetDialog(m_view); dlg->setFileName(name()); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const QString path = dlg->path(); const WorksheetView::ExportFormat format = dlg->exportFormat(); const int resolution = dlg->exportResolution(); WAIT_CURSOR; m_view->exportToFile(path, format, resolution); RESET_CURSOR; } delete dlg; return ret; } bool DatapickerImage::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, m_view); bool ret; dlg->setWindowTitle(i18nc("@title:window", "Print Datapicker Image")); if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool DatapickerImage::printPreview() const { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &DatapickerImageView::print); return dlg->exec(); } /*! Selects or deselects the Datapicker/DatapickerImage in the project explorer. This function is called in \c DatapickerImageView. The DatapickerImage gets deselected if there are selected items in the view, and selected if there are no selected items in the view. */ void DatapickerImage::setSelectedInView(const bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } void DatapickerImage::setSegmentsHoverEvent(const bool on) { m_segments->setAcceptHoverEvents(on); } QGraphicsScene* DatapickerImage::scene() const { return d->m_scene; } QRectF DatapickerImage::pageRect() const { return d->m_scene->sceneRect(); } void DatapickerImage::setPlotImageType(const DatapickerImage::PlotImageType type) { d->plotImageType = type; if (d->plotImageType == DatapickerImage::ProcessedImage) d->discretize(); emit requestUpdate(); } DatapickerImage::PlotImageType DatapickerImage::plotImageType() { return d->plotImageType; } void DatapickerImage::initSceneParameters() { setRotationAngle(0.0); setminSegmentLength(30); setPointSeparation(30); ReferencePoints axisPoints = d->axisPoints; axisPoints.ternaryScale = 1; axisPoints.type = DatapickerImage::Cartesian; setAxisPoints(axisPoints); EditorSettings settings; settings.foregroundThresholdHigh = 90; settings.foregroundThresholdLow = 30; settings.hueThresholdHigh = 360; settings.hueThresholdLow = 0; settings.intensityThresholdHigh = 100; settings.intensityThresholdLow = 20; settings.saturationThresholdHigh = 100; settings.saturationThresholdLow = 30; settings.valueThresholdHigh = 90; settings.valueThresholdLow = 30; setSettings(settings); DatapickerImage::PointsType plotPointsType = DatapickerImage::AxisPoints; setPlotPointsType(plotPointsType); } /* =============================== getter methods for background options ================================= */ CLASS_D_READER_IMPL(DatapickerImage, QString, fileName, fileName) CLASS_D_READER_IMPL(DatapickerImage, DatapickerImage::ReferencePoints, axisPoints, axisPoints) CLASS_D_READER_IMPL(DatapickerImage, DatapickerImage::EditorSettings, settings, settings) BASIC_D_READER_IMPL(DatapickerImage, float, rotationAngle, rotationAngle) BASIC_D_READER_IMPL(DatapickerImage, DatapickerImage::PointsType, plotPointsType, plotPointsType) BASIC_D_READER_IMPL(DatapickerImage, int, pointSeparation, pointSeparation) BASIC_D_READER_IMPL(DatapickerImage, int, minSegmentLength, minSegmentLength) BASIC_D_READER_IMPL(DatapickerImage, Symbol::Style, pointStyle, pointStyle) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointOpacity, pointOpacity) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointRotationAngle, pointRotationAngle) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointSize, pointSize) CLASS_D_READER_IMPL(DatapickerImage, QBrush, pointBrush, pointBrush) CLASS_D_READER_IMPL(DatapickerImage, QPen, pointPen, pointPen) BASIC_D_READER_IMPL(DatapickerImage, bool, pointVisibility, pointVisibility) /* ============================ setter methods and undo commands for background options ================= */ STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetFileName, QString, fileName, updateFileName) void DatapickerImage::setFileName(const QString& fileName) { if (fileName!= d->fileName) { beginMacro(i18n("%1: upload new image", name())); exec(new DatapickerImageSetFileNameCmd(d, fileName, ki18n("%1: upload image"))); endMacro(); } } STD_SETTER_CMD_IMPL_S(DatapickerImage, SetRotationAngle, float, rotationAngle) void DatapickerImage::setRotationAngle(float angle) { if (angle != d->rotationAngle) exec(new DatapickerImageSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_S(DatapickerImage, SetAxisPoints, DatapickerImage::ReferencePoints, axisPoints) void DatapickerImage::setAxisPoints(const DatapickerImage::ReferencePoints& points) { if (memcmp(&points, &d->axisPoints, sizeof(points)) != 0) exec(new DatapickerImageSetAxisPointsCmd(d, points, ki18n("%1: set Axis points"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetSettings, DatapickerImage::EditorSettings, settings, discretize) void DatapickerImage::setSettings(const DatapickerImage::EditorSettings& editorSettings) { if (memcmp(&editorSettings, &d->settings, sizeof(editorSettings)) != 0) exec(new DatapickerImageSetSettingsCmd(d, editorSettings, ki18n("%1: set editor settings"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetMinSegmentLength, int, minSegmentLength, makeSegments) void DatapickerImage::setminSegmentLength(const int value) { if (d->minSegmentLength != value) exec(new DatapickerImageSetMinSegmentLengthCmd(d, value, ki18n("%1: set minimum segment length"))); ; } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointStyle, Symbol::Style, pointStyle, retransform) void DatapickerImage::setPointStyle(Symbol::Style newStyle) { if (newStyle != d->pointStyle) exec(new DatapickerImageSetPointStyleCmd(d, newStyle, ki18n("%1: set point's style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointSize, qreal, pointSize, retransform) void DatapickerImage::setPointSize(qreal value) { if (!qFuzzyCompare(1 + value, 1 + d->pointSize)) exec(new DatapickerImageSetPointSizeCmd(d, value, ki18n("%1: set point's size"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointRotationAngle, qreal, pointRotationAngle, retransform) void DatapickerImage::setPointRotationAngle(qreal angle) { if (!qFuzzyCompare(1 + angle, 1 + d->pointRotationAngle)) exec(new DatapickerImageSetPointRotationAngleCmd(d, angle, ki18n("%1: rotate point"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointBrush, QBrush, pointBrush, retransform) void DatapickerImage::setPointBrush(const QBrush& newBrush) { if (newBrush != d->pointBrush) exec(new DatapickerImageSetPointBrushCmd(d, newBrush, ki18n("%1: set point's filling"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointPen, QPen, pointPen, retransform) void DatapickerImage::setPointPen(const QPen &newPen) { if (newPen != d->pointPen) exec(new DatapickerImageSetPointPenCmd(d, newPen, ki18n("%1: set outline style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointOpacity, qreal, pointOpacity, retransform) void DatapickerImage::setPointOpacity(qreal newOpacity) { if (newOpacity != d->pointOpacity) exec(new DatapickerImageSetPointOpacityCmd(d, newOpacity, ki18n("%1: set point's opacity"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointVisibility, bool, pointVisibility, retransform) void DatapickerImage::setPointVisibility(const bool on) { if (on != d->pointVisibility) exec(new DatapickerImageSetPointVisibilityCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } void DatapickerImage::setPrinting(bool on) const { QVector childPoints = parentAspect()->children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* point : childPoints) point->setPrinting(on); } void DatapickerImage::setPlotPointsType(const PointsType pointsType) { d->plotPointsType = pointsType; if (pointsType == DatapickerImage::AxisPoints) { //clear image int childCount = this->childCount(AbstractAspect::IncludeHidden); if (childCount) { beginMacro(i18n("%1: remove all axis points", name())); QVector childrenPoints = children(AbstractAspect::IncludeHidden); for (auto* point : childrenPoints) point->remove(); endMacro(); } m_segments->setSegmentsVisible(false); } else if (pointsType == DatapickerImage::CurvePoints) m_segments->setSegmentsVisible(false); else if (pointsType == DatapickerImage::SegmentPoints) { d->makeSegments(); m_segments->setSegmentsVisible(true); } } void DatapickerImage::setPointSeparation(const int value) { d->pointSeparation = value; } //############################################################################## //###################### Private implementation ############################### //############################################################################## DatapickerImagePrivate::DatapickerImagePrivate(DatapickerImage *owner) : q(owner), pageRect(0, 0, 1500, 1500), m_scene(new QGraphicsScene(pageRect)) { } QString DatapickerImagePrivate::name() const { return q->name(); } void DatapickerImagePrivate::retransform() { QVector childrenPoints = q->children(AbstractAspect::IncludeHidden); for (auto* point : childrenPoints) point->retransform(); } bool DatapickerImagePrivate::uploadImage(const QString& address) { bool rc = q->originalPlotImage.load(address); if (rc) { //convert the image to 32bit-format if this is not the case yet QImage::Format format = q->originalPlotImage.format(); if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_ARGB32_Premultiplied) q->originalPlotImage = q->originalPlotImage.convertToFormat(QImage::Format_RGB32); q->processedPlotImage = q->originalPlotImage; q->background = ImageEditor::findBackgroundColor(&q->originalPlotImage); //upload Histogram ImageEditor::uploadHistogram(q->intensityBins, &q->originalPlotImage, q->background, DatapickerImage::Intensity); ImageEditor::uploadHistogram(q->foregroundBins, &q->originalPlotImage, q->background, DatapickerImage::Foreground); ImageEditor::uploadHistogram(q->hueBins, &q->originalPlotImage, q->background, DatapickerImage::Hue); ImageEditor::uploadHistogram(q->saturationBins, &q->originalPlotImage, q->background, DatapickerImage::Saturation); ImageEditor::uploadHistogram(q->valueBins, &q->originalPlotImage, q->background, DatapickerImage::Value); discretize(); //resize the screen double w = Worksheet::convertToSceneUnits(q->originalPlotImage.width(), Worksheet::Inch)/QApplication::desktop()->physicalDpiX(); double h = Worksheet::convertToSceneUnits(q->originalPlotImage.height(), Worksheet::Inch)/QApplication::desktop()->physicalDpiX(); m_scene->setSceneRect(0, 0, w, h); q->isLoaded = true; } return rc; } void DatapickerImagePrivate::discretize() { if (plotImageType != DatapickerImage::ProcessedImage) return; ImageEditor::discretize(&q->processedPlotImage, &q->originalPlotImage, settings, q->background); if (plotPointsType != DatapickerImage::SegmentPoints) emit q->requestUpdate(); else makeSegments(); } void DatapickerImagePrivate::makeSegments() { if (plotPointsType != DatapickerImage::SegmentPoints) return; q->m_segments->makeSegments(q->processedPlotImage); q->m_segments->setSegmentsVisible(true); emit q->requestUpdate(); } DatapickerImagePrivate::~DatapickerImagePrivate() { delete m_scene; } void DatapickerImagePrivate::updateFileName() { WAIT_CURSOR; q->isLoaded = false; const QString& address = fileName.trimmed(); if (!address.isEmpty()) { if (uploadImage(address)) { q->initSceneParameters(); fileName = address; } } else { //hide segments if they are visible q->m_segments->setSegmentsVisible(false); } QVector childPoints = q->parentAspect()->children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); if (childPoints.count()) { for (auto* point : childPoints) point->remove(); } emit q->requestUpdate(); emit q->requestUpdateActions(); RESET_CURSOR; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void DatapickerImage::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "datapickerImage" ); writeBasicAttributes(writer); writeCommentElement(writer); //general properties writer->writeStartElement( "general" ); writer->writeAttribute( "fileName", d->fileName ); writer->writeAttribute( "plotPointsType", QString::number(d->plotPointsType) ); writer->writeEndElement(); writer->writeStartElement( "axisPoint" ); writer->writeAttribute( "graphType", QString::number(d->axisPoints.type) ); writer->writeAttribute( "ternaryScale", QString::number(d->axisPoints.ternaryScale) ); writer->writeAttribute( "axisPointLogicalX1", QString::number(d->axisPoints.logicalPos[0].x()) ); writer->writeAttribute( "axisPointLogicalY1", QString::number(d->axisPoints.logicalPos[0].y()) ); writer->writeAttribute( "axisPointLogicalX2", QString::number(d->axisPoints.logicalPos[1].x()) ); writer->writeAttribute( "axisPointLogicalY2", QString::number(d->axisPoints.logicalPos[1].y()) ); writer->writeAttribute( "axisPointLogicalX3", QString::number(d->axisPoints.logicalPos[2].x()) ); writer->writeAttribute( "axisPointLogicalY3", QString::number(d->axisPoints.logicalPos[2].y()) ); writer->writeAttribute( "axisPointLogicalZ1", QString::number(d->axisPoints.logicalPos[0].z()) ); writer->writeAttribute( "axisPointLogicalZ2", QString::number(d->axisPoints.logicalPos[1].z()) ); writer->writeAttribute( "axisPointLogicalZ3", QString::number(d->axisPoints.logicalPos[2].z()) ); writer->writeAttribute( "axisPointSceneX1", QString::number(d->axisPoints.scenePos[0].x()) ); writer->writeAttribute( "axisPointSceneY1", QString::number(d->axisPoints.scenePos[0].y()) ); writer->writeAttribute( "axisPointSceneX2", QString::number(d->axisPoints.scenePos[1].x()) ); writer->writeAttribute( "axisPointSceneY2", QString::number(d->axisPoints.scenePos[1].y()) ); writer->writeAttribute( "axisPointSceneX3", QString::number(d->axisPoints.scenePos[2].x()) ); writer->writeAttribute( "axisPointSceneY3", QString::number(d->axisPoints.scenePos[2].y()) ); writer->writeEndElement(); //editor and segment settings writer->writeStartElement( "editorSettings" ); writer->writeAttribute( "plotImageType", QString::number(d->plotImageType) ); writer->writeAttribute( "rotationAngle", QString::number(d->rotationAngle) ); writer->writeAttribute( "minSegmentLength", QString::number(d->minSegmentLength) ); writer->writeAttribute( "pointSeparation", QString::number(d->pointSeparation) ); writer->writeAttribute( "foregroundThresholdHigh", QString::number(d->settings.foregroundThresholdHigh) ); writer->writeAttribute( "foregroundThresholdLow", QString::number(d->settings.foregroundThresholdLow) ); writer->writeAttribute( "hueThresholdHigh", QString::number(d->settings.hueThresholdHigh) ); writer->writeAttribute( "hueThresholdLow", QString::number(d->settings.hueThresholdLow) ); writer->writeAttribute( "intensityThresholdHigh", QString::number(d->settings.intensityThresholdHigh) ); writer->writeAttribute( "intensityThresholdLow", QString::number(d->settings.intensityThresholdLow) ); writer->writeAttribute( "saturationThresholdHigh", QString::number(d->settings.saturationThresholdHigh) ); writer->writeAttribute( "saturationThresholdLow", QString::number(d->settings.saturationThresholdLow) ); writer->writeAttribute( "valueThresholdHigh", QString::number(d->settings.valueThresholdHigh) ); writer->writeAttribute( "valueThresholdLow", QString::number(d->settings.valueThresholdLow) ); writer->writeEndElement(); //symbol properties writer->writeStartElement( "symbolProperties" ); writer->writeAttribute( "pointRotationAngle", QString::number(d->pointRotationAngle) ); writer->writeAttribute( "pointOpacity", QString::number(d->pointOpacity) ); writer->writeAttribute( "pointSize", QString::number(d->pointSize) ); writer->writeAttribute( "pointStyle", QString::number(d->pointStyle) ); writer->writeAttribute( "pointVisibility", QString::number(d->pointVisibility) ); WRITE_QBRUSH(d->pointBrush); WRITE_QPEN(d->pointPen); writer->writeEndElement(); //serialize all children for (auto* child : children(IncludeHidden)) child->save(writer); writer->writeEndElement(); } //! Load from XML bool DatapickerImage::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "datapickerImage") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); d->fileName = str; str = attribs.value("plotPointsType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("plotPointsType").toString()); else d->plotPointsType = DatapickerImage::PointsType(str.toInt()); } else if (!preview && reader->name() == "axisPoint") { attribs = reader->attributes(); - str = attribs.value("graphType").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("graphType").toString()); - else - d->axisPoints.type = DatapickerImage::GraphType(str.toInt()); - - str = attribs.value("ternaryScale").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("ternaryScale").toString()); - else - d->axisPoints.ternaryScale = str.toDouble(); + READ_INT_VALUE("graphType", axisPoints.type, DatapickerImage::GraphType); + READ_INT_VALUE("ternaryScale", axisPoints.ternaryScale, int); str = attribs.value("axisPointLogicalX1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalX1").toString()); else d->axisPoints.logicalPos[0].setX(str.toDouble()); str = attribs.value("axisPointLogicalY1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalY1").toString()); else d->axisPoints.logicalPos[0].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalZ1").toString()); else d->axisPoints.logicalPos[0].setZ(str.toDouble()); str = attribs.value("axisPointLogicalX2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalX2").toString()); else d->axisPoints.logicalPos[1].setX(str.toDouble()); str = attribs.value("axisPointLogicalY2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalY2").toString()); else d->axisPoints.logicalPos[1].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalZ2").toString()); else d->axisPoints.logicalPos[1].setZ(str.toDouble()); str = attribs.value("axisPointLogicalX3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalX3").toString()); else d->axisPoints.logicalPos[2].setX(str.toDouble()); str = attribs.value("axisPointLogicalY3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalY3").toString()); else d->axisPoints.logicalPos[2].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalZ3").toString()); else d->axisPoints.logicalPos[2].setZ(str.toDouble()); str = attribs.value("axisPointSceneX1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneX1").toString()); else d->axisPoints.scenePos[0].setX(str.toDouble()); str = attribs.value("axisPointSceneY1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneY1").toString()); else d->axisPoints.scenePos[0].setY(str.toDouble()); str = attribs.value("axisPointSceneX2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneX2").toString()); else d->axisPoints.scenePos[1].setX(str.toDouble()); str = attribs.value("axisPointSceneY2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneY2").toString()); else d->axisPoints.scenePos[1].setY(str.toDouble()); str = attribs.value("axisPointSceneX3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneX3").toString()); else d->axisPoints.scenePos[2].setX(str.toDouble()); str = attribs.value("axisPointSceneY3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneY3").toString()); else d->axisPoints.scenePos[2].setY(str.toDouble()); } else if (!preview && reader->name() == "editorSettings") { attribs = reader->attributes(); - str = attribs.value("plotImageType").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("plotImageType").toString()); - else - d->plotImageType = DatapickerImage::PlotImageType(str.toInt()); - - str = attribs.value("rotationAngle").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("rotationAngle").toString()); - else - d->rotationAngle = str.toFloat(); - - str = attribs.value("minSegmentLength").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("minSegmentLength").toString()); - else - d->minSegmentLength = str.toInt(); - - str = attribs.value("pointSeparation").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("pointSeparation").toString()); - else - d->pointSeparation = str.toInt(); - - str = attribs.value("foregroundThresholdHigh").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("foregroundThresholdHigh").toString()); - else - d->settings.foregroundThresholdHigh = str.toInt(); - - str = attribs.value("foregroundThresholdLow").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("foregroundThresholdLow").toString()); - else - d->settings.foregroundThresholdLow = str.toInt(); - - str = attribs.value("hueThresholdHigh").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("hueThresholdHigh").toString()); - else - d->settings.hueThresholdHigh = str.toInt(); - - str = attribs.value("hueThresholdLow").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("hueThresholdLow").toString()); - else - d->settings.hueThresholdLow = str.toInt(); - - str = attribs.value("intensityThresholdHigh").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("intensityThresholdHigh").toString()); - else - d->settings.intensityThresholdHigh = str.toInt(); - - str = attribs.value("intensityThresholdLow").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("intensityThresholdLow").toString()); - else - d->settings.intensityThresholdLow = str.toInt(); - - str = attribs.value("saturationThresholdHigh").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("saturationThresholdHigh").toString()); - else - d->settings.saturationThresholdHigh = str.toInt(); - - str = attribs.value("saturationThresholdLow").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("saturationThresholdLow").toString()); - else - d->settings.saturationThresholdLow = str.toInt(); - - str = attribs.value("valueThresholdHigh").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("valueThresholdHigh").toString()); - else - d->settings.valueThresholdHigh = str.toInt(); - - str = attribs.value("valueThresholdLow").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("valueThresholdLow").toString()); - else - d->settings.valueThresholdLow = str.toInt(); - + READ_INT_VALUE("plotImageType", plotImageType, DatapickerImage::PlotImageType); + READ_DOUBLE_VALUE("rotationAngle", rotationAngle); + READ_INT_VALUE("minSegmentLength", minSegmentLength, int); + READ_INT_VALUE("pointSeparation", pointSeparation, int); + READ_INT_VALUE("foregroundThresholdHigh", settings.foregroundThresholdHigh, int); + READ_INT_VALUE("foregroundThresholdLow", settings.foregroundThresholdLow, int); + READ_INT_VALUE("hueThresholdHigh", settings.hueThresholdHigh, int); + READ_INT_VALUE("hueThresholdLow", settings.hueThresholdLow, int); + READ_INT_VALUE("intensityThresholdHigh", settings.intensityThresholdHigh, int); + READ_INT_VALUE("intensityThresholdLow", settings.intensityThresholdLow, int); + READ_INT_VALUE("saturationThresholdHigh", settings.saturationThresholdHigh, int); + READ_INT_VALUE("saturationThresholdLow", settings.saturationThresholdLow, int); + READ_INT_VALUE("valueThresholdHigh", settings.valueThresholdHigh, int); + READ_INT_VALUE("valueThresholdLow", settings.valueThresholdLow, int); } else if (!preview && reader->name() == "symbolProperties") { attribs = reader->attributes(); - str = attribs.value("pointRotationAngle").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("pointRotationAngle").toString()); - else - d->pointRotationAngle = str.toFloat(); - - str = attribs.value("pointOpacity").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("pointOpacity").toString()); - else - d->pointOpacity = str.toFloat(); - - str = attribs.value("pointSize").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("pointSize").toString()); - else - d->pointSize = str.toFloat(); - - str = attribs.value("pointStyle").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("pointStyle").toString()); - else - d->pointStyle = (Symbol::Style)str.toInt(); - - str = attribs.value("pointVisibility").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("pointVisibility").toString()); - else - d->pointVisibility = (bool)str.toInt(); - + READ_DOUBLE_VALUE("pointRotationAngle", pointRotationAngle); + READ_DOUBLE_VALUE("pointOpacity", pointOpacity); + READ_DOUBLE_VALUE("pointSize", pointSize); + READ_INT_VALUE("pointStyle", pointStyle, Symbol::Style); + READ_INT_VALUE("pointVisibility", pointVisibility, bool); READ_QBRUSH(d->pointBrush); READ_QPEN(d->pointPen); } else if (reader->name() == "datapickerPoint") { DatapickerPoint* datapickerPoint = new DatapickerPoint(QString()); datapickerPoint->setHidden(true); if (!datapickerPoint->load(reader, preview)) { delete datapickerPoint; return false; } else addChild(datapickerPoint); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } d->uploadImage(d->fileName); d->retransform(); return true; } diff --git a/src/backend/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp index 960daf529..8fd03a93f 100644 --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -1,971 +1,922 @@ /*************************************************************************** File : LiveDataSource.cpp Project : LabPlot Description : Represents live data source -------------------------------------------------------------------- Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "backend/core/Project.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class LiveDataSource \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters. \ingroup datasources */ LiveDataSource::LiveDataSource(const QString& name, bool loading) : Spreadsheet(name, loading, AspectType::LiveDataSource), m_updateTimer(new QTimer(this)) { initActions(); connect(m_updateTimer, &QTimer::timeout, this, &LiveDataSource::read); } LiveDataSource::~LiveDataSource() { //stop reading before deleting the objects pauseReading(); delete m_filter; delete m_fileSystemWatcher; delete m_file; delete m_localSocket; delete m_tcpSocket; delete m_serialPort; delete m_updateTimer; } -/*! - * depending on the update type, periodically or on data changes, starts the timer or activates the file watchers, respectively. - */ -void LiveDataSource::ready() { - DEBUG("LiveDataSource::ready() update type = " << ENUM_TO_STRING(LiveDataSource, UpdateType, m_updateType) << ", interval = " << m_updateInterval); - switch (m_updateType) { - case TimeInterval: - m_updateTimer->start(m_updateInterval); - DEBUG("STARTED TIMER. REMAINING TIME = " << m_updateTimer->remainingTime()); - break; - case NewData: - DEBUG("STARTING WATCHER"); - watch(); - } -} - void LiveDataSource::initActions() { - m_reloadAction = new QAction(QIcon::fromTheme("view-refresh"), i18n("Reload"), this); - connect(m_reloadAction, &QAction::triggered, this, &LiveDataSource::read); - - m_toggleLinkAction = new QAction(i18n("Link the file"), this); - m_toggleLinkAction->setCheckable(true); - connect(m_toggleLinkAction, &QAction::triggered, this, &LiveDataSource::linkToggled); - m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this); connect(m_plotDataAction, &QAction::triggered, this, &LiveDataSource::plotData); } QWidget* LiveDataSource::view() const { if (!m_partView) { m_view = new SpreadsheetView(const_cast(this), true); m_partView = m_view; } return m_partView; } /*! * \brief Returns a list with the names of the available ports */ QStringList LiveDataSource::availablePorts() { QStringList ports; // qDebug() << "available ports count:" << QSerialPortInfo::availablePorts().size(); for (const QSerialPortInfo& sp : QSerialPortInfo::availablePorts()) { ports.append(sp.portName()); DEBUG(" port " << sp.portName().toStdString() << ": " << sp.systemLocation().toStdString() << sp.description().toStdString() << ' ' << sp.manufacturer().toStdString() << ' ' << sp.serialNumber().toStdString()); } // For Testing: // ports.append("/dev/pts/26"); return ports; } /*! * \brief Returns a list with the supported baud rates */ QStringList LiveDataSource::supportedBaudRates() { QStringList baudRates; for (const auto& baud : QSerialPortInfo::standardBaudRates()) baudRates.append(QString::number(baud)); return baudRates; } /*! * \brief Updates this data source at this moment */ void LiveDataSource::updateNow() { DEBUG("LiveDataSource::updateNow() update interval = " << m_updateInterval); m_updateTimer->stop(); read(); //restart the timer after update if (m_updateType == TimeInterval) m_updateTimer->start(m_updateInterval); } /*! * \brief Continue reading from the live data source after it was paused */ void LiveDataSource::continueReading() { m_paused = false; switch (m_updateType) { case TimeInterval: m_updateTimer->start(m_updateInterval); break; - case NewData: + case NewData: connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); } } /*! * \brief Pause the reading of the live data source */ void LiveDataSource::pauseReading() { m_paused = true; switch (m_updateType) { case TimeInterval: m_updateTimer->stop(); break; case NewData: disconnect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); } } void LiveDataSource::setFileName(const QString& name) { m_fileName = name; } QString LiveDataSource::fileName() const { return m_fileName; } /*! * \brief Sets the local socket's server name to name * \param name */ void LiveDataSource::setLocalSocketName(const QString& name) { m_localSocketName = name; } QString LiveDataSource::localSocketName() const { return m_localSocketName; } void LiveDataSource::setFileType(AbstractFileFilter::FileType type) { m_fileType = type; } AbstractFileFilter::FileType LiveDataSource::fileType() const { return m_fileType; } void LiveDataSource::setFilter(AbstractFileFilter* f) { delete m_filter; m_filter = f; } AbstractFileFilter* LiveDataSource::filter() const { return m_filter; } -/*! - sets whether the file should be watched or not. - In the first case the data source will be automatically updated on file changes. -*/ -void LiveDataSource::setFileWatched(bool b) { - m_fileWatched = b; -} - -bool LiveDataSource::isFileWatched() const { - return m_fileWatched; -} - /*! * \brief Sets the serial port's baud rate * \param baudrate */ void LiveDataSource::setBaudRate(int baudrate) { m_baudRate = baudrate; } int LiveDataSource::baudRate() const { return m_baudRate; } /*! * \brief Sets the source's update interval to \c interval * \param interval */ void LiveDataSource::setUpdateInterval(int interval) { m_updateInterval = interval; if (!m_paused) m_updateTimer->start(m_updateInterval); } int LiveDataSource::updateInterval() const { return m_updateInterval; } /*! * \brief Sets how many values we should keep when keepLastValues is true * \param keepnvalues */ void LiveDataSource::setKeepNValues(int keepnvalues) { m_keepNValues = keepnvalues; } int LiveDataSource::keepNValues() const { return m_keepNValues; } /*! * \brief Sets the network socket's port to port * \param port */ void LiveDataSource::setPort(quint16 port) { m_port = port; } void LiveDataSource::setBytesRead(qint64 bytes) { m_bytesRead = bytes; } int LiveDataSource::bytesRead() const { return m_bytesRead; } int LiveDataSource::port() const { return m_port; } /*! * \brief Sets the serial port's name to name * \param name */ void LiveDataSource::setSerialPort(const QString& name) { m_serialPortName = name; } QString LiveDataSource::serialPortName() const { return m_serialPortName; } bool LiveDataSource::isPaused() const { return m_paused; } /*! * \brief Sets the sample size to size * \param size */ void LiveDataSource::setSampleSize(int size) { m_sampleSize = size; } int LiveDataSource::sampleSize() const { return m_sampleSize; } /*! * \brief Sets the source's type to sourcetype * \param sourcetype */ void LiveDataSource::setSourceType(SourceType sourcetype) { m_sourceType = sourcetype; } LiveDataSource::SourceType LiveDataSource::sourceType() const { return m_sourceType; } /*! * \brief Sets the source's reading type to readingType * \param readingType */ void LiveDataSource::setReadingType(ReadingType readingType) { m_readingType = readingType; } LiveDataSource::ReadingType LiveDataSource::readingType() const { return m_readingType; } /*! * \brief Sets the source's update type to updatetype and handles this change * \param updatetype */ void LiveDataSource::setUpdateType(UpdateType updatetype) { switch (updatetype) { case NewData: m_updateTimer->stop(); - if (m_fileSystemWatcher == nullptr) - watch(); - else + + if (!m_fileSystemWatcher) { + m_fileSystemWatcher = new QFileSystemWatcher; + + //connect to file changes to read the new data connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); + + //connect to file changes to re-add the file path again - need to cope with deletion of files in text editors which + //on save create a new file in the temp folder first and then swap with the original one. + connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [=]() {m_fileSystemWatcher->addPath(m_fileName);}); + } + + if (!m_fileSystemWatcher->files().contains(m_fileName)) + m_fileSystemWatcher->addPath(m_fileName); + break; case TimeInterval: - if (m_fileSystemWatcher) + if (m_fileSystemWatcher) { + m_fileSystemWatcher->removePath(m_fileName); disconnect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); + } } m_updateType = updatetype; } LiveDataSource::UpdateType LiveDataSource::updateType() const { return m_updateType; } /*! * \brief Sets the network socket's host * \param host */ void LiveDataSource::setHost(const QString& host) { m_host = host.simplified(); } QString LiveDataSource::host() const { return m_host; } /*! sets whether only a link to the file is saved in the project file (\c b=true) or the whole content of the file (\c b=false). */ void LiveDataSource::setFileLinked(bool b) { m_fileLinked = b; } /*! returns \c true if only a link to the file is saved in the project file. \c false otherwise. */ bool LiveDataSource::isFileLinked() const { return m_fileLinked; } +void LiveDataSource::setUseRelativePath(bool b) { + m_relativePath = b; +} + +bool LiveDataSource::useRelativePath() const { + return m_relativePath; +} + QIcon LiveDataSource::icon() const { QIcon icon; switch (m_fileType) { case AbstractFileFilter::Ascii: icon = QIcon::fromTheme("text-plain"); break; case AbstractFileFilter::Binary: icon = QIcon::fromTheme("application-octet-stream"); break; case AbstractFileFilter::Image: icon = QIcon::fromTheme("image-x-generic"); break; // TODO: missing icons case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: break; case AbstractFileFilter::FITS: icon = QIcon::fromTheme("kstars_fitsviewer"); break; case AbstractFileFilter::JSON: icon = QIcon::fromTheme("application-json"); break; case AbstractFileFilter::ROOT: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } return icon; } QMenu* LiveDataSource::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); QAction* firstAction = nullptr; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size() > 1) firstAction = menu->actions().at(1); menu->insertAction(firstAction, m_plotDataAction); menu->insertSeparator(firstAction); - //TODO: doesn't always make sense... -// if (!m_fileWatched) -// menu->insertAction(firstAction, m_reloadAction); -// -// m_toggleWatchAction->setChecked(m_fileWatched); -// menu->insertAction(firstAction, m_toggleWatchAction); -// -// m_toggleLinkAction->setChecked(m_fileLinked); -// menu->insertAction(firstAction, m_toggleLinkAction); - return menu; } //############################################################################## //################################# SLOTS #################################### //############################################################################## /* * called periodically or on new data changes (file changed, new data in the socket, etc.) */ void LiveDataSource::read() { DEBUG("\nLiveDataSource::read()"); if (!m_filter) return; if (m_reading) return; m_reading = true; //initialize the device (file, socket, serial port) when calling this function for the first time if (!m_prepared) { DEBUG(" Preparing device: update type = " << ENUM_TO_STRING(LiveDataSource, UpdateType, m_updateType)); switch (m_sourceType) { case FileOrPipe: m_file = new QFile(m_fileName); m_device = m_file; break; case NetworkTcpSocket: m_tcpSocket = new QTcpSocket(this); m_device = m_tcpSocket; m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); connect(m_tcpSocket, &QTcpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_tcpSocket, static_cast(&QTcpSocket::error), this, &LiveDataSource::tcpSocketError); break; case NetworkUdpSocket: m_udpSocket = new QUdpSocket(this); m_device = m_udpSocket; m_udpSocket->bind(QHostAddress(m_host), m_port); m_udpSocket->connectToHost(m_host, 0, QUdpSocket::ReadOnly); // only connect to readyRead when update is on new data if (m_updateType == NewData) connect(m_udpSocket, &QUdpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_udpSocket, static_cast(&QUdpSocket::error), this, &LiveDataSource::tcpSocketError); break; case LocalSocket: m_localSocket = new QLocalSocket(this); m_device = m_localSocket; m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); connect(m_localSocket, &QLocalSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_localSocket, static_cast(&QLocalSocket::error), this, &LiveDataSource::localSocketError); break; case SerialPort: m_serialPort = new QSerialPort(this); m_device = m_serialPort; DEBUG(" Serial: " << m_serialPortName.toStdString() << ", " << m_baudRate); m_serialPort->setBaudRate(m_baudRate); m_serialPort->setPortName(m_serialPortName); m_serialPort->open(QIODevice::ReadOnly); // only connect to readyRead when update is on new data if (m_updateType == NewData) connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead); connect(m_serialPort, static_cast(&QSerialPort::error), this, &LiveDataSource::serialPortError); break; case MQTT: break; } m_prepared = true; } qint64 bytes = 0; switch (m_sourceType) { case FileOrPipe: DEBUG("Reading FileOrPipe. type = " << ENUM_TO_STRING(AbstractFileFilter, FileType, m_fileType)); switch (m_fileType) { case AbstractFileFilter::Ascii: if (m_readingType == LiveDataSource::ReadingType::WholeFile) { static_cast(m_filter)->readFromLiveDevice(*m_file, this, 0); } else { bytes = static_cast(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); m_bytesRead += bytes; DEBUG("Read " << bytes << " bytes, in total: " << m_bytesRead); } break; case AbstractFileFilter::Binary: //TODO: not implemented yet // bytes = qSharedPointerCast(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); // m_bytesRead += bytes; case AbstractFileFilter::ROOT: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: //only re-reading of the whole file is supported m_filter->readDataFromFile(m_fileName, this); break; //TODO: other types not implemented yet case AbstractFileFilter::Image: case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: case AbstractFileFilter::JSON: break; } break; case NetworkTcpSocket: DEBUG("reading from TCP socket. state before abort = " << m_tcpSocket->state()); m_tcpSocket->abort(); m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); DEBUG("reading from TCP socket. state after reconnect = " << m_tcpSocket->state()); break; case NetworkUdpSocket: DEBUG(" Reading from UDP socket. state = " << m_udpSocket->state()); // reading data here if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; case LocalSocket: DEBUG(" Reading from local socket. state before abort = " << m_localSocket->state()); if (m_localSocket->state() == QLocalSocket::ConnectingState) m_localSocket->abort(); m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); if (m_localSocket->waitForConnected()) m_localSocket->waitForReadyRead(); DEBUG(" Reading from local socket. state after reconnect = " << m_localSocket->state()); break; case SerialPort: DEBUG(" Reading from serial port"); // reading data here if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; case MQTT: break; } m_reading = false; } /*! * Slot for the signal that is emitted once every time new data is available for reading from the device (not UDP or Serial). * It will only be emitted again once new data is available, such as when a new payload of network data has arrived on the network socket, * or when a new block of data has been appended to your device. */ void LiveDataSource::readyRead() { DEBUG("LiveDataSource::readyRead() update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType)); DEBUG(" REMAINING TIME = " << m_updateTimer->remainingTime()); if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //TODO: not implemented yet // else if (m_fileType == AbstractFileFilter::Binary) // dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //since we won't have the timer to call read() where we create new connections //for sequential devices in read() we just request data/connect to servers if (m_updateType == NewData) read(); } void LiveDataSource::localSocketError(QLocalSocket::LocalSocketError socketError) { Q_UNUSED(socketError); /*disconnect(m_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(localSocketError(QLocalSocket::LocalSocketError))); disconnect(m_localSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));*/ /*switch (socketError) { case QLocalSocket::ServerNotFoundError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket was not found. Please check the socket name.")); break; case QLocalSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The connection was refused by the peer")); break; case QLocalSocket::PeerClosedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket has closed the connection.")); break; default: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The following error occurred: %1.", m_localSocket->errorString())); }*/ } void LiveDataSource::tcpSocketError(QAbstractSocket::SocketError socketError) { Q_UNUSED(socketError); /*switch (socketError) { case QAbstractSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The connection was refused by the peer. Make sure the server is running and check the host name and port settings.")); break; case QAbstractSocket::RemoteHostClosedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The remote host closed the connection.")); break; case QAbstractSocket::HostNotFoundError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The host was not found. Please check the host name and port settings.")); break; default: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The following error occurred: %1.", m_tcpSocket->errorString())); }*/ } void LiveDataSource::serialPortError(QSerialPort::SerialPortError serialPortError) { switch (serialPortError) { case QSerialPort::DeviceNotFoundError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device.")); break; case QSerialPort::PermissionError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device. Please check your permissions on this device.")); break; case QSerialPort::OpenError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Device already opened.")); break; case QSerialPort::NotOpenError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device is not opened.")); break; case QSerialPort::ReadError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data.")); break; case QSerialPort::ResourceError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data. The device is removed.")); break; case QSerialPort::TimeoutError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device timed out.")); break; #ifndef _MSC_VER //MSVC complains about the usage of deprecated enums, g++ and clang complain about missing enums case QSerialPort::ParityError: case QSerialPort::FramingError: case QSerialPort::BreakConditionError: #endif case QSerialPort::WriteError: case QSerialPort::UnsupportedOperationError: case QSerialPort::UnknownError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The following error occurred: %1.", m_serialPort->errorString())); break; case QSerialPort::NoError: break; } } -void LiveDataSource::watchToggled() { - m_fileWatched = !m_fileWatched; - watch(); - project()->setChanged(true); -} - -void LiveDataSource::linkToggled() { - m_fileLinked = !m_fileLinked; - project()->setChanged(true); -} - -/*! - * Watch the file upon reading for changes if required - * uses m_fileName for FileOrPipe and LocalSocket - */ -void LiveDataSource::watch() { - DEBUG("LiveDataSource::watch() file name = " << m_fileName.toStdString()); - if (m_fileWatched) { - DEBUG(" file is watched"); - if (!m_fileSystemWatcher) { - m_fileSystemWatcher = new QFileSystemWatcher; - - //connect to file changes to read the new data - connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); - - //connect to file changes to re-add the file path again - need to cope with deletion of files in text editors which - //on save create a new file in the temp folder first and then swap with the original one. - connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [=]() {m_fileSystemWatcher->addPath(m_fileName);}); - } - - if (!m_fileSystemWatcher->files().contains(m_fileName)) - m_fileSystemWatcher->addPath(m_fileName); - } else { - DEBUG(" file is not watched"); - if (m_fileSystemWatcher) - m_fileSystemWatcher->removePath(m_fileName); - } -} - void LiveDataSource::plotData() { auto* dlg = new PlotDataDialog(this); dlg->exec(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void LiveDataSource::save(QXmlStreamWriter* writer) const { writer->writeStartElement("LiveDataSource"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); - writer->writeAttribute("fileName", m_fileName); - writer->writeAttribute("fileType", QString::number(m_fileType)); - writer->writeAttribute("fileWatched", QString::number(m_fileWatched)); - writer->writeAttribute("fileLinked", QString::number(m_fileLinked)); - writer->writeAttribute("updateType", QString::number(m_updateType)); - writer->writeAttribute("readingType", QString::number(m_readingType)); - writer->writeAttribute("sourceType", QString::number(m_sourceType)); - writer->writeAttribute("keepNValues", QString::number(m_keepNValues)); - - if (m_updateType == TimeInterval) - writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); - - if (m_readingType != TillEnd) - writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); switch (m_sourceType) { + case FileOrPipe: + writer->writeAttribute("fileType", QString::number(m_fileType)); + writer->writeAttribute("fileLinked", QString::number(m_fileLinked)); + writer->writeAttribute("relativePath", QString::number(m_relativePath)); + if (m_relativePath) { + //convert from the absolute to the relative path and save it + const Project* p = const_cast(this)->project(); + QFileInfo fi(p->fileName()); + writer->writeAttribute("fileName", fi.dir().relativeFilePath(m_fileName)); + }else + writer->writeAttribute("fileName", m_fileName); + + break; case SerialPort: writer->writeAttribute("baudRate", QString::number(m_baudRate)); writer->writeAttribute("serialPortName", m_serialPortName); - break; case NetworkTcpSocket: case NetworkUdpSocket: writer->writeAttribute("host", m_host); writer->writeAttribute("port", QString::number(m_port)); break; - case FileOrPipe: - break; case LocalSocket: break; case MQTT: break; default: break; } - writer->writeEndElement(); + writer->writeAttribute("updateType", QString::number(m_updateType)); + writer->writeAttribute("readingType", QString::number(m_readingType)); + writer->writeAttribute("sourceType", QString::number(m_sourceType)); + writer->writeAttribute("keepNValues", QString::number(m_keepNValues)); + + if (m_updateType == TimeInterval) + writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); + + if (m_readingType != TillEnd) + writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); + writer->writeEndElement(); //general //filter m_filter->save(writer); //columns if (!m_fileLinked) { for (auto* col : children(IncludeHidden)) col->save(writer); } writer->writeEndElement(); // "LiveDataSource" } /*! Loads from XML. */ bool LiveDataSource::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "LiveDataSource") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileName").toString()); else m_fileName = str; str = attribs.value("fileType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileType").toString()); else m_fileType = (AbstractFileFilter::FileType)str.toInt(); - str = attribs.value("fileWatched").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.subs("fileWatched").toString()); - else - m_fileWatched = str.toInt(); - str = attribs.value("fileLinked").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileLinked").toString()); else m_fileLinked = str.toInt(); + str = attribs.value("relativePath").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("relativePath").toString()); + else + m_relativePath = str.toInt(); + str = attribs.value("updateType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateType").toString()); else m_updateType = static_cast(str.toInt()); str = attribs.value("sourceType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sourceType").toString()); else m_sourceType = static_cast(str.toInt()); str = attribs.value("readingType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("readingType").toString()); else m_readingType = static_cast(str.toInt()); if (m_updateType == TimeInterval) { str = attribs.value("updateInterval").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateInterval").toString()); else m_updateInterval = str.toInt(); } if (m_readingType != TillEnd) { str = attribs.value("sampleSize").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sampleSize").toString()); else m_sampleSize = str.toInt(); } switch (m_sourceType) { case SerialPort: str = attribs.value("baudRate").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("baudRate").toString()); else m_baudRate = str.toInt(); str = attribs.value("serialPortName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("serialPortName").toString()); else m_serialPortName = str; break; case NetworkTcpSocket: case NetworkUdpSocket: str = attribs.value("host").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("host").toString()); else m_host = str; str = attribs.value("port").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("port").toString()); else m_host = str; break; case MQTT: break; case FileOrPipe: break; case LocalSocket: break; default: break; } } else if (reader->name() == "asciiFilter") { setFilter(new AsciiFilter); if (!m_filter->load(reader)) return false; } else if (reader->name() == "rootFilter") { setFilter(new ROOTFilter); if (!m_filter->load(reader)) return false; } else if (reader->name() == "column") { Column* column = new Column(QString(), AbstractColumn::Text); if (!column->load(reader, preview)) { delete column; setColumnCount(0); return false; } addChild(column); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } + return !reader->hasError(); +} + +void LiveDataSource::finalizeLoad() { + //convert from the relative path saved in the project file to the absolute file to work with + if (m_relativePath) { + QFileInfo fi(project()->fileName()); + m_fileName = fi.dir().absoluteFilePath(m_fileName); + } + //read the content of the file if it was only linked if (m_fileLinked && QFile::exists(m_fileName)) this->read(); - if (m_fileWatched) - watch(); - - return !reader->hasError(); + //call setUpdateType() to start watching the file for changes, is required + setUpdateType(m_updateType); } diff --git a/src/backend/datasources/LiveDataSource.h b/src/backend/datasources/LiveDataSource.h index 61cfcd7d8..7b47e92a5 100644 --- a/src/backend/datasources/LiveDataSource.h +++ b/src/backend/datasources/LiveDataSource.h @@ -1,214 +1,205 @@ /*************************************************************************** File : LiveDataSource.h Project : LabPlot Description : File data source -------------------------------------------------------------------- Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2017-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef LIVEDATASOURCE_H #define LIVEDATASOURCE_H #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include #include #include #include #include class QString; class AbstractFileFilter; class QFileSystemWatcher; class QAction; class QTcpSocket; class QUdpSocket; class QFile; class LiveDataSource : public Spreadsheet { Q_OBJECT Q_ENUMS(SourceType) Q_ENUMS(UpdateType) Q_ENUMS(ReadingType) public: enum SourceType { FileOrPipe = 0, // regular file or pipe NetworkTcpSocket, // TCP socket NetworkUdpSocket, // UDP socket LocalSocket, // local socket SerialPort, // serial port MQTT }; enum UpdateType { TimeInterval = 0, // update periodically using given interval NewData // update when new data is available }; enum ReadingType { ContinuousFixed = 0, // read continuously sampleSize number of samples (lines) FromEnd, // read sampleSize number of samples (lines) from end TillEnd, // read until the end WholeFile // reread whole file }; explicit LiveDataSource(const QString& name, bool loading = false); ~LiveDataSource() override; - void ready(); - static QStringList supportedBaudRates(); static QStringList availablePorts(); void setFileType(const AbstractFileFilter::FileType); AbstractFileFilter::FileType fileType() const; UpdateType updateType() const; void setUpdateType(UpdateType); SourceType sourceType() const; void setSourceType(SourceType); ReadingType readingType() const; void setReadingType(ReadingType); int sampleSize() const; void setSampleSize(int); void setBytesRead(qint64 bytes); int bytesRead() const; int port() const; void setPort(quint16); bool isPaused() const; void setSerialPort(const QString& name); QString serialPortName() const; QString host() const; void setHost(const QString&); int baudRate() const; void setBaudRate(int); void setUpdateInterval(int); int updateInterval() const; void setKeepNValues(int); int keepNValues() const; void setKeepLastValues(bool); bool keepLastValues() const; - void setFileWatched(bool); - bool isFileWatched() const; - void setFileLinked(bool); bool isFileLinked() const; + void setUseRelativePath(bool); + bool useRelativePath() const; + void setFileName(const QString&); QString fileName() const; void setLocalSocketName(const QString&); QString localSocketName() const; void updateNow(); void pauseReading(); void continueReading(); void setFilter(AbstractFileFilter*); AbstractFileFilter* filter() const; QIcon icon() const override; QMenu* createContextMenu() override; QWidget* view() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; + void finalizeLoad(); private: void initActions(); - void watch(); QString m_fileName; QString m_serialPortName; QString m_localSocketName; QString m_host; AbstractFileFilter::FileType m_fileType{AbstractFileFilter::Ascii}; UpdateType m_updateType; SourceType m_sourceType; ReadingType m_readingType; bool m_fileWatched{false}; bool m_fileLinked{false}; + bool m_relativePath{false}; bool m_paused{false}; bool m_prepared{false}; bool m_reading{false}; int m_sampleSize{1}; int m_keepNValues{0}; // number of values to keep (0 - all) int m_updateInterval{1000}; quint16 m_port{1027}; int m_baudRate{9600}; qint64 m_bytesRead{0}; AbstractFileFilter* m_filter{nullptr}; QTimer* m_updateTimer; QFileSystemWatcher* m_fileSystemWatcher{nullptr}; QFile* m_file{nullptr}; QLocalSocket* m_localSocket{nullptr}; QTcpSocket* m_tcpSocket{nullptr}; QUdpSocket* m_udpSocket{nullptr}; QSerialPort* m_serialPort{nullptr}; QIODevice* m_device{nullptr}; - - QAction* m_reloadAction; - QAction* m_toggleLinkAction; - QAction* m_showEditorAction; - QAction* m_showSpreadsheetAction; - QAction* m_plotDataAction; + QAction* m_plotDataAction{nullptr}; public slots: void read(); private slots: - void watchToggled(); - void linkToggled(); void plotData(); - void readyRead(); void localSocketError(QLocalSocket::LocalSocketError); void tcpSocketError(QAbstractSocket::SocketError); void serialPortError(QSerialPort::SerialPortError); }; #endif diff --git a/src/backend/worksheet/Worksheet.cpp b/src/backend/worksheet/Worksheet.cpp index 6beeecc4d..632efb483 100644 --- a/src/backend/worksheet/Worksheet.cpp +++ b/src/backend/worksheet/Worksheet.cpp @@ -1,1575 +1,1559 @@ /*************************************************************************** File : Worksheet.cpp Project : LabPlot Description : Worksheet -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) 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 "Worksheet.h" #include "WorksheetPrivate.h" #include "WorksheetElement.h" #include "commonfrontend/worksheet/WorksheetView.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/TreeModel.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "kdefrontend/worksheet/ExportWorksheetDialog.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include #include #include /** * \class Worksheet * \brief Top-level container for worksheet elements like plot, labels, etc. * * The worksheet is, besides the data containers \c Spreadsheet and \c Matrix, another central part of the application * and provides an area for showing and grouping together different kinds of worksheet objects - plots, labels &etc; * * * \ingroup worksheet */ Worksheet::Worksheet(const QString& name, bool loading) : AbstractPart(name, AspectType::Worksheet), d(new WorksheetPrivate(this)) { connect(this, &Worksheet::aspectAdded, this, &Worksheet::handleAspectAdded); connect(this, &Worksheet::aspectAboutToBeRemoved, this, &Worksheet::handleAspectAboutToBeRemoved); connect(this, &Worksheet::aspectRemoved, this, &Worksheet::handleAspectRemoved); if (!loading) init(); } Worksheet::~Worksheet() { delete d; } void Worksheet::init() { KConfig config; KConfigGroup group = config.group("Worksheet"); //size d->scaleContent = group.readEntry("ScaleContent", false); d->useViewSize = group.readEntry("UseViewSize", false); d->pageRect.setX(0); d->pageRect.setY(0); d->pageRect.setWidth(group.readEntry("Width", 1500)); d->pageRect.setHeight(group.readEntry("Height", 1500)); d->m_scene->setSceneRect(d->pageRect); //background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int) PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //layout d->layout = (Worksheet::Layout) group.readEntry("Layout", (int) Worksheet::VerticalLayout); d->layoutTopMargin = group.readEntry("LayoutTopMargin", convertToSceneUnits(1, Centimeter)); d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", convertToSceneUnits(1, Centimeter)); d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", convertToSceneUnits(1, Centimeter)); d->layoutRightMargin = group.readEntry("LayoutRightMargin", convertToSceneUnits(1, Centimeter)); d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", convertToSceneUnits(1, Centimeter)); d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", convertToSceneUnits(1, Centimeter)); d->layoutRowCount = group.readEntry("LayoutRowCount", 2); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 2); //default theme KConfigGroup settings = KSharedConfig::openConfig()->group(QLatin1String("Settings_Worksheet")); d->theme = settings.readEntry(QStringLiteral("Theme"), QString()); if (!d->theme.isEmpty()) loadTheme(d->theme); } /*! converts from \c unit to the scene units. At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertToSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { case Worksheet::Millimeter: return value*10.0; case Worksheet::Centimeter: return value*100.0; case Worksheet::Inch: return value*25.4*10.; case Worksheet::Point: return value*25.4/72.*10.; } return 0; } /*! converts from the scene units to \c unit . At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertFromSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { case Worksheet::Millimeter: return value/10.0; case Worksheet::Centimeter: return value/100.0; case Worksheet::Inch: return value/25.4/10.; case Worksheet::Point: return value/25.4/10.*72.; } return 0; } QIcon Worksheet::icon() const { return QIcon::fromTheme("labplot-worksheet"); } /** * Return a new context menu. The caller takes ownership of the menu. */ QMenu* Worksheet::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); Q_ASSERT(menu); emit requestProjectContextMenu(menu); return menu; } //! Construct a primary view on me. /** * This method may be called multiple times during the life time of an Aspect, or it might not get * called at all. Aspects must not depend on the existence of a view for their operation. */ QWidget* Worksheet::view() const { if (!m_partView) { m_view = new WorksheetView(const_cast(this)); m_partView = m_view; connect(m_view, &WorksheetView::statusInfo, this, &Worksheet::statusInfo); } return m_partView; } /*! * returns the list of all parent aspects (folders and sub-folders) * together with all the data containers required to plot the data in the worksheet */ QVector Worksheet::dependsOn() const { //add all parent aspects (folders and sub-folders) QVector aspects = AbstractAspect::dependsOn(); //traverse all plots and add all data containers they depend on for (const auto* plot : children()) aspects << plot->dependsOn(); return aspects; } bool Worksheet::exportView() const { auto* dlg = new ExportWorksheetDialog(m_view); dlg->setFileName(name()); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { QString path = dlg->path(); const WorksheetView::ExportFormat format = dlg->exportFormat(); const WorksheetView::ExportArea area = dlg->exportArea(); const bool background = dlg->exportBackground(); const int resolution = dlg->exportResolution(); WAIT_CURSOR; m_view->exportToFile(path, format, area, background, resolution); RESET_CURSOR; } delete dlg; return ret; } bool Worksheet::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, m_view); dlg->setWindowTitle(i18nc("@title:window", "Print Worksheet")); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool Worksheet::printPreview() const { auto* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &WorksheetView::print); return dlg->exec(); } void Worksheet::handleAspectAdded(const AbstractAspect* aspect) { const auto* addedElement = qobject_cast(aspect); if (!addedElement) return; if (aspect->parentAspect() != this) return; //add the GraphicsItem of the added child to the scene QGraphicsItem* item = addedElement->graphicsItem(); d->m_scene->addItem(item); const CartesianPlot* plot = dynamic_cast(aspect); if (plot) { - connect(plot, &CartesianPlot::mouseMoveCursorModeSignal, this, &Worksheet::cartesianPlotmouseMoveCursorMode); - connect(plot, &CartesianPlot::mouseMoveZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmouseMoveZoomSelectionMode); - connect(plot, &CartesianPlot::mousePressCursorModeSignal, this, &Worksheet::cartesianPlotmousePressCursorMode); - connect(plot, &CartesianPlot::mousePressZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmousePressZoomSelectionMode); - connect(plot, &CartesianPlot::mouseReleaseZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmouseReleaseZoomSelectionMode); - connect(plot, &CartesianPlot::mouseHoverZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmouseHoverZoomSelectionMode); + connect(plot, &CartesianPlot::mouseMoveCursorModeSignal, this, &Worksheet::cartesianPlotMouseMoveCursorMode); + connect(plot, &CartesianPlot::mouseMoveZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseMoveZoomSelectionMode); + connect(plot, &CartesianPlot::mousePressCursorModeSignal, this, &Worksheet::cartesianPlotMousePressCursorMode); + connect(plot, &CartesianPlot::mousePressZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMousePressZoomSelectionMode); + connect(plot, &CartesianPlot::mouseReleaseZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseReleaseZoomSelectionMode); + connect(plot, &CartesianPlot::mouseHoverZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseHoverZoomSelectionMode); connect(plot, &CartesianPlot::curveRemoved, this, &Worksheet::curveRemoved); connect(plot, &CartesianPlot::curveAdded, this, &Worksheet::curveAdded); connect(plot, &CartesianPlot::visibleChanged, this, &Worksheet::updateCompleteCursorTreeModel); connect(plot, &CartesianPlot::curveVisibilityChangedSignal, this, &Worksheet::updateCompleteCursorTreeModel); connect(plot, &CartesianPlot::curveDataChanged, this, &Worksheet::curveDataChanged); connect(plot, static_cast(&CartesianPlot::curveLinePenChanged), this, &Worksheet::updateCurveBackground); - connect(plot, &CartesianPlot::mouseModeChanged, this, &Worksheet::cartesianPlotmouseModeChanged); + connect(plot, &CartesianPlot::mouseModeChanged, this, &Worksheet::cartesianPlotMouseModeChangedSlot); auto* p = const_cast(plot); p->setLocked(d->plotsLocked); cursorModelPlotAdded(p->name()); } qreal zVal = 0; for (auto* child : children(IncludeHidden)) child->graphicsItem()->setZValue(zVal++); //if a theme was selected in the worksheet, apply this theme for newly added children if (!d->theme.isEmpty() && !isLoading()) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(addedElement)->loadThemeConfig(config); } //recalculated the layout if (!isLoading()) { if (d->layout != Worksheet::NoLayout) d->updateLayout(false); } } void Worksheet::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const auto* removedElement = qobject_cast(aspect); if (removedElement) { QGraphicsItem* item = removedElement->graphicsItem(); d->m_scene->removeItem(item); } } void Worksheet::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); if (d->layout != Worksheet::NoLayout) d->updateLayout(false); auto* plot = dynamic_cast(child); if (plot) cursorModelPlotRemoved(plot->name()); - } QGraphicsScene* Worksheet::scene() const { return d->m_scene; } QRectF Worksheet::pageRect() const { return d->m_scene->sceneRect(); } /*! this slot is called when a worksheet element is selected in the project explorer. emits \c itemSelected() which forwards this event to the \c WorksheetView in order to select the corresponding \c QGraphicsItem. */ void Worksheet::childSelected(const AbstractAspect* aspect) { auto* element = qobject_cast(const_cast(aspect)); if (element) emit itemSelected(element->graphicsItem()); } /*! this slot is called when a worksheet element is deselected in the project explorer. emits \c itemDeselected() which forwards this event to \c WorksheetView in order to deselect the corresponding \c QGraphicsItem. */ void Worksheet::childDeselected(const AbstractAspect* aspect) { auto* element = qobject_cast(const_cast(aspect)); if (element) emit itemDeselected(element->graphicsItem()); } /*! * Emits the signal to select or to deselect the aspect corresponding to \c QGraphicsItem \c item in the project explorer, * if \c selected=true or \c selected=false, respectively. * The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer. * This function is called in \c WorksheetView upon selection changes. */ void Worksheet::setItemSelectedInView(const QGraphicsItem* item, const bool b) { //determine the corresponding aspect const AbstractAspect* aspect(nullptr); for (const auto* child : children(IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; //forward selection/deselection to AbstractTreeModel if (b) emit childAspectSelectedInView(aspect); else emit childAspectDeselectedInView(aspect); } /*! * helper function: checks whether \c aspect or one of its children has the \c GraphicsItem \c item * Returns a pointer to \c WorksheetElement having this item. */ WorksheetElement* Worksheet::aspectFromGraphicsItem(const WorksheetElement* aspect, const QGraphicsItem* item) const { if ( aspect->graphicsItem() == item ) return const_cast(aspect); else { for (const auto* child : aspect->children(AbstractAspect::IncludeHidden) ) { WorksheetElement* a = this->aspectFromGraphicsItem(child, item); if (a) return a; } return nullptr; } } /*! Selects or deselects the worksheet in the project explorer. This function is called in \c WorksheetView. The worksheet gets deselected if there are selected items in the view, and selected if there are no selected items in the view. */ void Worksheet::setSelectedInView(const bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } void Worksheet::deleteAspectFromGraphicsItem(const QGraphicsItem* item) { Q_ASSERT(item); //determine the corresponding aspect AbstractAspect* aspect(nullptr); for (const auto* child : children(IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; if (aspect->parentAspect()) aspect->parentAspect()->removeChild(aspect); else this->removeChild(aspect); } void Worksheet::setIsClosing() { if (m_view) m_view->setIsClosing(); } /*! * \brief Worksheet::getPlotCount * \return number of CartesianPlot's in the Worksheet */ int Worksheet::getPlotCount() { return children().length(); } /*! * \brief Worksheet::getPlot * \param index Number of plot which should be returned * \return Pointer to the CartesianPlot which was searched with index */ WorksheetElement *Worksheet::getPlot(int index) { QVector cartesianPlots = children(); if (cartesianPlots.length()-1 >= index) return cartesianPlots[index]; return nullptr; } TreeModel* Worksheet::cursorModel() { return d->cursorData; } void Worksheet::update() { emit requestUpdate(); } void Worksheet::setSuppressLayoutUpdate(bool value) { d->suppressLayoutUpdate = value; } void Worksheet::updateLayout() { d->updateLayout(); } Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotActionMode() { return d->cartesianPlotActionMode; } Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotCursorMode() { return d->cartesianPlotCursorMode; } bool Worksheet::plotsLocked() { return d->plotsLocked; } void Worksheet::setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode mode) { if (d->cartesianPlotActionMode == mode) return; d->cartesianPlotActionMode = mode; project()->setChanged(true); } void Worksheet::setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode mode) { if (d->cartesianPlotCursorMode == mode) return; d->cartesianPlotCursorMode = mode; if (mode == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { d->suppressCursorPosChanged = true; QVector plots = children(); QPointF logicPos; if (!plots.isEmpty()) { for (int i = 0; i < 2; i++) { logicPos = QPointF(plots[0]->cursorPos(i), 0); // y value does not matter - cartesianPlotmousePressCursorMode(i, logicPos); + cartesianPlotMousePressCursorMode(i, logicPos); } } d->suppressCursorPosChanged = false; } updateCompleteCursorTreeModel(); project()->setChanged(true); } void Worksheet::setPlotsLocked(bool lock) { if (d->plotsLocked == lock) return; d->plotsLocked = lock; for (auto* plot: children()) plot->setLocked(lock); project()->setChanged(true); } void Worksheet::registerShortcuts() { m_view->registerShortcuts(); } void Worksheet::unregisterShortcuts() { m_view->unregisterShortcuts(); } /* =============================== getter methods for general options ==================================== */ BASIC_D_READER_IMPL(Worksheet, bool, scaleContent, scaleContent) BASIC_D_READER_IMPL(Worksheet, bool, useViewSize, useViewSize) /* =============================== getter methods for background options ================================= */ BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_D_READER_IMPL(Worksheet, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_D_READER_IMPL(Worksheet, QString, backgroundFileName, backgroundFileName) BASIC_D_READER_IMPL(Worksheet, float, backgroundOpacity, backgroundOpacity) /* =============================== getter methods for layout options ====================================== */ BASIC_D_READER_IMPL(Worksheet, Worksheet::Layout, layout, layout) BASIC_D_READER_IMPL(Worksheet, float, layoutTopMargin, layoutTopMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutBottomMargin, layoutBottomMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutLeftMargin, layoutLeftMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutRightMargin, layoutRightMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_D_READER_IMPL(Worksheet, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_D_READER_IMPL(Worksheet, int, layoutRowCount, layoutRowCount) BASIC_D_READER_IMPL(Worksheet, int, layoutColumnCount, layoutColumnCount) CLASS_D_READER_IMPL(Worksheet, QString, theme, theme) /* ============================ setter methods and undo commands for general options ===================== */ void Worksheet::setUseViewSize(bool useViewSize) { if (useViewSize != d->useViewSize) { d->useViewSize = useViewSize; emit useViewSizeRequested(); } } STD_SETTER_CMD_IMPL_S(Worksheet, SetScaleContent, bool, scaleContent) void Worksheet::setScaleContent(bool scaleContent) { if (scaleContent != d->scaleContent) exec(new WorksheetSetScaleContentCmd(d, scaleContent, ki18n("%1: change \"rescale the content\" property"))); } /* ============================ setter methods and undo commands for background options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void Worksheet::setBackgroundType(PlotArea::BackgroundType type) { if (type != d->backgroundType) exec(new WorksheetSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void Worksheet::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { if (style != d->backgroundColorStyle) exec(new WorksheetSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void Worksheet::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { if (style != d->backgroundImageStyle) exec(new WorksheetSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void Worksheet::setBackgroundBrushStyle(Qt::BrushStyle style) { if (style != d->backgroundBrushStyle) exec(new WorksheetSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void Worksheet::setBackgroundFirstColor(const QColor &color) { if (color!= d->backgroundFirstColor) exec(new WorksheetSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void Worksheet::setBackgroundSecondColor(const QColor &color) { if (color!= d->backgroundSecondColor) exec(new WorksheetSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFileName, QString, backgroundFileName, update) void Worksheet::setBackgroundFileName(const QString& fileName) { if (fileName!= d->backgroundFileName) exec(new WorksheetSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundOpacity, float, backgroundOpacity, update) void Worksheet::setBackgroundOpacity(float opacity) { if (opacity != d->backgroundOpacity) exec(new WorksheetSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set opacity"))); } /* ============================ setter methods and undo commands for layout options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetLayout, Worksheet::Layout, layout, updateLayout) void Worksheet::setLayout(Worksheet::Layout layout) { if (layout != d->layout) { beginMacro(i18n("%1: set layout", name())); exec(new WorksheetSetLayoutCmd(d, layout, ki18n("%1: set layout"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutTopMargin, float, layoutTopMargin, updateLayout) void Worksheet::setLayoutTopMargin(float margin) { if (margin != d->layoutTopMargin) { beginMacro(i18n("%1: set layout top margin", name())); exec(new WorksheetSetLayoutTopMarginCmd(d, margin, ki18n("%1: set layout top margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutBottomMargin, float, layoutBottomMargin, updateLayout) void Worksheet::setLayoutBottomMargin(float margin) { if (margin != d->layoutBottomMargin) { beginMacro(i18n("%1: set layout bottom margin", name())); exec(new WorksheetSetLayoutBottomMarginCmd(d, margin, ki18n("%1: set layout bottom margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutLeftMargin, float, layoutLeftMargin, updateLayout) void Worksheet::setLayoutLeftMargin(float margin) { if (margin != d->layoutLeftMargin) { beginMacro(i18n("%1: set layout left margin", name())); exec(new WorksheetSetLayoutLeftMarginCmd(d, margin, ki18n("%1: set layout left margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRightMargin, float, layoutRightMargin, updateLayout) void Worksheet::setLayoutRightMargin(float margin) { if (margin != d->layoutRightMargin) { beginMacro(i18n("%1: set layout right margin", name())); exec(new WorksheetSetLayoutRightMarginCmd(d, margin, ki18n("%1: set layout right margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, updateLayout) void Worksheet::setLayoutVerticalSpacing(float spacing) { if (spacing != d->layoutVerticalSpacing) { beginMacro(i18n("%1: set layout vertical spacing", name())); exec(new WorksheetSetLayoutVerticalSpacingCmd(d, spacing, ki18n("%1: set layout vertical spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, updateLayout) void Worksheet::setLayoutHorizontalSpacing(float spacing) { if (spacing != d->layoutHorizontalSpacing) { beginMacro(i18n("%1: set layout horizontal spacing", name())); exec(new WorksheetSetLayoutHorizontalSpacingCmd(d, spacing, ki18n("%1: set layout horizontal spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRowCount, int, layoutRowCount, updateLayout) void Worksheet::setLayoutRowCount(int count) { if (count != d->layoutRowCount) { beginMacro(i18n("%1: set layout row count", name())); exec(new WorksheetSetLayoutRowCountCmd(d, count, ki18n("%1: set layout row count"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutColumnCount, int, layoutColumnCount, updateLayout) void Worksheet::setLayoutColumnCount(int count) { if (count != d->layoutColumnCount) { beginMacro(i18n("%1: set layout column count", name())); exec(new WorksheetSetLayoutColumnCountCmd(d, count, ki18n("%1: set layout column count"))); endMacro(); } } class WorksheetSetPageRectCmd : public StandardMacroSetterCmd { public: WorksheetSetPageRectCmd(Worksheet::Private* target, QRectF newValue, const KLocalizedString& description) : StandardMacroSetterCmd(target, &Worksheet::Private::pageRect, newValue, description) {} void finalize() override { m_target->updatePageRect(); emit m_target->q->pageRectChanged(m_target->*m_field); } void finalizeUndo() override { m_target->m_scene->setSceneRect(m_target->*m_field); emit m_target->q->pageRectChanged(m_target->*m_field); } }; void Worksheet::setPageRect(const QRectF& rect) { //don't allow any rectangulars of width/height equal to zero if (qFuzzyCompare(rect.width(), 0.) || qFuzzyCompare(rect.height(), 0.)) { emit pageRectChanged(d->pageRect); return; } if (rect != d->pageRect) { if (!d->useViewSize) { beginMacro(i18n("%1: set page size", name())); exec(new WorksheetSetPageRectCmd(d, rect, ki18n("%1: set page size"))); endMacro(); } else { d->pageRect = rect; d->updatePageRect(); emit pageRectChanged(d->pageRect); } } } void Worksheet::setPrinting(bool on) const { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) child->setPrinting(on); } STD_SETTER_CMD_IMPL_S(Worksheet, SetTheme, QString, theme) void Worksheet::setTheme(const QString& theme) { if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } else { exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: disable theming"))); } } } -void Worksheet::cartesianPlotmousePressZoomSelectionMode(QPointF logicPos) { +void Worksheet::cartesianPlotMousePressZoomSelectionMode(QPointF logicPos) { if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) { CartesianPlot* plot = dynamic_cast(child); if (plot) plot->mousePressZoomSelectionMode(logicPos); } return; } CartesianPlot* plot = dynamic_cast(QObject::sender()); plot->mousePressZoomSelectionMode(logicPos); } -void Worksheet::cartesianPlotmouseReleaseZoomSelectionMode() { +void Worksheet::cartesianPlotMouseReleaseZoomSelectionMode() { if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) { CartesianPlot* plot = dynamic_cast(child); if (plot) plot->mouseReleaseZoomSelectionMode(); } return; } CartesianPlot* plot = dynamic_cast(QObject::sender()); plot->mouseReleaseZoomSelectionMode(); } -void Worksheet::cartesianPlotmousePressCursorMode(int cursorNumber, QPointF logicPos) { +void Worksheet::cartesianPlotMousePressCursorMode(int cursorNumber, QPointF logicPos) { if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) { CartesianPlot* plot = dynamic_cast(child); if (plot) plot->mousePressCursorMode(cursorNumber, logicPos); } } else { CartesianPlot* plot = dynamic_cast(QObject::sender()); if (plot) plot->mousePressCursorMode(cursorNumber, logicPos); } cursorPosChanged(cursorNumber, logicPos.x()); } -void Worksheet::cartesianPlotmouseMoveZoomSelectionMode(QPointF logicPos) { +void Worksheet::cartesianPlotMouseMoveZoomSelectionMode(QPointF logicPos) { if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) { CartesianPlot* plot = dynamic_cast(child); if (plot) plot->mouseMoveZoomSelectionMode(logicPos); } return; } CartesianPlot* plot = dynamic_cast(QObject::sender()); plot->mouseMoveZoomSelectionMode(logicPos); } -void Worksheet::cartesianPlotmouseHoverZoomSelectionMode(QPointF logicPos) { +void Worksheet::cartesianPlotMouseHoverZoomSelectionMode(QPointF logicPos) { if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) { CartesianPlot* plot = dynamic_cast(child); if (plot) plot->mouseHoverZoomSelectionMode(logicPos); } return; } CartesianPlot* plot = dynamic_cast(QObject::sender()); plot->mouseHoverZoomSelectionMode(logicPos); } -void Worksheet::cartesianPlotmouseMoveCursorMode(int cursorNumber, QPointF logicPos) { +void Worksheet::cartesianPlotMouseMoveCursorMode(int cursorNumber, QPointF logicPos) { if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) { CartesianPlot* plot = dynamic_cast(child); if (plot) plot->mouseMoveCursorMode(cursorNumber, logicPos); } } else { CartesianPlot* plot = dynamic_cast(QObject::sender()); plot->mouseMoveCursorMode(cursorNumber, logicPos); } cursorPosChanged(cursorNumber, logicPos.x()); } /*! * \brief Worksheet::cursorPosChanged * Updates the cursor treemodel with the new data * \param xPos: new position of the cursor * It is assumed, that the plots/curves are in the same order than receiving from * the children() function. It's not checked if the names are the same */ void Worksheet::cursorPosChanged(int cursorNumber, double xPos) { if (d->suppressCursorPosChanged) return; TreeModel* treeModel = cursorModel(); auto* sender = dynamic_cast(QObject::sender()); // if ApplyActionToSelection, each plot has it's own x value int rowPlot = 0; if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) { // x values rowPlot = 1; QModelIndex xName = treeModel->index(0, WorksheetPrivate::TreeModelColumn::SIGNALNAME); treeModel->setData(xName, QVariant("X")); double valueCursor[2]; for (int i = 0; i < 2; i++) { // need both cursors to calculate diff valueCursor[i] = sender->cursorPos(i); treeModel->setTreeData(QVariant(valueCursor[i]), 0, WorksheetPrivate::TreeModelColumn::CURSOR0+i); } treeModel->setTreeData(QVariant(valueCursor[1] - valueCursor[0]), 0, WorksheetPrivate::TreeModelColumn::CURSORDIFF); // y values for (int i = 0; i < getPlotCount(); i++) { // i=0 is the x Axis auto* plot = dynamic_cast(getPlot(i)); if (!plot || !plot->isVisible()) continue; QModelIndex plotIndex = treeModel->index(rowPlot, WorksheetPrivate::TreeModelColumn::PLOTNAME); // curves int rowCurve = 0; for (int j = 0; j < plot->curveCount(); j++) { // assumption: index of signals in model is the same than the index of the signal in the plot bool valueFound; const XYCurve* curve = plot->getCurve(j); if (!curve->isVisible()) continue; double value = curve->y(xPos, valueFound); if (cursorNumber == 0) { treeModel->setTreeData(QVariant(value), rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); double valueCursor1 = treeModel->treeData(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex).toDouble(); treeModel->setTreeData(QVariant(valueCursor1 - value), rowCurve, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); } else { treeModel->setTreeData(QVariant(value), rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); double valueCursor0 = treeModel->treeData(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex).toDouble(); treeModel->setTreeData(QVariant(value - valueCursor0), rowCurve, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); } rowCurve++; } rowPlot++; } } else { // apply to selection // assumption: plot is visible int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); if (plotIndex.data().toString().compare(sender->name()) != 0) continue; // x values (first row always exist) treeModel->setTreeData(QVariant("X"), 0, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex); double valueCursor[2]; for (int i = 0; i < 2; i++) { // need both cursors to calculate diff valueCursor[i] = sender->cursorPos(i); treeModel->setTreeData(QVariant(valueCursor[i]), 0, WorksheetPrivate::TreeModelColumn::CURSOR0+i, plotIndex); } treeModel->setTreeData(QVariant(valueCursor[1]-valueCursor[0]), 0, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); // y values int rowCurve = 1; // first is x value for (int j = 0; j< sender->curveCount(); j++) { // j=0 are the x values const XYCurve* curve = sender->getCurve(j); // -1 because we start with 1 for the x axis if (!curve->isVisible()) continue; // assumption: index of signals in model is the same than the index of the signal in the plot bool valueFound; double value = curve->y(xPos, valueFound); if (cursorNumber == 0) { treeModel->setTreeData(QVariant(value), rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); double valueCursor1 = treeModel->treeData(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex).toDouble(); treeModel->setTreeData(QVariant(valueCursor1 - value), rowCurve, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); } else { treeModel->setTreeData(QVariant(value), rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); double valueCursor0 = treeModel->treeData(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex).toDouble(); treeModel->setTreeData(QVariant(value - valueCursor0), rowCurve, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); } rowCurve++; } } } } void Worksheet::cursorModelPlotAdded(QString name) { TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); // add plot at the end treeModel->insertRows(rowCount, 1); // add empty rows. Then they become filled treeModel->setTreeData(QVariant(name), rowCount, WorksheetPrivate::TreeModelColumn::PLOTNAME); // rowCount instead of rowCount -1 because first row is the x value } void Worksheet::cursorModelPlotRemoved(QString name) { TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); // first is x Axis for (int i = 1; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); if (plotIndex.data().toString().compare(name) != 0) continue; treeModel->removeRows(plotIndex.row(), 1); return; } } -void Worksheet::cartesianPlotmouseModeChanged() { - // assumption: only called from a CartesianPlot - auto* plot = static_cast(QObject::sender()); +void Worksheet::cartesianPlotMouseModeChangedSlot(CartesianPlot::MouseMode mode) { if (d->updateCompleteCursorModel) { updateCompleteCursorTreeModel(); d->updateCompleteCursorModel = false; } - // If cursor dock is closed open it only, when the MouseMode is set to cursor. - // If it is already open, let it open, so the gui does not change. - if (plot->mouseMode() == CartesianPlot::MouseMode::Cursor) - emit showCursorDock(cursorModel(), children()); + emit cartesianPlotMouseModeChanged(mode); } void Worksheet::curveDataChanged(const XYCurve* curve) { auto* plot = dynamic_cast(QObject::sender()); if (!plot) return; TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; for (int j = 0; j < plot->curveCount(); j++) { if (plot->getCurve(j)->name().compare(curve->name()) != 0) continue; treeModel->setTreeData(QVariant(curve->name()), j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex); bool valueFound; double valueCursor0 = curve->y(plot->cursorPos(0), valueFound); treeModel->setTreeData(QVariant(valueCursor0), j, WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); double valueCursor1 = curve->y(plot->cursorPos(1), valueFound); treeModel->setTreeData(QVariant(valueCursor1), j, WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); treeModel->setTreeData(QVariant(valueCursor1-valueCursor0), j, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); break; } break; } } void Worksheet::curveAdded(const XYCurve* curve) { auto* plot = dynamic_cast(QObject::sender()); if (!plot) return; TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); // first row is the x axis, so starting at the second row for (int i = 1; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; for (int j = 0; j < plot->curveCount(); j++) { if (plot->getCurve(j)->name().compare(curve->name()) != 0) continue; treeModel->insertRow(j, plotIndex); QModelIndex curveIndex = treeModel->index(j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex); treeModel->setData(curveIndex, QVariant(curve->name())); bool valueFound; double valueCursor0 = curve->y(plot->cursorPos(0), valueFound); treeModel->setTreeData(QVariant(valueCursor0), j, WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); double valueCursor1 = curve->y(plot->cursorPos(1), valueFound); treeModel->setTreeData(QVariant(valueCursor1), j, WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); treeModel->setTreeData(QVariant(valueCursor1-valueCursor0), j, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); break; } break; } } void Worksheet::curveRemoved(const XYCurve* curve) { auto* plot = dynamic_cast(QObject::sender()); if (!plot) return; TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; int curveCount = treeModel->rowCount(plotIndex); for (int j = 0; j < curveCount; j++) { QModelIndex curveIndex = treeModel->index(j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex); if (curveIndex.data().toString().compare(curve->name()) != 0) continue; treeModel->removeRow(j, plotIndex); break; } break; } } /*! * Updates the background of the cuves entry in the treeview * @param pen Pen of the curve * @param curveName Curve name to find in treemodel */ void Worksheet::updateCurveBackground(QPen pen, QString curveName) { const CartesianPlot* plot = static_cast(QObject::sender()); TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; int curveCount = treeModel->rowCount(plotIndex); for (int j = 0; j < curveCount; j++) { QModelIndex curveIndex = treeModel->index(j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex); if (curveIndex.data().toString().compare(curveName) != 0) continue; QColor curveColor = pen.color(); curveColor.setAlpha(50); treeModel->setTreeData(QVariant(curveColor), j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex, Qt::BackgroundRole); return; } return; } } /** * @brief Worksheet::updateCompleteCursorTreeModel * If the plot or the curve are not available, the plot/curve is not in the treemodel! */ void Worksheet::updateCompleteCursorTreeModel() { TreeModel* treeModel = cursorModel(); treeModel->removeRows(0,treeModel->rowCount()); // remove all data int plotCount = getPlotCount(); if (plotCount < 1) return; int rowPlot = 0; if (cartesianPlotCursorMode() == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { // 1 because of the X data treeModel->insertRows(0, 1); //, treeModel->index(0,0)); // add empty rows. Then they become filled rowPlot = 1; // set X data QModelIndex xName = treeModel->index(0, WorksheetPrivate::TreeModelColumn::SIGNALNAME); treeModel->setData(xName, QVariant("X")); CartesianPlot* plot0 = dynamic_cast(getPlot(0)); double valueCursor[2]; for (int i = 0; i < 2; i++) { valueCursor[i] = plot0->cursorPos(i); QModelIndex cursor = treeModel->index(0,WorksheetPrivate::TreeModelColumn::CURSOR0+i); treeModel->setData(cursor, QVariant(valueCursor[i])); } QModelIndex diff = treeModel->index(0,WorksheetPrivate::TreeModelColumn::CURSORDIFF); treeModel->setData(diff, QVariant(valueCursor[1]-valueCursor[0])); } else { //treeModel->insertRows(0, plotCount, treeModel->index(0,0)); // add empty rows. Then they become filled } // set plot name, y value, background for (int i = 0; i < plotCount; i++) { CartesianPlot* plot = dynamic_cast(getPlot(i)); QModelIndex plotName; int addOne = 0; if (!plot || !plot->isVisible()) continue; // add new entry for the plot treeModel->insertRows(treeModel->rowCount(), 1); //, treeModel->index(0, 0)); if (cartesianPlotCursorMode() == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { plotName = treeModel->index(i + 1, WorksheetPrivate::TreeModelColumn::PLOTNAME); // plus one because first row are the x values treeModel->setData(plotName, QVariant(plot->name())); } else { addOne = 1; plotName = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); treeModel->setData(plotName, QVariant(plot->name())); treeModel->insertRows(0, 1, plotName); // one, because the first row are the x values QModelIndex xName = treeModel->index(0, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotName); treeModel->setData(xName, QVariant("X")); double valueCursor[2]; for (int i = 0; i < 2; i++) { valueCursor[i] = plot->cursorPos(i); QModelIndex cursor = treeModel->index(0, WorksheetPrivate::TreeModelColumn::CURSOR0+i, plotName); treeModel->setData(cursor, QVariant(valueCursor[i])); } QModelIndex diff = treeModel->index(0, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotName); treeModel->setData(diff, QVariant(valueCursor[1]-valueCursor[0])); } int rowCurve = addOne; for (int j = 0; j < plot->curveCount(); j++) { double cursorValue[2] = {NAN, NAN}; const XYCurve* curve = plot->getCurve(j); if (!curve->isVisible()) continue; for (int k = 0; k < 2; k++) { double xPos = plot->cursorPos(k); bool valueFound; cursorValue[k] = curve->y(xPos,valueFound); } treeModel->insertRows(rowCurve, 1, plotName); - QModelIndex backgroundColor = treeModel->index(rowCurve, 0, plotName); QColor curveColor = curve->linePen().color(); curveColor.setAlpha(50); - treeModel->setData(backgroundColor, QVariant(curveColor), Qt::BackgroundRole); - QModelIndex signalName = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotName); - treeModel->setData(signalName, QVariant(curve->name())); - QModelIndex signalCursor0 = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR0, plotName); - treeModel->setData(signalCursor0, QVariant(cursorValue[0])); - QModelIndex signalCursor2 = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR1, plotName); - treeModel->setData(signalCursor2, QVariant(cursorValue[1])); - QModelIndex differenceValues = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotName); - treeModel->setData(differenceValues, QVariant(cursorValue[1]-cursorValue[0])); - + treeModel->setTreeData(QVariant(curveColor), rowCurve, 0, plotName, Qt::BackgroundRole); + treeModel->setTreeData(QVariant(curve->name()), rowCurve, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotName); + treeModel->setTreeData(QVariant(cursorValue[0]), rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR0, plotName); + treeModel->setTreeData(QVariant(cursorValue[1]), rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR1, plotName); + treeModel->setTreeData(QVariant(cursorValue[1]-cursorValue[0]), rowCurve, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotName); rowCurve++; } rowPlot++; } } //############################################################################## //###################### Private implementation ############################### //############################################################################## WorksheetPrivate::WorksheetPrivate(Worksheet* owner) : q(owner), m_scene(new QGraphicsScene()) { QStringList headers = {i18n("Plot/Curve"), "V1", "V2", "V2-V1"}; cursorData = new TreeModel(headers, nullptr); } QString WorksheetPrivate::name() const { return q->name(); } /*! * called if the worksheet page (the actual size of worksheet's rectangular) was changed. * if a layout is active, it is is updated - this adjusts the sizes of the elements in the layout to the new page size. * if no layout is active and the option "scale content" is active, \c handleResize() is called to adjust zhe properties. */ void WorksheetPrivate::updatePageRect() { if (q->isLoading()) return; QRectF oldRect = m_scene->sceneRect(); m_scene->setSceneRect(pageRect); if (layout != Worksheet::NoLayout) updateLayout(); else { if (scaleContent) { qreal horizontalRatio = pageRect.width() / oldRect.width(); qreal verticalRatio = pageRect.height() / oldRect.height(); QVector childElements = q->children(AbstractAspect::IncludeHidden); if (useViewSize) { //don't make the change of the geometry undoable/redoable if the view size is used. for (auto* elem : childElements) { elem->setUndoAware(false); elem->handleResize(horizontalRatio, verticalRatio, true); elem->setUndoAware(true); } } else { // for (auto* child : childElements) // child->handleResize(horizontalRatio, verticalRatio, true); } } } } void WorksheetPrivate::update() { q->update(); } WorksheetPrivate::~WorksheetPrivate() { delete m_scene; } void WorksheetPrivate::updateLayout(bool undoable) { if (suppressLayoutUpdate) return; QVector list = q->children(); if (layout == Worksheet::NoLayout) { for (auto* elem : list) elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); return; } float x = layoutLeftMargin; float y = layoutTopMargin; float w, h; int count = list.count(); if (layout == Worksheet::VerticalLayout) { w = m_scene->sceneRect().width() - layoutLeftMargin - layoutRightMargin; h = (m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (count-1)*layoutVerticalSpacing)/count; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); y += h + layoutVerticalSpacing; } } else if (layout == Worksheet::HorizontalLayout) { w = (m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (count-1)*layoutHorizontalSpacing)/count; h = m_scene->sceneRect().height() - layoutTopMargin-layoutBottomMargin; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x += w + layoutHorizontalSpacing; } } else { //GridLayout //add new rows, if not sufficient if (count > layoutRowCount*layoutColumnCount) { layoutRowCount = floor( (float)count/layoutColumnCount + 0.5); emit q->layoutRowCountChanged(layoutRowCount); } w = (m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (layoutColumnCount-1)*layoutHorizontalSpacing)/layoutColumnCount; h = (m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (layoutRowCount-1)*layoutVerticalSpacing)/layoutRowCount; int columnIndex = 0; //counts the columns in a row for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x += w + layoutHorizontalSpacing; columnIndex++; if (columnIndex == layoutColumnCount) { columnIndex = 0; x = layoutLeftMargin; y += h + layoutVerticalSpacing; } } } } void WorksheetPrivate::setContainerRect(WorksheetElementContainer* elem, float x, float y, float h, float w, bool undoable) { if (useViewSize) { //when using the view size, no need to put rect changes onto the undo-stack elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else { //don't put rect changed onto the undo-stack if undoable-flag is set to true, //e.g. when new child is added or removed (the layout and the childrend rects will be updated anyway) if (!undoable) { elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else elem->setRect(QRectF(x,y,w,h)); } elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Worksheet::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "worksheet" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //geometry writer->writeStartElement( "geometry" ); QRectF rect = d->m_scene->sceneRect(); writer->writeAttribute( "x", QString::number(rect.x()) ); writer->writeAttribute( "y", QString::number(rect.y()) ); writer->writeAttribute( "width", QString::number(rect.width()) ); writer->writeAttribute( "height", QString::number(rect.height()) ); writer->writeAttribute( "useViewSize", QString::number(d->useViewSize) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); writer->writeAttribute( "layout", QString::number(d->layout) ); writer->writeAttribute( "topMargin", QString::number(d->layoutTopMargin) ); writer->writeAttribute( "bottomMargin", QString::number(d->layoutBottomMargin) ); writer->writeAttribute( "leftMargin", QString::number(d->layoutLeftMargin) ); writer->writeAttribute( "rightMargin", QString::number(d->layoutRightMargin) ); writer->writeAttribute( "verticalSpacing", QString::number(d->layoutVerticalSpacing) ); writer->writeAttribute( "horizontalSpacing", QString::number(d->layoutHorizontalSpacing) ); writer->writeAttribute( "columnCount", QString::number(d->layoutColumnCount) ); writer->writeAttribute( "rowCount", QString::number(d->layoutRowCount) ); writer->writeEndElement(); //background properties writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); // cartesian properties writer->writeStartElement( "plotProperties" ); writer->writeAttribute( "plotsLocked", QString::number(d->plotsLocked) ); writer->writeAttribute( "cartesianPlotActionMode", QString::number(d->cartesianPlotActionMode)); writer->writeAttribute( "cartesianPlotCursorMode", QString::number(d->cartesianPlotCursorMode)); writer->writeEndElement(); //serialize all children for (auto* child : children(IncludeHidden)) child->save(writer); writer->writeEndElement(); // close "worksheet" section } //! Load from XML bool Worksheet::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; //clear the theme that was potentially set in init() in order to correctly load here the worksheets without any theme used d->theme.clear(); KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; QRectF rect; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "worksheet") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else rect.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else rect.setY(str.toDouble()); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else rect.setWidth(str.toDouble()); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else rect.setHeight(str.toDouble()); READ_INT_VALUE("useViewSize", useViewSize, int); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); READ_INT_VALUE("layout", layout, Worksheet::Layout); READ_DOUBLE_VALUE("topMargin", layoutTopMargin); READ_DOUBLE_VALUE("bottomMargin", layoutBottomMargin); READ_DOUBLE_VALUE("leftMargin", layoutLeftMargin); READ_DOUBLE_VALUE("rightMargin", layoutRightMargin); READ_DOUBLE_VALUE("verticalSpacing", layoutVerticalSpacing); READ_DOUBLE_VALUE("horizontalSpacing", layoutHorizontalSpacing); READ_INT_VALUE("columnCount", layoutColumnCount, int); READ_INT_VALUE("rowCount", layoutRowCount, int); } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); READ_INT_VALUE("type", backgroundType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", backgroundColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", backgroundImageStyle, PlotArea::BackgroundImageStyle); READ_INT_VALUE("brushStyle", backgroundBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; READ_DOUBLE_VALUE("opacity", backgroundOpacity); } else if(!preview && reader->name() == "plotProperties") { attribs = reader->attributes(); READ_INT_VALUE("plotsLocked", plotsLocked, bool); READ_INT_VALUE("cartesianPlotActionMode", cartesianPlotActionMode, Worksheet::CartesianPlotActionMode); READ_INT_VALUE("cartesianPlotCursorMode", cartesianPlotCursorMode, Worksheet::CartesianPlotActionMode); } else if (reader->name() == "cartesianPlot") { CartesianPlot* plot = new CartesianPlot(QString()); plot->setIsLoading(true); if (!plot->load(reader, preview)) { delete plot; return false; } else addChildFast(plot); } else if (reader->name() == "textLabel") { TextLabel* label = new TextLabel(QString()); if (!label->load(reader, preview)) { delete label; return false; } else addChildFast(label); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (!preview) { d->m_scene->setSceneRect(rect); d->updateLayout(); } - // when creating a new CartesianPlot, this plot sends, that new XYCurves where added, - // but after creating the CartesianPlot, the CartesianPlot will be added to the Worksheet, - // where all connections to the signals will be made. So no update will be done automatically - updateCompleteCursorTreeModel(); return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Worksheet::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); //apply the same background color for Worksheet as for the CartesianPlot const KConfigGroup group = config.group("CartesianPlot"); this->setBackgroundBrushStyle((Qt::BrushStyle)group.readEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle())); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle)(group.readEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()))); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor())); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle)group.readEntry("BackgroundImageStyle",(int) this->backgroundImageStyle())); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", this->backgroundOpacity())); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor())); this->setBackgroundType((PlotArea::BackgroundType)(group.readEntry("BackgroundType",(int) this->backgroundType()))); //load the theme for all the children const QVector& childElements = children(AbstractAspect::IncludeHidden); for (auto* child : childElements) child->loadThemeConfig(config); } diff --git a/src/backend/worksheet/Worksheet.h b/src/backend/worksheet/Worksheet.h index 4aa4ff7bb..ed4b22127 100644 --- a/src/backend/worksheet/Worksheet.h +++ b/src/backend/worksheet/Worksheet.h @@ -1,192 +1,194 @@ /*************************************************************************** File : Worksheet.h Project : LabPlot Description : Worksheet (2D visualization) part -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) 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 * * * ***************************************************************************/ #ifndef WORKSHEET_H #define WORKSHEET_H #include "backend/core/AbstractPart.h" #include "backend/worksheet/plots/PlotArea.h" +#include "backend/worksheet/plots/cartesian/CartesianPlot.h" class QGraphicsItem; class QGraphicsScene; class QRectF; class WorksheetPrivate; class WorksheetView; class TreeModel; class XYCurve; class CartesianPlot; class Worksheet : public AbstractPart { Q_OBJECT public: explicit Worksheet(const QString& name, bool loading = false); ~Worksheet() override; enum Unit {Millimeter, Centimeter, Inch, Point}; enum Layout {NoLayout, VerticalLayout, HorizontalLayout, GridLayout}; enum CartesianPlotActionMode {ApplyActionToSelection, ApplyActionToAll}; static float convertToSceneUnits(const float value, const Worksheet::Unit unit); static float convertFromSceneUnits(const float value, const Worksheet::Unit unit); QIcon icon() const override; QMenu* createContextMenu() override; QWidget* view() const override; QVector dependsOn() const override; bool exportView() const override; bool printView() override; bool printPreview() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; QRectF pageRect() const; void setPageRect(const QRectF&); QGraphicsScene* scene() const; void update(); void setPrinting(bool) const; void setThemeName(const QString&); void setItemSelectedInView(const QGraphicsItem*, const bool); void setSelectedInView(const bool); void deleteAspectFromGraphicsItem(const QGraphicsItem*); void setIsClosing(); CartesianPlotActionMode cartesianPlotActionMode(); void setCartesianPlotActionMode(CartesianPlotActionMode mode); CartesianPlotActionMode cartesianPlotCursorMode(); void setCartesianPlotCursorMode(CartesianPlotActionMode mode); void setPlotsLocked(bool); bool plotsLocked(); int getPlotCount(); WorksheetElement* getPlot(int index); TreeModel* cursorModel(); void cursorModelPlotAdded(QString name); void cursorModelPlotRemoved(QString name); BASIC_D_ACCESSOR_DECL(float, backgroundOpacity, BackgroundOpacity) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundType, backgroundType, BackgroundType) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundColorStyle, backgroundColorStyle, BackgroundColorStyle) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundImageStyle, backgroundImageStyle, BackgroundImageStyle) BASIC_D_ACCESSOR_DECL(Qt::BrushStyle, backgroundBrushStyle, BackgroundBrushStyle) CLASS_D_ACCESSOR_DECL(QColor, backgroundFirstColor, BackgroundFirstColor) CLASS_D_ACCESSOR_DECL(QColor, backgroundSecondColor, BackgroundSecondColor) CLASS_D_ACCESSOR_DECL(QString, backgroundFileName, BackgroundFileName) BASIC_D_ACCESSOR_DECL(bool, scaleContent, ScaleContent) BASIC_D_ACCESSOR_DECL(bool, useViewSize, UseViewSize) BASIC_D_ACCESSOR_DECL(Worksheet::Layout, layout, Layout) BASIC_D_ACCESSOR_DECL(float, layoutTopMargin, LayoutTopMargin) BASIC_D_ACCESSOR_DECL(float, layoutBottomMargin, LayoutBottomMargin) BASIC_D_ACCESSOR_DECL(float, layoutLeftMargin, LayoutLeftMargin) BASIC_D_ACCESSOR_DECL(float, layoutRightMargin, LayoutRightMargin) BASIC_D_ACCESSOR_DECL(float, layoutHorizontalSpacing, LayoutHorizontalSpacing) BASIC_D_ACCESSOR_DECL(float, layoutVerticalSpacing, LayoutVerticalSpacing) BASIC_D_ACCESSOR_DECL(int, layoutRowCount, LayoutRowCount) BASIC_D_ACCESSOR_DECL(int, layoutColumnCount, LayoutColumnCount) QString theme() const; void setSuppressLayoutUpdate(bool); void updateLayout(); void registerShortcuts() override; void unregisterShortcuts() override; typedef WorksheetPrivate Private; public slots: void setTheme(const QString&); - void cartesianPlotmousePressZoomSelectionMode(QPointF logicPos); - void cartesianPlotmousePressCursorMode(int cursorNumber, QPointF logicPos); - void cartesianPlotmouseMoveZoomSelectionMode(QPointF logicPos); - void cartesianPlotmouseMoveCursorMode(int cursorNumber, QPointF logicPos); - void cartesianPlotmouseReleaseZoomSelectionMode(); - void cartesianPlotmouseHoverZoomSelectionMode(QPointF logicPos); - void cartesianPlotmouseModeChanged(); + void cartesianPlotMousePressZoomSelectionMode(QPointF logicPos); + void cartesianPlotMousePressCursorMode(int cursorNumber, QPointF logicPos); + void cartesianPlotMouseMoveZoomSelectionMode(QPointF logicPos); + void cartesianPlotMouseMoveCursorMode(int cursorNumber, QPointF logicPos); + void cartesianPlotMouseReleaseZoomSelectionMode(); + void cartesianPlotMouseHoverZoomSelectionMode(QPointF logicPos); + void cartesianPlotMouseModeChangedSlot(CartesianPlot::MouseMode); // slots needed by the cursor void updateCurveBackground(QPen pen, QString curveName); void updateCompleteCursorTreeModel(); void cursorPosChanged(int cursorNumber, double xPos); void curveAdded(const XYCurve* curve); void curveRemoved(const XYCurve* curve); void curveDataChanged(const XYCurve* curve); private: void init(); WorksheetElement* aspectFromGraphicsItem(const WorksheetElement*, const QGraphicsItem*) const; void loadTheme(const QString&); WorksheetPrivate* const d; mutable WorksheetView* m_view{nullptr}; friend class WorksheetPrivate; - private slots: +private slots: void handleAspectAdded(const AbstractAspect*); void handleAspectAboutToBeRemoved(const AbstractAspect*); void handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child); void childSelected(const AbstractAspect*) override; void childDeselected(const AbstractAspect*) override; - signals: +signals: void requestProjectContextMenu(QMenu*); void itemSelected(QGraphicsItem*); void itemDeselected(QGraphicsItem*); void requestUpdate(); void useViewSizeRequested(); + void cartesianPlotMouseModeChanged(CartesianPlot::MouseMode); void backgroundTypeChanged(PlotArea::BackgroundType); void backgroundColorStyleChanged(PlotArea::BackgroundColorStyle); void backgroundImageStyleChanged(PlotArea::BackgroundImageStyle); void backgroundBrushStyleChanged(Qt::BrushStyle); void backgroundFirstColorChanged(const QColor&); void backgroundSecondColorChanged(const QColor&); void backgroundFileNameChanged(const QString&); void backgroundOpacityChanged(float); void scaleContentChanged(bool); void pageRectChanged(const QRectF&); void layoutChanged(Worksheet::Layout); void layoutTopMarginChanged(float); void layoutBottomMarginChanged(float); void layoutLeftMarginChanged(float); void layoutRightMarginChanged(float); void layoutVerticalSpacingChanged(float); void layoutHorizontalSpacingChanged(float); void layoutRowCountChanged(int); void layoutColumnCountChanged(int); void themeChanged(const QString&); void showCursorDock(TreeModel*, QVector); }; #endif diff --git a/src/backend/worksheet/plots/cartesian/Axis.cpp b/src/backend/worksheet/plots/cartesian/Axis.cpp index 968f75535..0f10b30f0 100644 --- a/src/backend/worksheet/plots/cartesian/Axis.cpp +++ b/src/backend/worksheet/plots/cartesian/Axis.cpp @@ -1,2322 +1,2332 @@ /*************************************************************************** File : Axis.cpp Project : LabPlot Description : Axis for cartesian coordinate systems. -------------------------------------------------------------------- Copyright : (C) 2011-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/AxisPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/TextLabel.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/core/AbstractColumn.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" // #include "backend/lib/trace.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include #include extern "C" { #include "backend/nsl/nsl_math.h" } /** * \class AxisGrid * \brief Helper class to get the axis grid drawn with the z-Value=0. * * The painting of the grid lines is separated from the painting of the axis itself. * This allows to use a different z-values for the grid lines (z=0, drawn below all other objects ) * and for the axis (z=FLT_MAX, drawn on top of all other objects) * * \ingroup worksheet */ class AxisGrid : public QGraphicsItem { public: AxisGrid(AxisPrivate* a) { axis = a; setFlag(QGraphicsItem::ItemIsSelectable, false); setFlag(QGraphicsItem::ItemIsFocusable, false); setAcceptHoverEvents(false); } QRectF boundingRect() const override { QPainterPath gridShape; gridShape.addPath(WorksheetElement::shapeFromPath(axis->majorGridPath, axis->majorGridPen)); gridShape.addPath(WorksheetElement::shapeFromPath(axis->minorGridPath, axis->minorGridPen)); QRectF boundingRectangle = gridShape.boundingRect(); return boundingRectangle; } void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { Q_UNUSED(option) Q_UNUSED(widget) if (!axis->isVisible()) return; if (axis->linePath.isEmpty()) return; //draw major grid if (axis->majorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->majorGridOpacity); painter->setPen(axis->majorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->majorGridPath); } //draw minor grid if (axis->minorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->minorGridOpacity); painter->setPen(axis->minorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->minorGridPath); } } private: AxisPrivate* axis; }; /** * \class Axis * \brief Axis for cartesian coordinate systems. * * \ingroup worksheet */ Axis::Axis(const QString& name, AxisOrientation orientation) : WorksheetElement(name, AspectType::Axis), d_ptr(new AxisPrivate(this)) { d_ptr->orientation = orientation; init(); } Axis::Axis(const QString& name, AxisOrientation orientation, AxisPrivate* dd) : WorksheetElement(name, AspectType::Axis), d_ptr(dd) { d_ptr->orientation = orientation; init(); } void Axis::finalizeAdd() { Q_D(Axis); d->plot = dynamic_cast(parentAspect()); Q_ASSERT(d->plot); d->cSystem = dynamic_cast(d->plot->coordinateSystem()); } void Axis::init() { Q_D(Axis); KConfig config; KConfigGroup group = config.group("Axis"); d->autoScale = true; d->position = Axis::AxisCustom; d->offset = group.readEntry("PositionOffset", 0); d->scale = (Axis::AxisScale) group.readEntry("Scale", (int) Axis::ScaleLinear); d->autoScale = group.readEntry("AutoScale", true); d->start = group.readEntry("Start", 0); d->end = group.readEntry("End", 10); d->zeroOffset = group.readEntry("ZeroOffset", 0); d->scalingFactor = group.readEntry("ScalingFactor", 1.0); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int) Qt::SolidLine) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->arrowType = (Axis::ArrowType) group.readEntry("ArrowType", (int)Axis::NoArrow); d->arrowPosition = (Axis::ArrowPosition) group.readEntry("ArrowPosition", (int)Axis::ArrowRight); d->arrowSize = group.readEntry("ArrowSize", Worksheet::convertToSceneUnits(10, Worksheet::Point)); // axis title d->title = new TextLabel(this->name(), TextLabel::AxisTitle); connect( d->title, &TextLabel::changed, this, &Axis::labelChanged); addChild(d->title); d->title->setHidden(true); d->title->graphicsItem()->setParentItem(graphicsItem()); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); d->title->graphicsItem()->setAcceptHoverEvents(false); d->title->setText(this->name()); if (d->orientation == AxisVertical) d->title->setRotationAngle(90); d->titleOffsetX = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels d->titleOffsetY = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels d->majorTicksDirection = (Axis::TicksDirection) group.readEntry("MajorTicksDirection", (int) Axis::ticksOut); d->majorTicksType = (Axis::TicksType) group.readEntry("MajorTicksType", (int) Axis::TicksTotalNumber); d->majorTicksNumber = group.readEntry("MajorTicksNumber", 11); d->majorTicksIncrement = group.readEntry("MajorTicksIncrement", -1.0); // set to negative value, so axisdocks determines the value to not to have to much labels the first time switched to TicksIncrement d->majorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine) ); d->majorTicksPen.setColor( group.readEntry("MajorTicksColor", QColor(Qt::black) ) ); d->majorTicksPen.setWidthF( group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); d->majorTicksLength = group.readEntry("MajorTicksLength", Worksheet::convertToSceneUnits(6.0, Worksheet::Point)); d->majorTicksOpacity = group.readEntry("MajorTicksOpacity", 1.0); d->minorTicksDirection = (Axis::TicksDirection) group.readEntry("MinorTicksDirection", (int) Axis::ticksOut); d->minorTicksType = (Axis::TicksType) group.readEntry("MinorTicksType", (int) Axis::TicksTotalNumber); d->minorTicksNumber = group.readEntry("MinorTicksNumber", 1); d->minorTicksIncrement = group.readEntry("MinorTicksIncrement", -1.0); d->minorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine) ); d->minorTicksPen.setColor( group.readEntry("MinorTicksColor", QColor(Qt::black) ) ); d->minorTicksPen.setWidthF( group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); d->minorTicksLength = group.readEntry("MinorTicksLength", Worksheet::convertToSceneUnits(3.0, Worksheet::Point)); d->minorTicksOpacity = group.readEntry("MinorTicksOpacity", 1.0); //Labels d->labelsFormat = (Axis::LabelsFormat) group.readEntry("LabelsFormat", (int)Axis::FormatDecimal); d->labelsAutoPrecision = group.readEntry("LabelsAutoPrecision", true); d->labelsPrecision = group.readEntry("LabelsPrecision", 1); d->labelsDateTimeFormat = group.readEntry("LabelsDateTimeFormat", "yyyy-MM-dd hh:mm:ss"); d->labelsPosition = (Axis::LabelsPosition) group.readEntry("LabelsPosition", (int) Axis::LabelsOut); d->labelsOffset = group.readEntry("LabelsOffset", Worksheet::convertToSceneUnits( 5.0, Worksheet::Point )); d->labelsRotationAngle = group.readEntry("LabelsRotation", 0); d->labelsFont = group.readEntry("LabelsFont", QFont()); d->labelsFont.setPixelSize( Worksheet::convertToSceneUnits( 10.0, Worksheet::Point ) ); d->labelsColor = group.readEntry("LabelsFontColor", QColor(Qt::black)); d->labelsPrefix = group.readEntry("LabelsPrefix", "" ); d->labelsSuffix = group.readEntry("LabelsSuffix", "" ); d->labelsOpacity = group.readEntry("LabelsOpacity", 1.0); //major grid d->majorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MajorGridStyle", (int) Qt::NoPen) ); d->majorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) ); d->majorGridPen.setWidthF( group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->majorGridOpacity = group.readEntry("MajorGridOpacity", 1.0); //minor grid d->minorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MinorGridStyle", (int) Qt::NoPen) ); d->minorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) ); d->minorGridPen.setWidthF( group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->minorGridOpacity = group.readEntry("MinorGridOpacity", 1.0); } /*! * For the most frequently edited properties, create Actions and ActionGroups for the context menu. * For some ActionGroups the actual actions are created in \c GuiTool, */ void Axis::initActions() { visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Axis::visibilityChangedSlot); //Orientation orientationActionGroup = new QActionGroup(this); orientationActionGroup->setExclusive(true); connect(orientationActionGroup, &QActionGroup::triggered, this, &Axis::orientationChangedSlot); orientationHorizontalAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal"), orientationActionGroup); orientationHorizontalAction->setCheckable(true); orientationVerticalAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical"), orientationActionGroup); orientationVerticalAction->setCheckable(true); //Line lineStyleActionGroup = new QActionGroup(this); lineStyleActionGroup->setExclusive(true); connect(lineStyleActionGroup, &QActionGroup::triggered, this, &Axis::lineStyleChanged); lineColorActionGroup = new QActionGroup(this); lineColorActionGroup->setExclusive(true); connect(lineColorActionGroup, &QActionGroup::triggered, this, &Axis::lineColorChanged); //Ticks //TODO } void Axis::initMenus() { this->initActions(); //Orientation orientationMenu = new QMenu(i18n("Orientation")); orientationMenu->setIcon(QIcon::fromTheme("labplot-axis-horizontal")); orientationMenu->addAction(orientationHorizontalAction); orientationMenu->addAction(orientationVerticalAction); //Line lineMenu = new QMenu(i18n("Line")); lineMenu->setIcon(QIcon::fromTheme("draw-line")); lineStyleMenu = new QMenu(i18n("Style"), lineMenu); lineStyleMenu->setIcon(QIcon::fromTheme("object-stroke-style")); lineMenu->setIcon(QIcon::fromTheme("draw-line")); lineMenu->addMenu( lineStyleMenu ); lineColorMenu = new QMenu(i18n("Color"), lineMenu); lineColorMenu->setIcon(QIcon::fromTheme("fill-color")); GuiTools::fillColorMenu( lineColorMenu, lineColorActionGroup ); lineMenu->addMenu( lineColorMenu ); m_menusInitialized = true; } QMenu* Axis::createContextMenu() { if (!m_menusInitialized) initMenus(); Q_D(const Axis); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //Orientation if ( d->orientation == AxisHorizontal ) orientationHorizontalAction->setChecked(true); else orientationVerticalAction->setChecked(true); menu->insertMenu(firstAction, orientationMenu); //Line styles GuiTools::updatePenStyles( lineStyleMenu, lineStyleActionGroup, d->linePen.color() ); GuiTools::selectPenStyleAction(lineStyleActionGroup, d->linePen.style() ); GuiTools::selectColorAction(lineColorActionGroup, d->linePen.color() ); menu->insertMenu(firstAction, lineMenu); menu->insertSeparator(firstAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon Axis::icon() const{ Q_D(const Axis); QIcon ico; if (d->orientation == Axis::AxisHorizontal) ico = QIcon::fromTheme("labplot-axis-horizontal"); else ico = QIcon::fromTheme("labplot-axis-vertical"); return ico; } Axis::~Axis() { if (m_menusInitialized) { delete orientationMenu; delete lineMenu; } //no need to delete d->title, since it was added with addChild in init(); //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } QGraphicsItem *Axis::graphicsItem() const { return d_ptr; } /*! * overrides the implementation in WorksheetElement and sets the z-value to the maximal possible, * axes are drawn on top of all other object in the plot. */ void Axis::setZValue(qreal) { Q_D(Axis); d->setZValue(std::numeric_limits::max()); d->gridItem->setParentItem(d->parentItem()); d->gridItem->setZValue(0); } void Axis::retransform() { Q_D(Axis); d->retransform(); } void Axis::retransformTickLabelStrings() { Q_D(Axis); d->retransformTickLabelStrings(); } void Axis::setSuppressRetransform(bool value) { Q_D(Axis); d->suppressRetransform = value; } void Axis::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_D(Axis); Q_UNUSED(pageResize); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); QPen pen = d->linePen; pen.setWidthF(pen.widthF() * ratio); d->linePen = pen; d->majorTicksLength *= ratio; // ticks are perpendicular to axis line -> verticalRatio relevant d->minorTicksLength *= ratio; d->labelsFont.setPixelSize( d->labelsFont.pixelSize() * ratio ); //TODO: take into account rotated labels d->labelsOffset *= ratio; d->title->handleResize(horizontalRatio, verticalRatio, pageResize); } /* ============================ getter methods ================= */ BASIC_SHARED_D_READER_IMPL(Axis, bool, autoScale, autoScale) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisOrientation, orientation, orientation) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisPosition, position, position) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisScale, scale, scale) BASIC_SHARED_D_READER_IMPL(Axis, double, offset, offset) BASIC_SHARED_D_READER_IMPL(Axis, double, start, start) BASIC_SHARED_D_READER_IMPL(Axis, double, end, end) BASIC_SHARED_D_READER_IMPL(Axis, qreal, scalingFactor, scalingFactor) BASIC_SHARED_D_READER_IMPL(Axis, qreal, zeroOffset, zeroOffset) BASIC_SHARED_D_READER_IMPL(Axis, TextLabel*, title, title) BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetX, titleOffsetX) BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetY, titleOffsetY) CLASS_SHARED_D_READER_IMPL(Axis, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, lineOpacity, lineOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowType, arrowType, arrowType) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowPosition, arrowPosition, arrowPosition) BASIC_SHARED_D_READER_IMPL(Axis, qreal, arrowSize, arrowSize) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, majorTicksDirection, majorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, majorTicksType, majorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, majorTicksNumber, majorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksIncrement, majorTicksIncrement) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, majorTicksColumn, majorTicksColumn) QString& Axis::majorTicksColumnPath() const { return d_ptr->majorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksLength, majorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorTicksPen, majorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksOpacity, majorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, minorTicksDirection, minorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, minorTicksType, minorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, minorTicksNumber, minorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksIncrement, minorTicksIncrement) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, minorTicksColumn, minorTicksColumn) QString& Axis::minorTicksColumnPath() const { return d_ptr->minorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksLength, minorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorTicksPen, minorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksOpacity, minorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsFormat, labelsFormat, labelsFormat); BASIC_SHARED_D_READER_IMPL(Axis, bool, labelsAutoPrecision, labelsAutoPrecision); BASIC_SHARED_D_READER_IMPL(Axis, int, labelsPrecision, labelsPrecision); BASIC_SHARED_D_READER_IMPL(Axis, QString, labelsDateTimeFormat, labelsDateTimeFormat); BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsPosition, labelsPosition, labelsPosition); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOffset, labelsOffset); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsRotationAngle, labelsRotationAngle); CLASS_SHARED_D_READER_IMPL(Axis, QColor, labelsColor, labelsColor); CLASS_SHARED_D_READER_IMPL(Axis, QFont, labelsFont, labelsFont); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsPrefix, labelsPrefix); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsSuffix, labelsSuffix); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOpacity, labelsOpacity); CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorGridPen, majorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorGridOpacity, majorGridOpacity) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorGridPen, minorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorGridOpacity, minorGridOpacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(Axis, SetAutoScale, bool, autoScale, retransform); void Axis::setAutoScale(bool autoScale) { Q_D(Axis); if (autoScale != d->autoScale) { exec(new AxisSetAutoScaleCmd(d, autoScale, ki18n("%1: set axis auto scaling"))); if (autoScale) { auto* plot = qobject_cast(parentAspect()); if (!plot) return; if (d->orientation == Axis::AxisHorizontal) { d->end = plot->xMax(); d->start = plot->xMin(); } else { d->end = plot->yMax(); d->start = plot->yMin(); } retransform(); emit endChanged(d->end); emit startChanged(d->start); } } } STD_SWAP_METHOD_SETTER_CMD_IMPL(Axis, SetVisible, bool, swapVisible); void Axis::setVisible(bool on) { Q_D(Axis); exec(new AxisSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool Axis::isVisible() const { Q_D(const Axis); return d->isVisible(); } void Axis::setPrinting(bool on) { Q_D(Axis); d->setPrinting(on); } STD_SETTER_CMD_IMPL_F_S(Axis, SetOrientation, Axis::AxisOrientation, orientation, retransform); void Axis::setOrientation( AxisOrientation orientation) { Q_D(Axis); if (orientation != d->orientation) exec(new AxisSetOrientationCmd(d, orientation, ki18n("%1: set axis orientation"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetPosition, Axis::AxisPosition, position, retransform); void Axis::setPosition(AxisPosition position) { Q_D(Axis); if (position != d->position) exec(new AxisSetPositionCmd(d, position, ki18n("%1: set axis position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScaling, Axis::AxisScale, scale, retransformTicks); void Axis::setScale(AxisScale scale) { Q_D(Axis); if (scale != d->scale) exec(new AxisSetScalingCmd(d, scale, ki18n("%1: set axis scale"))); } STD_SETTER_CMD_IMPL_F(Axis, SetOffset, double, offset, retransform); void Axis::setOffset(double offset, bool undo) { Q_D(Axis); if (offset != d->offset) { if (undo) { exec(new AxisSetOffsetCmd(d, offset, ki18n("%1: set axis offset"))); } else { d->offset = offset; //don't need to call retransform() afterward //since the only usage of this call is in CartesianPlot, where retransform is called for all children anyway. } emit positionChanged(offset); } } STD_SETTER_CMD_IMPL_F_S(Axis, SetStart, double, start, retransform); void Axis::setStart(double start) { Q_D(Axis); if (start != d->start) exec(new AxisSetStartCmd(d, start, ki18n("%1: set axis start"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetEnd, double, end, retransform); void Axis::setEnd(double end) { Q_D(Axis); if (end != d->end) exec(new AxisSetEndCmd(d, end, ki18n("%1: set axis end"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetZeroOffset, qreal, zeroOffset, retransform); void Axis::setZeroOffset(qreal zeroOffset) { Q_D(Axis); if (zeroOffset != d->zeroOffset) exec(new AxisSetZeroOffsetCmd(d, zeroOffset, ki18n("%1: set axis zero offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScalingFactor, qreal, scalingFactor, retransform); void Axis::setScalingFactor(qreal scalingFactor) { Q_D(Axis); if (scalingFactor != d->scalingFactor) exec(new AxisSetScalingFactorCmd(d, scalingFactor, ki18n("%1: set axis scaling factor"))); } //Title STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetX, qreal, titleOffsetX, retransform); void Axis::setTitleOffsetX(qreal offset) { Q_D(Axis); if (offset != d->titleOffsetX) exec(new AxisSetTitleOffsetXCmd(d, offset, ki18n("%1: set title offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetY, qreal, titleOffsetY, retransform); void Axis::setTitleOffsetY(qreal offset) { Q_D(Axis); if (offset != d->titleOffsetY) exec(new AxisSetTitleOffsetYCmd(d, offset, ki18n("%1: set title offset"))); } //Line STD_SETTER_CMD_IMPL_F_S(Axis, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect); void Axis::setLinePen(const QPen &pen) { Q_D(Axis); if (pen != d->linePen) exec(new AxisSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLineOpacity, qreal, lineOpacity, update); void Axis::setLineOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->lineOpacity) exec(new AxisSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowType, Axis::ArrowType, arrowType, retransformArrow); void Axis::setArrowType(ArrowType type) { Q_D(Axis); if (type != d->arrowType) exec(new AxisSetArrowTypeCmd(d, type, ki18n("%1: set arrow type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowPosition, Axis::ArrowPosition, arrowPosition, retransformArrow); void Axis::setArrowPosition(ArrowPosition position) { Q_D(Axis); if (position != d->arrowPosition) exec(new AxisSetArrowPositionCmd(d, position, ki18n("%1: set arrow position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowSize, qreal, arrowSize, retransformArrow); void Axis::setArrowSize(qreal arrowSize) { Q_D(Axis); if (arrowSize != d->arrowSize) exec(new AxisSetArrowSizeCmd(d, arrowSize, ki18n("%1: set arrow size"))); } //Major ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksDirection, Axis::TicksDirection, majorTicksDirection, retransformTicks); void Axis::setMajorTicksDirection(TicksDirection majorTicksDirection) { Q_D(Axis); if (majorTicksDirection != d->majorTicksDirection) exec(new AxisSetMajorTicksDirectionCmd(d, majorTicksDirection, ki18n("%1: set major ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksType, Axis::TicksType, majorTicksType, retransformTicks); void Axis::setMajorTicksType(TicksType majorTicksType) { Q_D(Axis); if (majorTicksType!= d->majorTicksType) exec(new AxisSetMajorTicksTypeCmd(d, majorTicksType, ki18n("%1: set major ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksNumber, int, majorTicksNumber, retransformTicks); void Axis::setMajorTicksNumber(int majorTicksNumber) { Q_D(Axis); if (majorTicksNumber != d->majorTicksNumber) exec(new AxisSetMajorTicksNumberCmd(d, majorTicksNumber, ki18n("%1: set the total number of the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksIncrement, qreal, majorTicksIncrement, retransformTicks); void Axis::setMajorTicksIncrement(qreal majorTicksIncrement) { Q_D(Axis); if (majorTicksIncrement != d->majorTicksIncrement) exec(new AxisSetMajorTicksIncrementCmd(d, majorTicksIncrement, ki18n("%1: set the increment for the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksColumn, const AbstractColumn*, majorTicksColumn, retransformTicks) void Axis::setMajorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->majorTicksColumn) { exec(new AxisSetMajorTicksColumnCmd(d, column, ki18n("%1: assign major ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::majorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksPen, QPen, majorTicksPen, recalcShapeAndBoundingRect); void Axis::setMajorTicksPen(const QPen& pen) { Q_D(Axis); if (pen != d->majorTicksPen) exec(new AxisSetMajorTicksPenCmd(d, pen, ki18n("%1: set major ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksLength, qreal, majorTicksLength, retransformTicks); void Axis::setMajorTicksLength(qreal majorTicksLength) { Q_D(Axis); if (majorTicksLength != d->majorTicksLength) exec(new AxisSetMajorTicksLengthCmd(d, majorTicksLength, ki18n("%1: set major ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksOpacity, qreal, majorTicksOpacity, update); void Axis::setMajorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorTicksOpacity) exec(new AxisSetMajorTicksOpacityCmd(d, opacity, ki18n("%1: set major ticks opacity"))); } //Minor ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksDirection, Axis::TicksDirection, minorTicksDirection, retransformTicks); void Axis::setMinorTicksDirection(TicksDirection minorTicksDirection) { Q_D(Axis); if (minorTicksDirection != d->minorTicksDirection) exec(new AxisSetMinorTicksDirectionCmd(d, minorTicksDirection, ki18n("%1: set minor ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksType, Axis::TicksType, minorTicksType, retransformTicks); void Axis::setMinorTicksType(TicksType minorTicksType) { Q_D(Axis); if (minorTicksType!= d->minorTicksType) exec(new AxisSetMinorTicksTypeCmd(d, minorTicksType, ki18n("%1: set minor ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksNumber, int, minorTicksNumber, retransformTicks); void Axis::setMinorTicksNumber(int minorTicksNumber) { Q_D(Axis); if (minorTicksNumber != d->minorTicksNumber) exec(new AxisSetMinorTicksNumberCmd(d, minorTicksNumber, ki18n("%1: set the total number of the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksIncrement, qreal, minorTicksIncrement, retransformTicks); void Axis::setMinorTicksIncrement(qreal minorTicksIncrement) { Q_D(Axis); if (minorTicksIncrement != d->minorTicksIncrement) exec(new AxisSetMinorTicksIncrementCmd(d, minorTicksIncrement, ki18n("%1: set the increment for the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksColumn, const AbstractColumn*, minorTicksColumn, retransformTicks) void Axis::setMinorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->minorTicksColumn) { exec(new AxisSetMinorTicksColumnCmd(d, column, ki18n("%1: assign minor ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::minorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksPen, QPen, minorTicksPen, recalcShapeAndBoundingRect); void Axis::setMinorTicksPen(const QPen& pen) { Q_D(Axis); if (pen != d->minorTicksPen) exec(new AxisSetMinorTicksPenCmd(d, pen, ki18n("%1: set minor ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksLength, qreal, minorTicksLength, retransformTicks); void Axis::setMinorTicksLength(qreal minorTicksLength) { Q_D(Axis); if (minorTicksLength != d->minorTicksLength) exec(new AxisSetMinorTicksLengthCmd(d, minorTicksLength, ki18n("%1: set minor ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksOpacity, qreal, minorTicksOpacity, update); void Axis::setMinorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorTicksOpacity) exec(new AxisSetMinorTicksOpacityCmd(d, opacity, ki18n("%1: set minor ticks opacity"))); } //Labels STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFormat, Axis::LabelsFormat, labelsFormat, retransformTicks); void Axis::setLabelsFormat(LabelsFormat labelsFormat) { Q_D(Axis); if (labelsFormat != d->labelsFormat) { exec(new AxisSetLabelsFormatCmd(d, labelsFormat, ki18n("%1: set labels format"))); //TODO: this part is not undo/redo-aware if (d->labelsFormatAutoChanged && labelsFormat == Axis::FormatDecimal) d->labelsFormatDecimalOverruled = true; else d->labelsFormatDecimalOverruled = false; } } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsAutoPrecision, bool, labelsAutoPrecision, retransformTickLabelStrings); void Axis::setLabelsAutoPrecision(bool labelsAutoPrecision) { Q_D(Axis); if (labelsAutoPrecision != d->labelsAutoPrecision) exec(new AxisSetLabelsAutoPrecisionCmd(d, labelsAutoPrecision, ki18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrecision, int, labelsPrecision, retransformTickLabelStrings); void Axis::setLabelsPrecision(int labelsPrecision) { Q_D(Axis); if (labelsPrecision != d->labelsPrecision) exec(new AxisSetLabelsPrecisionCmd(d, labelsPrecision, ki18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsDateTimeFormat, QString, labelsDateTimeFormat, retransformTickLabelStrings); void Axis::setLabelsDateTimeFormat(const QString& format) { Q_D(Axis); if (format != d->labelsDateTimeFormat) exec(new AxisSetLabelsDateTimeFormatCmd(d, format, ki18n("%1: set labels datetime format"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPosition, Axis::LabelsPosition, labelsPosition, retransformTickLabelPositions); void Axis::setLabelsPosition(LabelsPosition labelsPosition) { Q_D(Axis); if (labelsPosition != d->labelsPosition) exec(new AxisSetLabelsPositionCmd(d, labelsPosition, ki18n("%1: set labels position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOffset, double, labelsOffset, retransformTickLabelPositions); void Axis::setLabelsOffset(double offset) { Q_D(Axis); if (offset != d->labelsOffset) exec(new AxisSetLabelsOffsetCmd(d, offset, ki18n("%1: set label offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, retransformTickLabelPositions); void Axis::setLabelsRotationAngle(qreal angle) { Q_D(Axis); if (angle != d->labelsRotationAngle) exec(new AxisSetLabelsRotationAngleCmd(d, angle, ki18n("%1: set label rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsColor, QColor, labelsColor, update); void Axis::setLabelsColor(const QColor& color) { Q_D(Axis); if (color != d->labelsColor) exec(new AxisSetLabelsColorCmd(d, color, ki18n("%1: set label color"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFont, QFont, labelsFont, retransformTickLabelStrings); void Axis::setLabelsFont(const QFont& font) { Q_D(Axis); if (font != d->labelsFont) exec(new AxisSetLabelsFontCmd(d, font, ki18n("%1: set label font"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrefix, QString, labelsPrefix, retransformTickLabelStrings); void Axis::setLabelsPrefix(const QString& prefix) { Q_D(Axis); if (prefix != d->labelsPrefix) exec(new AxisSetLabelsPrefixCmd(d, prefix, ki18n("%1: set label prefix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsSuffix, QString, labelsSuffix, retransformTickLabelStrings); void Axis::setLabelsSuffix(const QString& suffix) { Q_D(Axis); if (suffix != d->labelsSuffix) exec(new AxisSetLabelsSuffixCmd(d, suffix, ki18n("%1: set label suffix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOpacity, qreal, labelsOpacity, update); void Axis::setLabelsOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->labelsOpacity) exec(new AxisSetLabelsOpacityCmd(d, opacity, ki18n("%1: set labels opacity"))); } //Major grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridPen, QPen, majorGridPen, retransformMajorGrid); void Axis::setMajorGridPen(const QPen& pen) { Q_D(Axis); if (pen != d->majorGridPen) exec(new AxisSetMajorGridPenCmd(d, pen, ki18n("%1: set major grid style"))); } -STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridOpacity, qreal, majorGridOpacity, update); +STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridOpacity, qreal, majorGridOpacity, updateGrid); void Axis::setMajorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorGridOpacity) exec(new AxisSetMajorGridOpacityCmd(d, opacity, ki18n("%1: set major grid opacity"))); } //Minor grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridPen, QPen, minorGridPen, retransformMinorGrid); void Axis::setMinorGridPen(const QPen& pen) { Q_D(Axis); if (pen != d->minorGridPen) exec(new AxisSetMinorGridPenCmd(d, pen, ki18n("%1: set minor grid style"))); } -STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridOpacity, qreal, minorGridOpacity, update); +STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridOpacity, qreal, minorGridOpacity, updateGrid); void Axis::setMinorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorGridOpacity) exec(new AxisSetMinorGridOpacityCmd(d, opacity, ki18n("%1: set minor grid opacity"))); } //############################################################################## //#################################### SLOTs ################################ //############################################################################## void Axis::labelChanged() { Q_D(Axis); d->recalcShapeAndBoundingRect(); } void Axis::retransformTicks() { Q_D(Axis); d->retransformTicks(); } void Axis::majorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect == d->majorTicksColumn) { d->majorTicksColumn = nullptr; d->retransformTicks(); } } void Axis::minorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect == d->minorTicksColumn) { d->minorTicksColumn = nullptr; d->retransformTicks(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Axis::orientationChangedSlot(QAction* action) { if (action == orientationHorizontalAction) this->setOrientation(AxisHorizontal); else this->setOrientation(AxisVertical); } void Axis::lineStyleChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action)); this->setLinePen(pen); } void Axis::lineColorChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setColor(GuiTools::colorFromAction(lineColorActionGroup, action)); this->setLinePen(pen); } void Axis::visibilityChangedSlot() { Q_D(const Axis); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### AxisPrivate::AxisPrivate(Axis* owner) : gridItem(new AxisGrid(this)), q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemIsFocusable, true); setAcceptHoverEvents(true); } QString AxisPrivate::name() const{ return q->name(); } bool AxisPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } QRectF AxisPrivate::boundingRect() const{ return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath AxisPrivate::shape() const{ return axisShape; } /*! recalculates the position of the axis on the worksheet */ void AxisPrivate::retransform() { if (suppressRetransform || !plot) return; // PERFTRACE(name().toLatin1() + ", AxisPrivate::retransform()"); m_suppressRecalc = true; retransformLine(); m_suppressRecalc = false; recalcShapeAndBoundingRect(); } void AxisPrivate::retransformLine() { if (suppressRetransform) return; linePath = QPainterPath(); lines.clear(); QPointF startPoint; QPointF endPoint; if (orientation == Axis::AxisHorizontal) { if (position == Axis::AxisTop) offset = plot->yMax(); else if (position == Axis::AxisBottom) offset = plot->yMin(); else if (position == Axis::AxisCentered) offset = plot->yMin() + (plot->yMax()-plot->yMin())/2; startPoint.setX(start); startPoint.setY(offset); endPoint.setX(end); endPoint.setY(offset); } else { // vertical if (position == Axis::AxisLeft) offset = plot->xMin(); else if (position == Axis::AxisRight) offset = plot->xMax(); else if (position == Axis::AxisCentered) offset = plot->xMin() + (plot->xMax()-plot->xMin())/2; startPoint.setX(offset); startPoint.setY(start); endPoint.setY(end); endPoint.setX(offset); } lines.append(QLineF(startPoint, endPoint)); lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MarkGaps); for (const auto& line : lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } if (linePath.isEmpty()) { recalcShapeAndBoundingRect(); return; } else { retransformArrow(); retransformTicks(); } } void AxisPrivate::retransformArrow() { if (suppressRetransform) return; arrowPath = QPainterPath(); if (arrowType == Axis::NoArrow || lines.isEmpty()) { recalcShapeAndBoundingRect(); return; } if (arrowPosition == Axis::ArrowRight || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(lines.size()-1).p2(); this->addArrow(endPoint, 1); } if (arrowPosition == Axis::ArrowLeft || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(0).p1(); this->addArrow(endPoint, -1); } recalcShapeAndBoundingRect(); } void AxisPrivate::addArrow(QPointF startPoint, int direction) { static const double cos_phi = cos(M_PI/6.); if (orientation == Axis::AxisHorizontal) { QPointF endPoint = QPointF(startPoint.x() + direction*arrowSize, startPoint.y()); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/8, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; } } else { //vertical orientation QPointF endPoint = QPointF(startPoint.x(), startPoint.y()-direction*arrowSize); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/8)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; } } } //! helper function for retransformTicks() bool AxisPrivate::transformAnchor(QPointF* anchorPoint) { QVector points; points.append(*anchorPoint); points = cSystem->mapLogicalToScene(points); if (points.count() != 1) { // point is not mappable or in a coordinate gap return false; } else { *anchorPoint = points.at(0); return true; } } /*! recalculates the position of the axis ticks. */ void AxisPrivate::retransformTicks() { if (suppressRetransform) return; //TODO: check that start and end are > 0 for log and >=0 for sqrt, etc. majorTicksPath = QPainterPath(); minorTicksPath = QPainterPath(); majorTickPoints.clear(); minorTickPoints.clear(); tickLabelValues.clear(); if ( majorTicksNumber < 1 || (majorTicksDirection == Axis::noTicks && minorTicksDirection == Axis::noTicks) ) { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } //determine the spacing for the major ticks double majorTicksSpacing = 0; int tmpMajorTicksNumber = 0; if (majorTicksType == Axis::TicksTotalNumber) { //the total number of the major ticks is given - > determine the spacing tmpMajorTicksNumber = majorTicksNumber; switch (scale) { case Axis::ScaleLinear: majorTicksSpacing = (end-start)/(majorTicksNumber-1); break; case Axis::ScaleLog10: majorTicksSpacing = (log10(end)-log10(start))/(majorTicksNumber-1); break; case Axis::ScaleLog2: majorTicksSpacing = (log(end)-log(start))/log(2)/(majorTicksNumber-1); break; case Axis::ScaleLn: majorTicksSpacing = (log(end)-log(start))/(majorTicksNumber-1); break; case Axis::ScaleSqrt: majorTicksSpacing = (sqrt(end)-sqrt(start))/(majorTicksNumber-1); break; case Axis::ScaleX2: majorTicksSpacing = (pow(end,2)-pow(start,2))/(majorTicksNumber-1); } } else if (majorTicksType == Axis::TicksIncrement) { //the spacing (increment) of the major ticks is given - > determine the number majorTicksSpacing = majorTicksIncrement; switch (scale) { case Axis::ScaleLinear: tmpMajorTicksNumber = qRound((end-start)/majorTicksSpacing + 1); break; case Axis::ScaleLog10: tmpMajorTicksNumber = qRound((log10(end)-log10(start))/majorTicksSpacing + 1); break; case Axis::ScaleLog2: tmpMajorTicksNumber = qRound((log(end)-log(start))/log(2)/majorTicksSpacing + 1); break; case Axis::ScaleLn: tmpMajorTicksNumber = qRound((log(end)-log(start))/majorTicksSpacing + 1); break; case Axis::ScaleSqrt: tmpMajorTicksNumber = qRound((sqrt(end)-sqrt(start))/majorTicksSpacing + 1); break; case Axis::ScaleX2: tmpMajorTicksNumber = qRound((pow(end,2)-pow(start,2))/majorTicksSpacing + 1); } } else { //custom column was provided if (majorTicksColumn) { tmpMajorTicksNumber = majorTicksColumn->rowCount(); } else { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } } int tmpMinorTicksNumber; if (minorTicksType == Axis::TicksTotalNumber) tmpMinorTicksNumber = minorTicksNumber; else if (minorTicksType == Axis::TicksIncrement) tmpMinorTicksNumber = (end - start)/ (majorTicksNumber - 1)/minorTicksIncrement - 1; else (minorTicksColumn) ? tmpMinorTicksNumber = minorTicksColumn->rowCount() : tmpMinorTicksNumber = 0; QPointF anchorPoint; QPointF startPoint; QPointF endPoint; qreal majorTickPos = 0.0; qreal minorTickPos; qreal nextMajorTickPos = 0.0; const int xDirection = cSystem->xDirection(); const int yDirection = cSystem->yDirection(); const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2; const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2; bool valid; for (int iMajor = 0; iMajor < tmpMajorTicksNumber; iMajor++) { //calculate major tick's position if (majorTicksType != Axis::TicksCustomColumn) { switch (scale) { case Axis::ScaleLinear: majorTickPos = start + majorTicksSpacing*iMajor; nextMajorTickPos = start + majorTicksSpacing*(iMajor+1); break; case Axis::ScaleLog10: majorTickPos = pow(10, log10(start) + majorTicksSpacing*iMajor); nextMajorTickPos = pow(10, log10(start) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleLog2: majorTickPos = pow(2, log(start)/log(2) + majorTicksSpacing*iMajor); nextMajorTickPos = pow(2, log(start)/log(2) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleLn: majorTickPos = exp(log(start) + majorTicksSpacing*iMajor); nextMajorTickPos = exp(log(start) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleSqrt: majorTickPos = pow(sqrt(start) + majorTicksSpacing*iMajor, 2); nextMajorTickPos = pow(sqrt(start) + majorTicksSpacing*(iMajor+1), 2); break; case Axis::ScaleX2: majorTickPos = sqrt(sqrt(start) + majorTicksSpacing*iMajor); nextMajorTickPos = sqrt(sqrt(start) + majorTicksSpacing*(iMajor+1)); break; } } else { majorTickPos = majorTicksColumn->valueAt(iMajor); if (std::isnan(majorTickPos)) break; //stop iterating after the first non numerical value in the column } //calculate start and end points for major tick's line if (majorTicksDirection != Axis::noTicks ) { if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(majorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } } } else { // vertical anchorPoint.setY(majorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } } } //add major tick's line to the painter path if (valid) { majorTicksPath.moveTo(startPoint); majorTicksPath.lineTo(endPoint); majorTickPoints << anchorPoint; tickLabelValues<< scalingFactor*majorTickPos+zeroOffset; } } //minor ticks if ((Axis::noTicks != minorTicksDirection) && (tmpMajorTicksNumber > 1) && (tmpMinorTicksNumber > 0) && (iMajorvalueAt(iMinor); if (std::isnan(minorTickPos)) break; //stop iterating after the first non numerical value in the column //in the case a custom column is used for the minor ticks, we draw them _once_ for the whole range of the axis. //execute the minor ticks loop only once. if (iMajor > 0) break; } //calculate start and end points for minor tick's line if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(minorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? -yDirection * minorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? -yDirection * minorTicksLength : 0); } } } else { // vertical anchorPoint.setY(minorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? -xDirection * minorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? -xDirection * minorTicksLength : 0, 0); } } } //add minor tick's line to the painter path if (valid) { minorTicksPath.moveTo(startPoint); minorTicksPath.lineTo(endPoint); minorTickPoints << anchorPoint; } } } } //tick positions where changed -> update the position of the tick labels and grid lines retransformTickLabelStrings(); retransformMajorGrid(); retransformMinorGrid(); } /*! creates the tick label strings starting with the most optimal (=the smallest possible number of float digits) precision for the floats */ void AxisPrivate::retransformTickLabelStrings() { if (suppressRetransform) return; // DEBUG("AxisPrivate::retransformTickLabelStrings()"); if (labelsAutoPrecision) { //check, whether we need to increase the current precision int newPrecision = upperLabelsPrecision(labelsPrecision); if (newPrecision!= labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } else { //check, whether we can reduce the current precision newPrecision = lowerLabelsPrecision(labelsPrecision); if (newPrecision!= labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } } } // DEBUG("labelsPrecision =" << labelsPrecision); //automatically switch from 'decimal' to 'scientific' format for big numbers (>10^4) //and back to decimal when the numbers get smaller after the auto-switch again if (labelsFormat == Axis::FormatDecimal && !labelsFormatDecimalOverruled) { for (auto value : tickLabelValues) { if (std::abs(value) > 1e4) { labelsFormat = Axis::FormatScientificE; emit q->labelsFormatChanged(labelsFormat); labelsFormatAutoChanged = true; break; } } } else if (labelsFormatAutoChanged ) { //check whether we still have big numbers bool changeBack = true; for (auto value : tickLabelValues) { if (std::abs(value) > 1e4) { changeBack = false; break; } } if (changeBack) { labelsFormatAutoChanged = false; labelsFormat = Axis::FormatDecimal; emit q->labelsFormatChanged(labelsFormat); } } tickLabelStrings.clear(); QString str; if ( (orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ) { if (labelsFormat == Axis::FormatDecimal) { QString nullStr = QString::number(0, 'f', labelsPrecision); for (const auto value : tickLabelValues) { str = QString::number(value, 'f', labelsPrecision); if (str == "-" + nullStr) str = nullStr; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatScientificE) { QString nullStr = QString::number(0, 'e', labelsPrecision); for (const auto value : tickLabelValues) { str = QString::number(value, 'e', labelsPrecision); if (str == "-" + nullStr) str = nullStr; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers10) { for (const auto value : tickLabelValues) { str = "10" + QString::number(log10(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers2) { for (const auto value : tickLabelValues) { str = "2" + QString::number(log2(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowersE) { for (const auto value : tickLabelValues) { str = "e" + QString::number(log(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatMultipliesPi) { for (const auto value : tickLabelValues) { str = "" + QString::number(value / M_PI, 'f', labelsPrecision) + "" + QChar(0x03C0); str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } } else { for (const auto value : tickLabelValues) { QDateTime dateTime; dateTime.setMSecsSinceEpoch(value); str = dateTime.toString(labelsDateTimeFormat); str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } //recalculate the position of the tick labels retransformTickLabelPositions(); } /*! returns the smallest upper limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::upperLabelsPrecision(int precision) { // DEBUG("AxisPrivate::upperLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, increase the precision. QVector tempValues; for (const auto value : tickLabelValues) tempValues.append( nsl_math_round_places(value, precision) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate for the current precision found, increase the precision and check again return upperLabelsPrecision(precision + 1); } } } //no duplicates for the current precision found: return the current value // DEBUG(" upper precision = " << precision); return precision; } /*! returns highest lower limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::lowerLabelsPrecision(int precision) { // DEBUG("AxisPrivate::lowerLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, decrease the precision. QVector tempValues; for (const auto value : tickLabelValues) tempValues.append( nsl_math_round_places(value, precision-1) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate found for the reduced precision //-> current precision cannot be reduced, return the current value // DEBUG(" lower precision = " << precision); return precision; } } } //no duplicates found, reduce further, and check again if (precision == 0) return 0; else return lowerLabelsPrecision(precision - 1); } /*! recalculates the position of the tick labels. Called when the geometry related properties (position, offset, font size, suffix, prefix) of the labels are changed. */ void AxisPrivate::retransformTickLabelPositions() { tickLabelPoints.clear(); if (majorTicksDirection == Axis::noTicks || labelsPosition == Axis::NoLabels) { recalcShapeAndBoundingRect(); return; } QFontMetrics fm(labelsFont); float width = 0; float height = fm.ascent(); QPointF pos; const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2; const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2; const int xDirection = cSystem->xDirection(); const int yDirection = cSystem->yDirection(); QPointF startPoint, endPoint, anchorPoint; QTextDocument td; td.setDefaultFont(labelsFont); double cosinus = cos(labelsRotationAngle * M_PI / 180); // calculate only one time double sinus = sin(labelsRotationAngle * M_PI / 180); // calculate only one time for ( int i = 0; i < majorTickPoints.size(); i++ ) { if ((orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric)) { if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { width = fm.boundingRect(tickLabelStrings.at(i)).width(); } else { td.setHtml(tickLabelStrings.at(i)); width = td.size().width(); height = td.size().height(); } } else { // Datetime width = fm.boundingRect(tickLabelStrings.at(i)).width(); } double diffx = cosinus * width; double diffy = sinus * width; anchorPoint = majorTickPoints.at(i); //center align all labels with respect to the end point of the tick line if (orientation == Axis::AxisHorizontal) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } // for rotated labels (angle is not zero), align label's corner at the position of the tick if (abs(labelsRotationAngle) > 179.999 && abs(labelsRotationAngle) < 180.009) { // +-180° if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() + width/2); pos.setY( endPoint.y() + labelsOffset ); } else { pos.setX( startPoint.x() + width/2); pos.setY( startPoint.y() - height + labelsOffset ); } } else if (labelsRotationAngle <= -0.01) { // [-0.01°, -180°) if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() + sinus * height/2); pos.setY( endPoint.y() + labelsOffset + cosinus * height/2); } else { pos.setX( startPoint.x() + sinus * height/2 - diffx); pos.setY( startPoint.y() + labelsOffset + cosinus * height/2 + diffy); } } else if (labelsRotationAngle >= 0.01) { // [0.01°, 180°) if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - diffx + sinus * height/2); pos.setY( endPoint.y() + labelsOffset + diffy + cosinus * height/2); } else { pos.setX( startPoint.x() + sinus * height/2); pos.setY( startPoint.y() + labelsOffset + cosinus * height/2); } } else { // 0° if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - width/2); pos.setY( endPoint.y() + height + labelsOffset ); } else { pos.setX( startPoint.x() - width/2); pos.setY( startPoint.y() + labelsOffset ); } } // ---------------------- vertical ------------------------- } else { if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } if (labelsRotationAngle >= 89.999 && labelsRotationAngle <= 90.009) { // +90° if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - labelsOffset); pos.setY( endPoint.y() + width/2 ); } else { pos.setX( startPoint.x() - labelsOffset); pos.setY( startPoint.y() + width/2); } } else if (labelsRotationAngle >= -90.999 && labelsRotationAngle <= -89.009) { // -90° if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - labelsOffset - height); pos.setY( endPoint.y() - width/2 ); } else { pos.setX( startPoint.x() - labelsOffset); pos.setY( startPoint.y() - width/2 ); } } else if (abs(labelsRotationAngle) > 179.999 && abs(labelsRotationAngle) < 180.009) { // +-180° if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - labelsOffset); pos.setY( endPoint.y() - height/2); } else { pos.setX( startPoint.x() - labelsOffset + width); pos.setY( startPoint.y() - height/2 ); } } else if (abs(labelsRotationAngle) >= 0.01 && abs(labelsRotationAngle) < 90.01) { // [0.01°, 90°) if (labelsPosition == Axis::LabelsOut) { // left pos.setX( endPoint.x() - labelsOffset - diffx + sinus * height/2); pos.setY( endPoint.y() + cosinus * height/2 + diffy); } else { pos.setX( startPoint.x() - labelsOffset + sinus * height/2); pos.setY( startPoint.y() + cosinus * height/2); } } else if (abs(labelsRotationAngle) >= 90.01 && abs(labelsRotationAngle) < 180) { // [90.01, 180) if (labelsPosition == Axis::LabelsOut) { // left pos.setX( endPoint.x() - labelsOffset + sinus * height/2); pos.setY( endPoint.y() + cosinus * height/2); } else { pos.setX( startPoint.x() - labelsOffset - diffx + sinus * height/2); pos.setY( startPoint.y() + diffy + cosinus * height/2); } } else { // 0° if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - width - labelsOffset); pos.setY( endPoint.y() + height/2 ); } else { pos.setX( startPoint.x() - labelsOffset); pos.setY( startPoint.y() + height/2 ); } } } tickLabelPoints << pos; } recalcShapeAndBoundingRect(); } void AxisPrivate::retransformMajorGrid() { if (suppressRetransform) return; majorGridPath = QPainterPath(); if (majorGridPen.style() == Qt::NoPen || majorTickPoints.size() == 0) { recalcShapeAndBoundingRect(); return; } //major tick points are already in scene coordinates, convert them back to logical... //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function. //Currently, grid lines disappear somtimes without this flag QVector logicalMajorTickPoints = cSystem->mapSceneToLogical(majorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); if (logicalMajorTickPoints.isEmpty()) return; //TODO: //when iterating over all grid lines, skip the first and the last points for auto scaled axes, //since we don't want to paint any grid lines at the plot boundaries bool skipLowestTick, skipUpperTick; if (orientation == Axis::AxisHorizontal) { //horizontal axis skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).x(), plot->xMin()); skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).x(), plot->xMax()); } else { skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).y(), plot->yMin()); skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).y(), plot->yMax()); } int start, end; if (skipLowestTick) { if (logicalMajorTickPoints.size() > 1) start = 1; else start = 0; } else { start = 0; } if (skipUpperTick) { if (logicalMajorTickPoints.size() > 1) end = logicalMajorTickPoints.size() - 1; else end = 0; } else { end = logicalMajorTickPoints.size(); } QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis double yMin = plot->yMin(); double yMax = plot->yMax(); for (int i = start; i < end; ++i) { const QPointF& point = logicalMajorTickPoints.at(i); lines.append( QLineF(point.x(), yMin, point.x(), yMax) ); } } else { //vertical axis double xMin = plot->xMin(); double xMax = plot->xMax(); //skip the first and the last points, since we don't want to paint any grid lines at the plot boundaries for (int i = start; i < end; ++i) { const QPointF& point = logicalMajorTickPoints.at(i); lines.append( QLineF(xMin, point.y(), xMax, point.y()) ); } } lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); for (const auto& line : lines) { majorGridPath.moveTo(line.p1()); majorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void AxisPrivate::retransformMinorGrid() { if (suppressRetransform) return; minorGridPath = QPainterPath(); if (minorGridPen.style() == Qt::NoPen) { recalcShapeAndBoundingRect(); return; } //minor tick points are already in scene coordinates, convert them back to logical... //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function. //Currently, grid lines disappear somtimes without this flag QVector logicalMinorTickPoints = cSystem->mapSceneToLogical(minorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis double yMin = plot->yMin(); double yMax = plot->yMax(); for (const auto point : logicalMinorTickPoints) lines.append( QLineF(point.x(), yMin, point.x(), yMax) ); } else { //vertical axis double xMin = plot->xMin(); double xMax = plot->xMax(); for (const auto point: logicalMinorTickPoints) lines.append( QLineF(xMin, point.y(), xMax, point.y()) ); } lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); for (const auto& line : lines) { minorGridPath.moveTo(line.p1()); minorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } +/*! + * called when the opacity of the grid was changes, update the grid graphics item + */ +//TODO: this function is only needed for loaded projects where update() doesn't seem to be enough +//and we have to call gridItem->update() explicitely. +//This is not required for newly created plots/axes. Why is this difference? +void AxisPrivate::updateGrid() { + gridItem->update(); +} + void AxisPrivate::recalcShapeAndBoundingRect() { if (m_suppressRecalc) return; prepareGeometryChange(); if (linePath.isEmpty()) { axisShape = QPainterPath(); boundingRectangle = QRectF(); title->setPositionInvalid(true); if (plot) plot->prepareGeometryChange(); return; } else { title->setPositionInvalid(false); } axisShape = WorksheetElement::shapeFromPath(linePath, linePen); axisShape.addPath(WorksheetElement::shapeFromPath(arrowPath, linePen)); axisShape.addPath(WorksheetElement::shapeFromPath(majorTicksPath, majorTicksPen)); axisShape.addPath(WorksheetElement::shapeFromPath(minorTicksPath, minorTicksPen)); QPainterPath tickLabelsPath = QPainterPath(); if (labelsPosition != Axis::NoLabels) { QTransform trafo; QPainterPath tempPath; QFontMetrics fm(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); for (int i = 0; i < tickLabelPoints.size(); i++) { tempPath = QPainterPath(); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { tempPath.addRect(fm.boundingRect(tickLabelStrings.at(i))); } else { td.setHtml(tickLabelStrings.at(i)); tempPath.addRect(QRectF(0, -td.size().height(), td.size().width(), td.size().height())); } trafo.reset(); trafo.translate( tickLabelPoints.at(i).x(), tickLabelPoints.at(i).y() ); trafo.rotate(-labelsRotationAngle); tempPath = trafo.map(tempPath); tickLabelsPath.addPath(WorksheetElement::shapeFromPath(tempPath, linePen)); } axisShape.addPath(WorksheetElement::shapeFromPath(tickLabelsPath, QPen())); } //add title label, if available if ( title->isVisible() && !title->text().text.isEmpty() ) { //determine the new position of the title label: //we calculate the new position here and not in retransform(), //since it depends on the size and position of the tick labels, tickLabelsPath, available here. QRectF rect = linePath.boundingRect(); qreal offsetX = titleOffsetX - labelsOffset; //the distance to the axis line qreal offsetY = titleOffsetY - labelsOffset; //the distance to the axis line if (orientation == Axis::AxisHorizontal) { offsetY -= title->graphicsItem()->boundingRect().height()/2; if (labelsPosition == Axis::LabelsOut) offsetY -= tickLabelsPath.boundingRect().height(); title->setPosition( QPointF( (rect.topLeft().x() + rect.topRight().x())/2 + titleOffsetX, rect.bottomLeft().y() - offsetY ) ); } else { offsetX -= title->graphicsItem()->boundingRect().width()/2; if (labelsPosition == Axis::LabelsOut) offsetX -= tickLabelsPath.boundingRect().width(); title->setPosition( QPointF( rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y())/2 - titleOffsetY) ); } axisShape.addPath(WorksheetElement::shapeFromPath(title->graphicsItem()->mapToParent(title->graphicsItem()->shape()), linePen)); } boundingRectangle = axisShape.boundingRect(); //if the axis goes beyond the current bounding box of the plot (too high offset is used, too long labels etc.) //request a prepareGeometryChange() for the plot in order to properly keep track of geometry changes if (plot) plot->prepareGeometryChange(); } /*! paints the content of the axis. Reimplemented from \c QGraphicsItem. \sa QGraphicsItem::paint() */ void AxisPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (linePath.isEmpty()) return; //draw the line if (linePen.style() != Qt::NoPen) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::SolidPattern); painter->drawPath(linePath); //draw the arrow if (arrowType != Axis::NoArrow) painter->drawPath(arrowPath); } //draw the major ticks if (majorTicksDirection != Axis::noTicks) { painter->setOpacity(majorTicksOpacity); painter->setPen(majorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(majorTicksPath); } //draw the minor ticks if (minorTicksDirection != Axis::noTicks) { painter->setOpacity(minorTicksOpacity); painter->setPen(minorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(minorTicksPath); } // draw tick labels if (labelsPosition != Axis::NoLabels) { painter->setOpacity(labelsOpacity); painter->setPen(QPen(labelsColor)); painter->setFont(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); if ((orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric)) { for (int i = 0; i < tickLabelPoints.size(); i++) { painter->translate(tickLabelPoints.at(i)); painter->save(); painter->rotate(-labelsRotationAngle); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); } else { td.setHtml(tickLabelStrings.at(i)); painter->translate(0, -td.size().height()); td.drawContents(painter); } painter->restore(); painter->translate(-tickLabelPoints.at(i)); } } else { // datetime for (int i = 0; i < tickLabelPoints.size(); i++) { painter->translate(tickLabelPoints.at(i)); painter->save(); painter->rotate(-labelsRotationAngle); painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); painter->restore(); painter->translate(-tickLabelPoints.at(i)); } } } if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(axisShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(axisShape); } } void AxisPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void AxisPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(axisShape.boundingRect()); } } void AxisPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(axisShape.boundingRect()); } } void AxisPrivate::setPrinting(bool on) { m_printing = on; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Axis::save(QXmlStreamWriter* writer) const{ Q_D(const Axis); writer->writeStartElement( "axis" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); writer->writeAttribute( "autoScale", QString::number(d->autoScale) ); writer->writeAttribute( "orientation", QString::number(d->orientation) ); writer->writeAttribute( "position", QString::number(d->position) ); writer->writeAttribute( "scale", QString::number(d->scale) ); writer->writeAttribute( "offset", QString::number(d->offset) ); writer->writeAttribute( "start", QString::number(d->start) ); writer->writeAttribute( "end", QString::number(d->end) ); writer->writeAttribute( "scalingFactor", QString::number(d->scalingFactor) ); writer->writeAttribute( "zeroOffset", QString::number(d->zeroOffset) ); writer->writeAttribute( "titleOffsetX", QString::number(d->titleOffsetX) ); writer->writeAttribute( "titleOffsetY", QString::number(d->titleOffsetY) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //label d->title->save( writer ); //line writer->writeStartElement( "line" ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeAttribute( "arrowType", QString::number(d->arrowType) ); writer->writeAttribute( "arrowPosition", QString::number(d->arrowPosition) ); writer->writeAttribute( "arrowSize", QString::number(d->arrowSize) ); writer->writeEndElement(); //major ticks writer->writeStartElement( "majorTicks" ); writer->writeAttribute( "direction", QString::number(d->majorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->majorTicksType) ); writer->writeAttribute( "number", QString::number(d->majorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->majorTicksIncrement) ); WRITE_COLUMN(d->majorTicksColumn, majorTicksColumn); writer->writeAttribute( "length", QString::number(d->majorTicksLength) ); WRITE_QPEN(d->majorTicksPen); writer->writeAttribute( "opacity", QString::number(d->majorTicksOpacity) ); writer->writeEndElement(); //minor ticks writer->writeStartElement( "minorTicks" ); writer->writeAttribute( "direction", QString::number(d->minorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->minorTicksType) ); writer->writeAttribute( "number", QString::number(d->minorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->minorTicksIncrement) ); WRITE_COLUMN(d->minorTicksColumn, minorTicksColumn); writer->writeAttribute( "length", QString::number(d->minorTicksLength) ); WRITE_QPEN(d->minorTicksPen); writer->writeAttribute( "opacity", QString::number(d->minorTicksOpacity) ); writer->writeEndElement(); //extra ticks //labels writer->writeStartElement( "labels" ); writer->writeAttribute( "position", QString::number(d->labelsPosition) ); writer->writeAttribute( "offset", QString::number(d->labelsOffset) ); writer->writeAttribute( "rotation", QString::number(d->labelsRotationAngle) ); writer->writeAttribute( "format", QString::number(d->labelsFormat) ); writer->writeAttribute( "precision", QString::number(d->labelsPrecision) ); writer->writeAttribute( "autoPrecision", QString::number(d->labelsAutoPrecision) ); writer->writeAttribute( "dateTimeFormat", d->labelsDateTimeFormat ); WRITE_QCOLOR(d->labelsColor); WRITE_QFONT(d->labelsFont); writer->writeAttribute( "prefix", d->labelsPrefix ); writer->writeAttribute( "suffix", d->labelsSuffix ); writer->writeAttribute( "opacity", QString::number(d->labelsOpacity) ); writer->writeEndElement(); //grid writer->writeStartElement( "majorGrid" ); WRITE_QPEN(d->majorGridPen); writer->writeAttribute( "opacity", QString::number(d->majorGridOpacity) ); writer->writeEndElement(); writer->writeStartElement( "minorGrid" ); WRITE_QPEN(d->minorGridPen); writer->writeAttribute( "opacity", QString::number(d->minorGridOpacity) ); writer->writeEndElement(); writer->writeEndElement(); // close "axis" section } //! Load from XML bool Axis::load(XmlStreamReader* reader, bool preview) { Q_D(Axis); if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "axis") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_INT_VALUE("autoScale", autoScale, bool); READ_INT_VALUE("orientation", orientation, Axis::AxisOrientation); READ_INT_VALUE("position", position, Axis::AxisPosition); READ_INT_VALUE("scale", scale, Axis::AxisScale); READ_DOUBLE_VALUE("offset", offset); READ_DOUBLE_VALUE("start", start); READ_DOUBLE_VALUE("end", end); READ_DOUBLE_VALUE("scalingFactor", scalingFactor); READ_DOUBLE_VALUE("zeroOffset", zeroOffset); READ_DOUBLE_VALUE("titleOffsetX", titleOffsetX); READ_DOUBLE_VALUE("titleOffsetY", titleOffsetY); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (reader->name() == "textLabel") { d->title->load(reader, preview); } else if (!preview && reader->name() == "line") { attribs = reader->attributes(); READ_QPEN(d->linePen); READ_DOUBLE_VALUE("opacity", lineOpacity); READ_INT_VALUE("arrowType", arrowType, Axis::ArrowType); READ_INT_VALUE("arrowPosition", arrowPosition, Axis::ArrowPosition); READ_DOUBLE_VALUE("arrowSize", arrowSize); } else if (!preview && reader->name() == "majorTicks") { attribs = reader->attributes(); READ_INT_VALUE("direction", majorTicksDirection, Axis::TicksDirection); READ_INT_VALUE("type", majorTicksType, Axis::TicksType); READ_INT_VALUE("number", majorTicksNumber, int); READ_DOUBLE_VALUE("increment", majorTicksIncrement); READ_COLUMN(majorTicksColumn); READ_DOUBLE_VALUE("length", majorTicksLength); READ_QPEN(d->majorTicksPen); READ_DOUBLE_VALUE("opacity", majorTicksOpacity); } else if (!preview && reader->name() == "minorTicks") { attribs = reader->attributes(); READ_INT_VALUE("direction", minorTicksDirection, Axis::TicksDirection); READ_INT_VALUE("type", minorTicksType, Axis::TicksType); READ_INT_VALUE("number", minorTicksNumber, int); READ_DOUBLE_VALUE("increment", minorTicksIncrement); READ_COLUMN(minorTicksColumn); READ_DOUBLE_VALUE("length", minorTicksLength); READ_QPEN(d->minorTicksPen); READ_DOUBLE_VALUE("opacity", minorTicksOpacity); } else if (!preview && reader->name() == "labels") { attribs = reader->attributes(); READ_INT_VALUE("position", labelsPosition, Axis::LabelsPosition); READ_DOUBLE_VALUE("offset", labelsOffset); READ_DOUBLE_VALUE("rotation", labelsRotationAngle); READ_INT_VALUE("format", labelsFormat, Axis::LabelsFormat); READ_INT_VALUE("precision", labelsPrecision, int); READ_INT_VALUE("autoPrecision", labelsAutoPrecision, bool); d->labelsDateTimeFormat = attribs.value("dateTimeFormat").toString(); READ_QCOLOR(d->labelsColor); READ_QFONT(d->labelsFont); //don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml) d->labelsPrefix = attribs.value("prefix").toString(); d->labelsSuffix = attribs.value("suffix").toString(); READ_DOUBLE_VALUE("opacity", labelsOpacity); } else if (!preview && reader->name() == "majorGrid") { attribs = reader->attributes(); READ_QPEN(d->majorGridPen); READ_DOUBLE_VALUE("opacity", majorGridOpacity); } else if (!preview && reader->name() == "minorGrid") { attribs = reader->attributes(); READ_QPEN(d->minorGridPen); READ_DOUBLE_VALUE("opacity", minorGridOpacity); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Axis::loadThemeConfig(const KConfig& config) { const KConfigGroup group = config.group("Axis"); //we don't want to show the major and minor grid lines for non-first horizontal/vertical axes //determine the index of the axis among other axes having the same orientation bool firstAxis = true; for (const auto* axis : parentAspect()->children()) { if (orientation() == axis->orientation()) { if (axis == this) { break; } else { firstAxis = false; break; } } } QPen p; // Tick label this->setLabelsColor(group.readEntry("LabelsFontColor",(QColor) this->labelsColor())); this->setLabelsOpacity(group.readEntry("LabelsOpacity",this->labelsOpacity())); //Line this->setLineOpacity(group.readEntry("LineOpacity",this->lineOpacity())); p.setColor(group.readEntry("LineColor", (QColor) this->linePen().color())); p.setStyle((Qt::PenStyle)group.readEntry("LineStyle",(int) this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); this->setLinePen(p); //Major ticks this->setMajorGridOpacity(group.readEntry("MajorGridOpacity", this->majorGridOpacity())); p.setColor(group.readEntry("MajorGridColor",(QColor) this->majorGridPen().color())); if (firstAxis) p.setStyle((Qt::PenStyle)group.readEntry("MajorGridStyle",(int) this->majorGridPen().style())); else p.setStyle(Qt::NoPen); p.setWidthF(group.readEntry("MajorGridWidth", this->majorGridPen().widthF())); this->setMajorGridPen(p); p.setColor(group.readEntry("MajorTicksColor",(QColor)this->majorTicksPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MajorTicksLineStyle",(int) this->majorTicksPen().style())); p.setWidthF(group.readEntry("MajorTicksWidth", this->majorTicksPen().widthF())); this->setMajorTicksPen(p); this->setMajorTicksOpacity(group.readEntry("MajorTicksOpacity",this->majorTicksOpacity())); //Minor ticks this->setMinorGridOpacity(group.readEntry("MinorGridOpacity", this->minorGridOpacity())); p.setColor(group.readEntry("MinorGridColor",(QColor) this->minorGridPen().color())); if (firstAxis) p.setStyle((Qt::PenStyle)group.readEntry("MinorGridStyle",(int) this->minorGridPen().style())); else p.setStyle(Qt::NoPen); p.setWidthF(group.readEntry("MinorGridWidth", this->minorGridPen().widthF())); this->setMinorGridPen(p); p.setColor(group.readEntry("MinorTicksColor",(QColor) this->minorTicksPen().color())); p.setWidthF(group.readEntry("MinorTicksWidth", this->minorTicksPen().widthF())); this->setMinorTicksPen(p); this->setMinorTicksOpacity(group.readEntry("MinorTicksOpacity",this->minorTicksOpacity())); const QVector& childElements = children(AbstractAspect::IncludeHidden); for (auto* child : childElements) child->loadThemeConfig(config); } void Axis::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Axis"); // Tick label group.writeEntry("LabelsFontColor", (QColor) this->labelsColor()); group.writeEntry("LabelsOpacity", this->labelsOpacity()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineColor", (QColor) this->linePen().color()); group.writeEntry("LineStyle", (int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Major ticks group.writeEntry("MajorGridOpacity", this->majorGridOpacity()); group.writeEntry("MajorGridColor", (QColor) this->majorGridPen().color()); group.writeEntry("MajorGridStyle", (int) this->majorGridPen().style()); group.writeEntry("MajorGridWidth", this->majorGridPen().widthF()); group.writeEntry("MajorTicksColor", (QColor)this->majorTicksPen().color()); group.writeEntry("MajorTicksLineStyle", (int) this->majorTicksPen().style()); group.writeEntry("MajorTicksWidth", this->majorTicksPen().widthF()); group.writeEntry("MajorTicksOpacity", this->majorTicksOpacity()); group.writeEntry("MajorTicksType", (int)this->majorTicksType()); //Minor ticks group.writeEntry("MinorGridOpacity", this->minorGridOpacity()); group.writeEntry("MinorGridColor",(QColor) this->minorGridPen().color()); group.writeEntry("MinorGridStyle", (int) this->minorGridPen().style()); group.writeEntry("MinorGridWidth", this->minorGridPen().widthF()); group.writeEntry("MinorTicksColor", (QColor) this->minorTicksPen().color()); group.writeEntry("MinorTicksLineStyle",( int) this->minorTicksPen().style()); group.writeEntry("MinorTicksWidth", this->minorTicksPen().widthF()); group.writeEntry("MinorTicksOpacity", this->minorTicksOpacity()); group.writeEntry("MinorTicksType", (int)this->minorTicksType()); const QVector& childElements = children(AbstractAspect::IncludeHidden); childElements.at(0)->saveThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/AxisPrivate.h b/src/backend/worksheet/plots/cartesian/AxisPrivate.h index 3b1629098..cab4cde5d 100644 --- a/src/backend/worksheet/plots/cartesian/AxisPrivate.h +++ b/src/backend/worksheet/plots/cartesian/AxisPrivate.h @@ -1,170 +1,171 @@ /*************************************************************************** File : AxisPrivate.h Project : LabPlot Description : Private members of Axis. -------------------------------------------------------------------- Copyright : (C) 2011-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 * * * ***************************************************************************/ #ifndef AXISPRIVATE_H #define AXISPRIVATE_H #include #include #include #include "Axis.h" class QGraphicsSceneHoverEvent; class AxisGrid; class CartesianPlot; class CartesianCoordinateSystem; class TextLabel; class AxisPrivate: public QGraphicsItem { public: explicit AxisPrivate(Axis*); QRectF boundingRect() const override; QPainterPath shape() const override; QString name() const; void retransform(); void retransformLine(); void retransformArrow(); void retransformTicks(); void retransformTickLabelPositions(); void retransformTickLabelStrings(); void retransformMinorGrid(); void retransformMajorGrid(); + void updateGrid(); bool swapVisible(bool); void recalcShapeAndBoundingRect(); void setPrinting(bool); //general bool autoScale; Axis::AxisOrientation orientation; //!< horizontal or vertical Axis::AxisPosition position; //!< left, right, bottom, top or custom (usually not changed after creation) Axis::AxisScale scale; double offset; //!< offset from zero in the direction perpendicular to the axis double start; //!< start coordinate of the axis line double end; //!< end coordinate of the axis line qreal scalingFactor; qreal zeroOffset; //line QVector lines; QPen linePen; qreal lineOpacity; Axis::ArrowType arrowType; Axis::ArrowPosition arrowPosition; qreal arrowSize; // Title TextLabel* title; qreal titleOffsetX; //distance to the axis line qreal titleOffsetY; //distance to the axis line // Ticks Axis::TicksDirection majorTicksDirection; //!< major ticks direction: inwards, outwards, both, or none Axis::TicksType majorTicksType; //!< the way how the number of major ticks is specified - either as a total number or an increment int majorTicksNumber; //!< number of major ticks qreal majorTicksIncrement; //!< increment (step) for the major ticks const AbstractColumn* majorTicksColumn{nullptr}; //!< column containing values for major ticks' positions QString majorTicksColumnPath; qreal majorTicksLength; //!< major tick length (in page units!) QPen majorTicksPen; qreal majorTicksOpacity; Axis::TicksDirection minorTicksDirection; //!< minor ticks direction: inwards, outwards, both, or none Axis::TicksType minorTicksType; //!< the way how the number of minor ticks is specified - either as a total number or an increment int minorTicksNumber; //!< number of minor ticks (between each two major ticks) qreal minorTicksIncrement; //!< increment (step) for the minor ticks const AbstractColumn* minorTicksColumn{nullptr}; //!< column containing values for minor ticks' positions QString minorTicksColumnPath; qreal minorTicksLength; //!< minor tick length (in page units!) QPen minorTicksPen; qreal minorTicksOpacity; // Tick Label Axis::LabelsFormat labelsFormat; int labelsPrecision; bool labelsAutoPrecision; QString labelsDateTimeFormat; Axis::LabelsPosition labelsPosition; qreal labelsRotationAngle; QColor labelsColor; QFont labelsFont; qreal labelsOffset; //!< offset, distance to the end of the tick line (in page units) qreal labelsOpacity; QString labelsPrefix; QString labelsSuffix; //Grid AxisGrid* gridItem; QPen majorGridPen; qreal majorGridOpacity; QPen minorGridPen; qreal minorGridOpacity; Axis* const q; QPainterPath linePath; QPainterPath majorGridPath; QPainterPath minorGridPath; bool suppressRetransform{false}; bool labelsFormatDecimalOverruled{false}; bool labelsFormatAutoChanged{false}; CartesianPlot* plot{nullptr}; const CartesianCoordinateSystem* cSystem{nullptr}; private: void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override; void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr) override; void addArrow(QPointF point, int direction); int upperLabelsPrecision(int); int lowerLabelsPrecision(int); bool transformAnchor(QPointF*); QPainterPath arrowPath; QPainterPath majorTicksPath; QPainterPath minorTicksPath; QRectF boundingRectangle; QPainterPath axisShape; QVector majorTickPoints;//!< position of the major ticks on the axis. QVector minorTickPoints;//!< position of the major ticks on the axis. QVector tickLabelPoints; //!< position of the major tick labels (left lower edge of label's bounding rect) QVector tickLabelValues; //!< major tick labels values QVector tickLabelStrings; //!< the actual text of the major tick labels bool m_hovered{false}; bool m_suppressRecalc{false}; bool m_printing{false}; }; #endif diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index e89276ee0..8cf6be8fc 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,3635 +1,3724 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2018 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017-2018 by Garvit Khatri (garvitdelhi@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CartesianPlot.h" #include "CartesianPlotPrivate.h" #include "Axis.h" #include "XYCurve.h" #include "Histogram.h" #include "XYEquationCurve.h" #include "XYDataReductionCurve.h" #include "XYDifferentiationCurve.h" #include "XYIntegrationCurve.h" #include "XYInterpolationCurve.h" #include "XYSmoothCurve.h" #include "XYFitCurve.h" #include "XYFourierFilterCurve.h" #include "XYFourierTransformCurve.h" #include "XYConvolutionCurve.h" #include "XYCorrelationCurve.h" #include "backend/core/Project.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum. #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/widgets/ThemesWidget.h" #include #include #include #include #include #include #include #include #include #include #include /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name) : AbstractPlot(name, new CartesianPlotPrivate(this), AspectType::CartesianPlot) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd) : AbstractPlot(name, dd, AspectType::CartesianPlot) { init(); } CartesianPlot::~CartesianPlot() { if (m_menusInitialized) { delete addNewMenu; delete zoomMenu; delete themeMenu; } delete m_coordinateSystem; //don't need to delete objects added with addChild() //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } /*! initializes all member variables of \c CartesianPlot */ void CartesianPlot::init() { Q_D(CartesianPlot); d->cSystem = new CartesianCoordinateSystem(this); m_coordinateSystem = d->cSystem; d->rangeType = CartesianPlot::RangeFree; d->xRangeFormat = CartesianPlot::Numeric; d->yRangeFormat = CartesianPlot::Numeric; d->xRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->yRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->rangeFirstValues = 1000; d->rangeLastValues = 1000; d->autoScaleX = true; d->autoScaleY = true; d->xScale = ScaleLinear; d->yScale = ScaleLinear; d->xRangeBreakingEnabled = false; d->yRangeBreakingEnabled = false; //the following factor determines the size of the offset between the min/max points of the curves //and the coordinate system ranges, when doing auto scaling //Factor 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. //TODO: make this factor optional. //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option d->autoScaleOffsetFactor = 0.0f; m_plotArea = new PlotArea(name() + " plot area"); addChildFast(m_plotArea); //Plot title m_title = new TextLabel(this->name() + QLatin1String("- ") + i18n("Title"), TextLabel::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //offset between the plot area and the area defining the coordinate system, in scene units. d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->rightPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->bottomPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->symmetricPadding = true; connect(this, &AbstractAspect::aspectAdded, this, &CartesianPlot::childAdded); connect(this, &AbstractAspect::aspectRemoved, this, &CartesianPlot::childRemoved); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); + + //theme is not set at this point, initialize the color palette with default colors + this->setColorPalette(KConfig()); } /*! initializes all children of \c CartesianPlot and setups a default plot of type \c type with a plot title. */ void CartesianPlot::initDefault(Type type) { Q_D(CartesianPlot); switch (type) { case FourAxes: { d->xMin = 0.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; //Axes Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); QPen pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("x axis 2", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisTop); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMinorGridPen(pen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("y axis 2", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisRight); axis->setStart(0); axis->setEnd(1); axis->setOffset(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxes: { d->xMin = 0.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); break; } case TwoAxesCentered: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxesCenteredZero: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } } d->xMinPrev = d->xMin; d->xMaxPrev = d->xMax; d->yMinPrev = d->yMin; d->yMaxPrev = d->yMax; //Geometry, specify the plot rect in scene coordinates. //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system float x = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float y = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float w = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); float h = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); //all plot children are initialized -> set the geometry of the plot in scene coordinates. d->rect = QRectF(x,y,w,h); d->retransform(); } void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this); addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), this); // no icons yet addDataReductionCurveAction = new QAction(i18n("xy-curve from a Data Reduction"), this); addDifferentiationCurveAction = new QAction(i18n("xy-curve from a Differentiation"), this); addIntegrationCurveAction = new QAction(i18n("xy-curve from an Integration"), this); addInterpolationCurveAction = new QAction(i18n("xy-curve from an Interpolation"), this); addSmoothCurveAction = new QAction(i18n("xy-curve from a Smooth"), this); addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve from a Fit to Data"), this); addFourierFilterCurveAction = new QAction(i18n("xy-curve from a Fourier Filter"), this); addFourierTransformCurveAction = new QAction(i18n("xy-curve from a Fourier Transform"), this); addConvolutionCurveAction = new QAction(i18n("xy-curve from a (De-)Convolution"), this); addCorrelationCurveAction = new QAction(i18n("xy-curve from a Auto-/Cross-Correlation"), this); // addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("xy-curve from an interpolation"), this); // addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smooth-curve"), i18n("xy-curve from a smooth"), this); // addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("xy-curve from a Fourier filter"), this); // addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_transform-curve"), i18n("xy-curve from a Fourier transform"), this); // addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("xy-curve from a (de-)convolution"), this); // addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-correlation-curve"), i18n("xy-curve from a auto-/cross-correlation"), this); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), this); if (children().size()>0) addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal Axis"), this); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical Axis"), this); addTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), this); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), this); connect(addCurveAction, &QAction::triggered, this, &CartesianPlot::addCurve); connect(addHistogramAction,&QAction::triggered, this, &CartesianPlot::addHistogram); connect(addEquationCurveAction, &QAction::triggered, this, &CartesianPlot::addEquationCurve); connect(addDataReductionCurveAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationCurveAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationCurveAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationCurveAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothCurveAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addFitCurveAction, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); connect(addFourierTransformCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve); connect(addConvolutionCurveAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationCurveAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); connect(addLegendAction, &QAction::triggered, this, static_cast(&CartesianPlot::addLegend)); connect(addHorizontalAxisAction, &QAction::triggered, this, &CartesianPlot::addHorizontalAxis); connect(addVerticalAxisAction, &QAction::triggered, this, &CartesianPlot::addVerticalAxis); connect(addTextLabelAction, &QAction::triggered, this, &CartesianPlot::addTextLabel); connect(addCustomPointAction, &QAction::triggered, this, &CartesianPlot::addCustomPoint); //Analysis menu actions addDataOperationAction = new QAction(i18n("Data Operation"), this); addDataReductionAction = new QAction(i18n("Reduce Data"), this); addDifferentiationAction = new QAction(i18n("Differentiate"), this); addIntegrationAction = new QAction(i18n("Integrate"), this); addInterpolationAction = new QAction(i18n("Interpolate"), this); addSmoothAction = new QAction(i18n("Smooth"), this); addConvolutionAction = new QAction(i18n("Convolute/Deconvolute"), this); addCorrelationAction = new QAction(i18n("Auto-/Cross-Correlation"), this); QAction* fitAction = new QAction(i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); addFitAction.append(fitAction); fitAction = new QAction(i18n("Power"), this); fitAction->setData(PlotDataDialog::FitPower); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 1)"), this); fitAction->setData(PlotDataDialog::FitExp1); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 2)"), this); fitAction->setData(PlotDataDialog::FitExp2); addFitAction.append(fitAction); fitAction = new QAction(i18n("Inverse exponential"), this); fitAction->setData(PlotDataDialog::FitInvExp); addFitAction.append(fitAction); fitAction = new QAction(i18n("Gauss"), this); fitAction->setData(PlotDataDialog::FitGauss); addFitAction.append(fitAction); fitAction = new QAction(i18n("Cauchy-Lorentz"), this); fitAction->setData(PlotDataDialog::FitCauchyLorentz); addFitAction.append(fitAction); fitAction = new QAction(i18n("Arc Tangent"), this); fitAction->setData(PlotDataDialog::FitTan); addFitAction.append(fitAction); fitAction = new QAction(i18n("Hyperbolic Tangent"), this); fitAction->setData(PlotDataDialog::FitTanh); addFitAction.append(fitAction); fitAction = new QAction(i18n("Error Function"), this); fitAction->setData(PlotDataDialog::FitErrFunc); addFitAction.append(fitAction); fitAction = new QAction(i18n("Custom"), this); fitAction->setData(PlotDataDialog::FitCustom); addFitAction.append(fitAction); addFourierFilterAction = new QAction(i18n("Fourier Filter"), this); connect(addDataReductionAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addConvolutionAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); for (const auto& action : addFitAction) connect(action, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); //zoom/navigate actions scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("Auto Scale"), this); scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("Auto Scale X"), this); scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("Auto Scale Y"), this); zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this); zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this); zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("Zoom In X"), this); zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("Zoom Out X"), this); zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("Zoom In Y"), this); zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("Zoom Out Y"), this); shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left X"), this); shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right X"), this); shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Up Y"), this); shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Down Y"), this); cursorAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Cursor"), this); // TODO: change icon connect(scaleAutoAction, &QAction::triggered, this, &CartesianPlot::scaleAuto); connect(scaleAutoXAction, &QAction::triggered, this, &CartesianPlot::scaleAutoX); connect(scaleAutoYAction, &QAction::triggered, this, &CartesianPlot::scaleAutoY); connect(zoomInAction, &QAction::triggered, this, &CartesianPlot::zoomIn); connect(zoomOutAction, &QAction::triggered, this, &CartesianPlot::zoomOut); connect(zoomInXAction, &QAction::triggered, this, &CartesianPlot::zoomInX); connect(zoomOutXAction, &QAction::triggered, this, &CartesianPlot::zoomOutX); connect(zoomInYAction, &QAction::triggered, this, &CartesianPlot::zoomInY); connect(zoomOutYAction, &QAction::triggered, this, &CartesianPlot::zoomOutY); connect(shiftLeftXAction, &QAction::triggered, this, &CartesianPlot::shiftLeftX); connect(shiftRightXAction, &QAction::triggered, this, &CartesianPlot::shiftRightX); connect(shiftUpYAction, &QAction::triggered, this, &CartesianPlot::shiftUpY); connect(shiftDownYAction, &QAction::triggered, this, &CartesianPlot::shiftDownY); connect(cursorAction, &QAction::triggered, this, &CartesianPlot::cursor); //visibility action visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlot::visibilityChanged); } void CartesianPlot::initMenus() { initActions(); addNewMenu = new QMenu(i18n("Add New")); addNewMenu->setIcon(QIcon::fromTheme("list-add")); addNewMenu->addAction(addCurveAction); addNewMenu->addAction(addHistogramAction); addNewMenu->addAction(addEquationCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addDataReductionCurveAction); addNewMenu->addAction(addDifferentiationCurveAction); addNewMenu->addAction(addIntegrationCurveAction); addNewMenu->addAction(addInterpolationCurveAction); addNewMenu->addAction(addSmoothCurveAction); addNewMenu->addAction(addFitCurveAction); addNewMenu->addAction(addFourierFilterCurveAction); addNewMenu->addAction(addFourierTransformCurveAction); addNewMenu->addAction(addConvolutionCurveAction); addNewMenu->addAction(addCorrelationCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addLegendAction); addNewMenu->addSeparator(); addNewMenu->addAction(addHorizontalAxisAction); addNewMenu->addAction(addVerticalAxisAction); addNewMenu->addSeparator(); addNewMenu->addAction(addTextLabelAction); addNewMenu->addSeparator(); addNewMenu->addAction(addCustomPointAction); zoomMenu = new QMenu(i18n("Zoom")); zoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); zoomMenu->addAction(scaleAutoAction); zoomMenu->addAction(scaleAutoXAction); zoomMenu->addAction(scaleAutoYAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInAction); zoomMenu->addAction(zoomOutAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInXAction); zoomMenu->addAction(zoomOutXAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInYAction); zoomMenu->addAction(zoomOutYAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftLeftXAction); zoomMenu->addAction(shiftRightXAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftUpYAction); zoomMenu->addAction(shiftDownYAction); // Data manipulation menu QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); dataManipulationMenu->addAction(addDataOperationAction); dataManipulationMenu->addAction(addDataReductionAction); // Data fit menu QMenu* dataFitMenu = new QMenu(i18n("Fit")); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); dataFitMenu->addAction(addFitAction.at(2)); dataFitMenu->addAction(addFitAction.at(3)); dataFitMenu->addAction(addFitAction.at(4)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(5)); dataFitMenu->addAction(addFitAction.at(6)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(7)); dataFitMenu->addAction(addFitAction.at(8)); dataFitMenu->addAction(addFitAction.at(9)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(10)); //analysis menu dataAnalysisMenu = new QMenu(i18n("Analysis")); dataAnalysisMenu->insertMenu(nullptr, dataManipulationMenu); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addDifferentiationAction); dataAnalysisMenu->addAction(addIntegrationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addInterpolationAction); dataAnalysisMenu->addAction(addSmoothAction); dataAnalysisMenu->addAction(addFourierFilterAction); dataAnalysisMenu->addAction(addConvolutionAction); dataAnalysisMenu->addAction(addCorrelationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addMenu(dataFitMenu); //themes menu themeMenu = new QMenu(i18n("Apply Theme")); themeMenu->setIcon(QIcon::fromTheme("color-management")); auto* themeWidget = new ThemesWidget(nullptr); connect(themeWidget, &ThemesWidget::themeSelected, this, &CartesianPlot::loadTheme); connect(themeWidget, &ThemesWidget::themeSelected, themeMenu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(themeWidget); themeMenu->addAction(widgetAction); m_menusInitialized = true; } QMenu* CartesianPlot::createContextMenu() { if (!m_menusInitialized) initMenus(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertMenu(firstAction, addNewMenu); menu->insertMenu(firstAction, zoomMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, themeMenu); menu->insertSeparator(firstAction); return menu; } QMenu* CartesianPlot::analysisMenu() { if (!m_menusInitialized) initMenus(); return dataAnalysisMenu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlot::icon() const { return QIcon::fromTheme("office-chart-line"); } QVector CartesianPlot::dependsOn() const { //aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices) QVector aspects; for (const auto* curve : children()) { if (curve->xColumn() && dynamic_cast(curve->xColumn()->parentAspect()) ) aspects << curve->xColumn()->parentAspect(); if (curve->yColumn() && dynamic_cast(curve->yColumn()->parentAspect()) ) aspects << curve->yColumn()->parentAspect(); } return aspects; } void CartesianPlot::navigate(CartesianPlot::NavigationOperation op) { if (op == ScaleAuto) scaleAuto(); else if (op == ScaleAutoX) scaleAutoX(); else if (op == ScaleAutoY) scaleAutoY(); else if (op == ZoomIn) zoomIn(); else if (op == ZoomOut) zoomOut(); else if (op == ZoomInX) zoomInX(); else if (op == ZoomOutX) zoomOutX(); else if (op == ZoomInY) zoomInY(); else if (op == ZoomOutY) zoomOutY(); else if (op == ShiftLeftX) shiftLeftX(); else if (op == ShiftRightX) shiftRightX(); else if (op == ShiftUpY) shiftUpY(); else if (op == ShiftDownY) shiftDownY(); } void CartesianPlot::setSuppressDataChangedSignal(bool value) { Q_D(CartesianPlot); d->suppressRetransform = value; } void CartesianPlot::processDropEvent(QDropEvent* event) { PERFTRACE("CartesianPlot::processDropEvent"); const QMimeData* mimeData = event->mimeData(); if (!mimeData) return; //deserialize the mime data to the vector of aspect pointers QByteArray data = mimeData->data(QLatin1String("labplot-dnd")); QVector vec; QDataStream stream(&data, QIODevice::ReadOnly); stream >> vec; QVector columns; for (auto a : vec) { auto* aspect = (AbstractAspect*)a; auto* column = dynamic_cast(aspect); if (column) columns << column; } //return if there are no columns being dropped. //TODO: extend this later when we allow to drag&drop plots, etc. if (columns.isEmpty()) return; //determine the first column with "x plot designation" as the x-data column for all curves to be created const AbstractColumn* xColumn = nullptr; for (const auto* column : columns) { if (column->plotDesignation() == AbstractColumn::X) { xColumn = column; break; } } //if no column with "x plot designation" is available, use the x-data column of the first curve in the plot, if (xColumn == nullptr) { QVector curves = children(); if (!curves.isEmpty()) xColumn = curves.at(0)->xColumn(); } //use the first dropped column if no column with "x plot designation" nor curves are available if (xColumn == nullptr) xColumn = columns.at(0); //create curves bool curvesAdded = false; for (const auto* column : columns) { if (column == xColumn) continue; XYCurve* curve = new XYCurve(column->name()); curve->suppressRetransform(true); //suppress retransform, all curved will be recalculated at the end curve->setXColumn(xColumn); curve->setYColumn(column); addChild(curve); curve->suppressRetransform(false); curvesAdded = true; } if (curvesAdded) dataChanged(); } bool CartesianPlot::isPanningActive() const { Q_D(const CartesianPlot); return d->panningStarted; } //############################################################################## //################################ getter methods ############################ //############################################################################## BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormat) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormat) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMax, xMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, xScale, xScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMax, yMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, yScale, yScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks) CLASS_SHARED_D_READER_IMPL(CartesianPlot, QPen, cursorPen, cursorPen); CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor0Enable, cursor0Enable); CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor1Enable, cursor1Enable); CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme) /*! returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding) in plot's coordinates */ QRectF CartesianPlot::dataRect() const { Q_D(const CartesianPlot); return d->dataRect; } CartesianPlot::MouseMode CartesianPlot::mouseMode() const { Q_D(const CartesianPlot); return d->mouseMode; } const QString& CartesianPlot::xRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->xRangeDateTimeFormat; } const QString& CartesianPlot::yRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->yRangeDateTimeFormat; } //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## /*! set the rectangular, defined in scene coordinates */ class CartesianPlotSetRectCmd : public QUndoCommand { public: CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) { setText(i18n("%1: change geometry rect", m_private->name())); }; void redo() override { // const double horizontalRatio = m_rect.width() / m_private->rect.width(); // const double verticalRatio = m_rect.height() / m_private->rect.height(); qSwap(m_private->rect, m_rect); // m_private->q->handleResize(horizontalRatio, verticalRatio, false); m_private->retransform(); emit m_private->q->rectChanged(m_private->rect); }; void undo() override { redo(); } private: CartesianPlotPrivate* m_private; QRectF m_rect; }; void CartesianPlot::setRect(const QRectF& rect) { Q_D(CartesianPlot); if (rect != d->rect) exec(new CartesianPlotSetRectCmd(d, rect)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged); void CartesianPlot::setRangeType(RangeType type) { Q_D(CartesianPlot); if (type != d->rangeType) exec(new CartesianPlotSetRangeTypeCmd(d, type, ki18n("%1: set range type"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeFormat, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormatChanged); void CartesianPlot::setXRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->xRangeFormat) exec(new CartesianPlotSetXRangeFormatCmd(d, format, ki18n("%1: set x-range format"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeFormat, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormatChanged); void CartesianPlot::setYRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->yRangeFormat) exec(new CartesianPlotSetYRangeFormatCmd(d, format, ki18n("%1: set y-range format"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged); void CartesianPlot::setRangeLastValues(int values) { Q_D(CartesianPlot); if (values != d->rangeLastValues) exec(new CartesianPlotSetRangeLastValuesCmd(d, values, ki18n("%1: set range"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged); void CartesianPlot::setRangeFirstValues(int values) { Q_D(CartesianPlot); if (values != d->rangeFirstValues) exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, ki18n("%1: set range"))); } class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleX; if (m_autoScale) { m_minOld = m_private->xMin; m_maxOld = m_private->xMax; m_private->q->scaleAutoX(); } m_private->autoScaleX = m_autoScale; emit m_private->q->xAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->xMin = m_minOld; m_private->xMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleX = m_autoScaleOld; emit m_private->q->xAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleX(bool autoScaleX) { Q_D(CartesianPlot); if (autoScaleX != d->autoScaleX) exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales) void CartesianPlot::setXMin(double xMin) { Q_D(CartesianPlot); if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales) void CartesianPlot::setXMax(double xMax) { Q_D(CartesianPlot); if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales) void CartesianPlot::setXScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->xScale) exec(new CartesianPlotSetXScaleCmd(d, scale, ki18n("%1: set x scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales) void CartesianPlot::setXRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->xRangeBreakingEnabled) exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, ki18n("%1: x-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales) void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) { Q_D(CartesianPlot); exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, ki18n("%1: x-range breaks changed"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleY; if (m_autoScale) { m_minOld = m_private->yMin; m_maxOld = m_private->yMax; m_private->q->scaleAutoY(); } m_private->autoScaleY = m_autoScale; emit m_private->q->yAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->yMin = m_minOld; m_private->yMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleY = m_autoScaleOld; emit m_private->q->yAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleY(bool autoScaleY) { Q_D(CartesianPlot); if (autoScaleY != d->autoScaleY) exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, double, yMin, retransformScales) void CartesianPlot::setYMin(double yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales) void CartesianPlot::setYMax(double yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%1: set max y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales) void CartesianPlot::setYScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->yScale) exec(new CartesianPlotSetYScaleCmd(d, scale, ki18n("%1: set y scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales) void CartesianPlot::setYRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->yRangeBreakingEnabled) exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, ki18n("%1: y-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales) void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) { Q_D(CartesianPlot); exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, ki18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursorPen, QPen, cursorPen, update) void CartesianPlot::setCursorPen(const QPen &pen) { Q_D(CartesianPlot); if (pen != d->cursorPen) exec(new CartesianPlotSetCursorPenCmd(d, pen, ki18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor0Enable, bool, cursor0Enable, updateCursor) void CartesianPlot::setCursor0Enable(const bool &enable) { Q_D(CartesianPlot); if (enable != d->cursor0Enable) { if (std::isnan(d->cursor0Pos.x())) { // if never set, set initial position d->cursor0Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x()); mousePressCursorModeSignal(0, d->cursor0Pos); // simulate mousePress to update values in the cursor dock } exec(new CartesianPlotSetCursor0EnableCmd(d, enable, ki18n("%1: Cursor0 enable"))); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor1Enable, bool, cursor1Enable, updateCursor) void CartesianPlot::setCursor1Enable(const bool &enable) { Q_D(CartesianPlot); if (enable != d->cursor1Enable) { if (std::isnan(d->cursor1Pos.x())) { // if never set, set initial position d->cursor1Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x()); mousePressCursorModeSignal(1, d->cursor0Pos); // simulate mousePress to update values in the cursor dock } exec(new CartesianPlotSetCursor1EnableCmd(d, enable, ki18n("%1: Cursor1 enable"))); } } STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme) void CartesianPlot::setTheme(const QString& theme) { Q_D(CartesianPlot); if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } else exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: disable theming"))); } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", Axis::AxisHorizontal); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(xMin()); axis->setEnd(xMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addVerticalAxis() { Axis* axis = new Axis("y-axis", Axis::AxisVertical); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(yMin()); axis->setEnd(yMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addCurve() { addChild(new XYCurve("xy-curve")); } void CartesianPlot::addEquationCurve() { addChild(new XYEquationCurve("f(x)")); } void CartesianPlot::addHistogram() { addChild(new Histogram("Histogram")); } /*! * returns the first selected XYCurve in the plot */ const XYCurve* CartesianPlot::currentCurve() const { for (const auto* curve : this->children()) { if (curve->graphicsItem()->isSelected()) return curve; } return nullptr; } void CartesianPlot::addDataReductionCurve() { XYDataReductionCurve* curve = new XYDataReductionCurve("Data reduction"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: reduce '%2'", name(), curCurve->name()) ); curve->setName( i18n("Reduction of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->dataReductionDataChanged(curve->dataReductionData()); } else { beginMacro(i18n("%1: add data reduction curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addDifferentiationCurve() { XYDifferentiationCurve* curve = new XYDifferentiationCurve("Differentiation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Derivative of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->differentiationDataChanged(curve->differentiationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addIntegrationCurve() { XYIntegrationCurve* curve = new XYIntegrationCurve("Integration"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Integral of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->integrationDataChanged(curve->integrationData()); } else { beginMacro(i18n("%1: add integration curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addInterpolationCurve() { XYInterpolationCurve* curve = new XYInterpolationCurve("Interpolation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Interpolation of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); curve->recalculate(); this->addChild(curve); emit curve->interpolationDataChanged(curve->interpolationData()); } else { beginMacro(i18n("%1: add interpolation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addSmoothCurve() { XYSmoothCurve* curve = new XYSmoothCurve("Smooth"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) ); curve->setName( i18n("Smoothing of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->smoothDataChanged(curve->smoothData()); } else { beginMacro(i18n("%1: add smoothing curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFitCurve() { DEBUG("CartesianPlot::addFitCurve()"); XYFitCurve* curve = new XYFitCurve("fit"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fit to '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); //set the fit model category and type const auto* action = qobject_cast(QObject::sender()); PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); curve->initFitData(type); curve->initStartValues(curCurve); //fit with weights for y if the curve has error bars for y if (curCurve->yErrorType() == XYCurve::SymmetricError && curCurve->yErrorPlusColumn()) { XYFitCurve::FitData fitData = curve->fitData(); fitData.yWeightsType = nsl_fit_weight_instrumental; curve->setFitData(fitData); curve->setYErrorColumn(curCurve->yErrorPlusColumn()); } curve->recalculate(); //add the child after the fit was calculated so the dock widgets gets the fit results //and call retransform() after this to calculate and to paint the data points of the fit-curve this->addChild(curve); curve->retransform(); } else { beginMacro(i18n("%1: add fit curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierFilterCurve() { XYFourierFilterCurve* curve = new XYFourierFilterCurve("Fourier filter"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); } else { beginMacro(i18n("%1: add Fourier filter curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierTransformCurve() { XYFourierTransformCurve* curve = new XYFourierTransformCurve("Fourier transform"); this->addChild(curve); } void CartesianPlot::addConvolutionCurve() { XYConvolutionCurve* curve = new XYConvolutionCurve("Convolution"); this->addChild(curve); } void CartesianPlot::addCorrelationCurve() { XYCorrelationCurve* curve = new XYCorrelationCurve("Auto-/Cross-Correlation"); this->addChild(curve); } /*! * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser. */ void CartesianPlot::addLegend(CartesianPlotLegend* legend) { m_legend = legend; this->addChild(legend); } void CartesianPlot::addLegend() { //don't do anything if there's already a legend if (m_legend) return; m_legend = new CartesianPlotLegend(this, "legend"); this->addChild(m_legend); m_legend->retransform(); //only one legend is allowed -> disable the action if (m_menusInitialized) addLegendAction->setEnabled(false); } void CartesianPlot::addTextLabel() { TextLabel* label = new TextLabel("text label"); this->addChild(label); label->setParentGraphicsItem(graphicsItem()); } void CartesianPlot::addCustomPoint() { CustomPoint* point = new CustomPoint(this, "custom point"); this->addChild(point); } int CartesianPlot::curveCount(){ return children().length(); } const XYCurve* CartesianPlot::getCurve(int index){ return children()[index]; } double CartesianPlot::cursorPos(int cursorNumber) { Q_D(CartesianPlot); if (cursorNumber == 0) return d->cursor0Pos.x(); else return d->cursor1Pos.x(); } void CartesianPlot::childAdded(const AbstractAspect* child) { Q_D(CartesianPlot); const auto* curve = qobject_cast(child); if (curve) { connect(curve, &XYCurve::dataChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xDataChanged, this, &CartesianPlot::xDataChanged); + connect(curve, &XYCurve::xErrorTypeChanged, this, &CartesianPlot::dataChanged); + connect(curve, &XYCurve::xErrorPlusColumnChanged, this, &CartesianPlot::dataChanged); + connect(curve, &XYCurve::xErrorMinusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::yDataChanged, this, &CartesianPlot::yDataChanged); + connect(curve, &XYCurve::yErrorTypeChanged, this, &CartesianPlot::dataChanged); + connect(curve, &XYCurve::yErrorPlusColumnChanged, this, &CartesianPlot::dataChanged); + connect(curve, &XYCurve::yErrorMinusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, static_cast(&XYCurve::visibilityChanged), this, &CartesianPlot::curveVisibilityChanged); //update the legend on changes of the name, line and symbol styles connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::lineTypeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::linePenChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::lineOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsStyleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsSizeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsRotationAngleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsBrushChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsPenChanged, this, &CartesianPlot::updateLegend); connect(curve, SIGNAL(linePenChanged(QPen)), this, SIGNAL(curveLinePenChanged(QPen))); // feed forward linePenChanged, because Worksheet needs because CursorDock must be updated too updateLegend(); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; //in case the first curve is added, check whether we start plotting datetime data if (children().size() == 1) { const auto* col = dynamic_cast(curve->xColumn()); if (col) { if (col->columnMode() == AbstractColumn::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all horizontal axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisHorizontal) { auto* filter = static_cast(col->outputFilter()); d->xRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->xRangeDateTimeFormat); axis->setUndoAware(true); } } } } col = dynamic_cast(curve->yColumn()); if (col) { if (col->columnMode() == AbstractColumn::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all vertical axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisVertical) { auto* filter = static_cast(col->outputFilter()); d->yRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->yRangeDateTimeFormat); axis->setUndoAware(true); } } } } } emit curveAdded(curve); } else { const auto* hist = qobject_cast(child); if (hist) { connect(hist, &Histogram::dataChanged, this, &CartesianPlot::dataChanged); connect(hist, &Histogram::visibilityChanged, this, &CartesianPlot::curveVisibilityChanged); updateLegend(); } // if an element is hovered, the curves which are handled manually in this class // must be unhovered const WorksheetElement* element = static_cast(child); connect(element, &WorksheetElement::hovered, this, &CartesianPlot::childHovered); } if (!isLoading()) { //if a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty()) { const auto* elem = dynamic_cast(child); if (elem) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(elem)->loadThemeConfig(config); } } else { //no theme is available, apply the default colors for curves only, s.a. XYCurve::loadThemeConfig() const auto* curve = dynamic_cast(child); if (curve) { int index = indexOfChild(curve); QColor themeColor; if (index < m_themeColorPalette.size()) themeColor = m_themeColorPalette.at(index); else { if (m_themeColorPalette.size()) themeColor = m_themeColorPalette.last(); } auto* c = const_cast(curve); //Line QPen p = curve->linePen(); p.setColor(themeColor); c->setLinePen(p); //Drop line p = curve->dropLinePen(); p.setColor(themeColor); c->setDropLinePen(p); //Symbol QBrush brush = c->symbolsBrush(); brush.setColor(themeColor); c->setSymbolsBrush(brush); p = c->symbolsPen(); p.setColor(themeColor); c->setSymbolsPen(p); //Filling c->setFillingFirstColor(themeColor); //Error bars p.setColor(themeColor); c->setErrorBarsPen(p); } } } } void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); if (m_legend == child) { if (m_menusInitialized) addLegendAction->setEnabled(true); m_legend = nullptr; } else { const auto* curve = qobject_cast(child); if (curve) { updateLegend(); emit curveRemoved(curve); } } } /*! * \brief CartesianPlot::childHovered * Unhover all curves, when another child is hovered. The hover handling for the curves is done in their parent (CartesianPlot), * because the hover should set when the curve is hovered and not just the bounding rect (for more see hoverMoveEvent) */ void CartesianPlot::childHovered() { Q_D(CartesianPlot); bool curveSender = dynamic_cast(QObject::sender()) != nullptr; if (!d->isSelected()) { if (d->m_hovered) d->m_hovered = false; d->update(); } if (!curveSender) { for (auto curve: children()) curve->setHover(false); } } void CartesianPlot::updateLegend() { if (m_legend) m_legend->retransform(); } /*! called when in one of the curves the data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::dataChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; bool updated = false; if (d->autoScaleX && d->autoScaleY) updated = this->scaleAuto(); else if (d->autoScaleX) updated = this->scaleAutoX(); else if (d->autoScaleY) updated = this->scaleAutoY(); if (!updated || !QObject::sender()) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); else { //no sender available, the function was called in CartesianPlot::dataChanged() //via plot->dataChaged() in the file filter (live data source got new data) //-> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive for (auto* c : children()) { c->recalcLogicalPoints(); c->retransform(); } } } } } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::xDataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; bool updated = false; if (d->autoScaleX) updated = this->scaleAutoX(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); const AbstractColumn* col = curve->xColumn(); if (col->columnMode() == AbstractColumn::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } emit curveDataChanged(dynamic_cast(QObject::sender())); } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::yDataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; bool updated = false; if (d->autoScaleY) this->scaleAutoY(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); const AbstractColumn* col = curve->yColumn(); if (col->columnMode() == AbstractColumn::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } emit curveDataChanged(dynamic_cast(QObject::sender())); } void CartesianPlot::curveVisibilityChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; updateLegend(); if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); emit curveVisibilityChangedSignal(); } void CartesianPlot::curveLinePenChanged(QPen pen) { const auto* curve = qobject_cast(QObject::sender()); emit curveLinePenChanged(pen, curve->name()); } void CartesianPlot::setMouseMode(const MouseMode mouseMode) { Q_D(CartesianPlot); d->mouseMode = mouseMode; d->setHandlesChildEvents(mouseMode != CartesianPlot::SelectionMode); QList items = d->childItems(); if (d->mouseMode == CartesianPlot::SelectionMode) { for (auto* item : items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, false); } else { for (auto* item : items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, true); } //when doing zoom selection, prevent the graphics item from being movable //if it's currently movable (no worksheet layout available) const auto* worksheet = dynamic_cast(parentAspect()); if (worksheet) { if (mouseMode == CartesianPlot::SelectionMode) { if (worksheet->layout() != Worksheet::NoLayout) graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); else graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); } else //zoom m_selection graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } - emit mouseModeChanged(); + emit mouseModeChanged(mouseMode); } void CartesianPlot::setLocked(bool locked) { Q_D(CartesianPlot); d->locked = locked; } bool CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { - int count = 0; - switch (d->rangeType) { - case CartesianPlot::RangeFree: - count = 0; - break; - case CartesianPlot::RangeLast: - count = -d->rangeLastValues; - break; - case CartesianPlot::RangeFirst: - count = d->rangeFirstValues; - break; - } - - d->curvesXMin = INFINITY; - d->curvesXMax = -INFINITY; - - //loop over all xy-curves and determine the maximum and minimum x-values - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - if (!curve->xColumn()) - continue; - - const double min = curve->xColumn()->minimum(count); - if (min < d->curvesXMin) - d->curvesXMin = min; - - const double max = curve->xColumn()->maximum(count); - if (max > d->curvesXMax) - d->curvesXMax = max; - } - - //loop over all histograms and determine the maximum and minimum x-values - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - if (!curve->dataColumn()) - continue; - - const double min = curve->getXMinimum(); - if (d->curvesXMin > min) - d->curvesXMin = min; - - const double max = curve->getXMaximum(); - if (max > d->curvesXMax) - d->curvesXMax = max; - } - + calculateCurvesXMinMax(); d->curvesXMinMaxIsDirty = false; } bool update = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; update = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; update = true; } if (update) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } setAutoScaleX(true); d->retransformScales(); } return update; } bool CartesianPlot::scaleAutoY() { Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { - int count = 0; - switch (d->rangeType) { - case CartesianPlot::RangeFree: - count = 0; - break; - case CartesianPlot::RangeLast: - count = -d->rangeLastValues; - break; - case CartesianPlot::RangeFirst: - count = d->rangeFirstValues; - break; - } - - d->curvesYMin = INFINITY; - d->curvesYMax = -INFINITY; - - //loop over all xy-curves and determine the maximum and minimum y-values - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - if (!curve->yColumn()) - continue; - - const double min = curve->yColumn()->minimum(count); - if (min < d->curvesYMin) - d->curvesYMin = min; - - const double max = curve->yColumn()->maximum(count); - if (max > d->curvesYMax) - d->curvesYMax = max; - } - - //loop over all histograms and determine the maximum y-value - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - - const double min = curve->getYMinimum(); - if (d->curvesYMin > min) - d->curvesYMin = min; - - const double max = curve->getYMaximum(); - if (max > d->curvesYMax) - d->curvesYMax = max; - } - + calculateCurvesYMinMax(); d->curvesYMinMaxIsDirty = false; } - bool update = false; if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; update = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; update = true; } if (update) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } setAutoScaleY(true); d->retransformScales(); } return update; } bool CartesianPlot::scaleAuto() { DEBUG("CartesianPlot::scaleAuto()"); Q_D(CartesianPlot); - int count = 0; - switch (d->rangeType) { - case CartesianPlot::RangeFree: - count = 0; - break; - case CartesianPlot::RangeLast: - count = -d->rangeLastValues; - break; - case CartesianPlot::RangeFirst: - count = d->rangeFirstValues; - break; - } - if (d->curvesXMinMaxIsDirty) { - d->curvesXMin = INFINITY; - d->curvesXMax = -INFINITY; - - //loop over all xy-curves and determine the maximum and minimum x-values - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - if (!curve->xColumn()) - continue; - - const double min = curve->xColumn()->minimum(count); - if (min < d->curvesXMin) - d->curvesXMin = min; - - double max = curve->xColumn()->maximum(count); - if (max > d->curvesXMax) - d->curvesXMax = max; - } - - //loop over all histograms and determine the maximum and minimum x-values - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - if (!curve->dataColumn()) - continue; - - const double min = curve->getXMinimum(); - if (d->curvesXMin > min) - d->curvesXMin = min; - - const double max = curve->getXMaximum(); - if (max > d->curvesXMax) - d->curvesXMax = max; - - } - + calculateCurvesXMinMax(); d->curvesXMinMaxIsDirty = false; } if (d->curvesYMinMaxIsDirty) { - d->curvesYMin = INFINITY; - d->curvesYMax = -INFINITY; - - //loop over all xy-curves and determine the maximum and minimum y-values - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - if (!curve->yColumn()) - continue; - - const double min = curve->yColumn()->minimum(count); - if (min < d->curvesYMin) - d->curvesYMin = min; - - const double max = curve->yColumn()->maximum(count); - if (max > d->curvesYMax) - d->curvesYMax = max; - } - - //loop over all histograms and determine the maximum y-value - for (const auto* curve : this->children()) { - if (!curve->isVisible()) - continue; - - const double min = curve->getYMinimum(); - if (d->curvesYMin > min) - d->curvesYMin = min; - - const double max = curve->getYMaximum(); - if (max > d->curvesYMax) - d->curvesYMax = max; - } + calculateCurvesYMinMax(); + d->curvesYMinMaxIsDirty = false; } bool updateX = false; bool updateY = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; updateX = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; updateX = true; } if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; updateY = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; updateY = true; } DEBUG(" xmin/xmax = " << d->xMin << '/' << d->xMax << ", ymin/ymax = " << d->yMin << '/' << d->yMax); if (updateX || updateY) { if (updateX) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } setAutoScaleX(true); } if (updateY) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } setAutoScaleY(true); } d->retransformScales(); } return (updateX || updateY); } +void CartesianPlot::calculateCurvesXMinMax() { + Q_D(CartesianPlot); + int count = 0; + switch (d->rangeType) { + case CartesianPlot::RangeFree: + count = 0; + break; + case CartesianPlot::RangeLast: + count = -d->rangeLastValues; + break; + case CartesianPlot::RangeFirst: + count = d->rangeFirstValues; + break; + } + + d->curvesXMin = INFINITY; + d->curvesXMax = -INFINITY; + + //loop over all xy-curves and determine the maximum and minimum x-values + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + + auto* xColumn = curve->xColumn(); + if (!xColumn) + continue; + + double min = xColumn->minimum(count); + if (min < d->curvesXMin) + d->curvesXMin = min; + + double max = xColumn->maximum(count); + if (max > d->curvesXMax) + d->curvesXMax = max; + + //take error bars into account + auto xErrorType = curve->xErrorType(); + if (xErrorType != XYCurve::NoError) { + //consider error bars only if error columns are set + auto* xErrorPlusColumn = curve->xErrorPlusColumn(); + auto* xErrorMinusColumn = curve->xErrorMinusColumn(); + if ( (xErrorType == XYCurve::SymmetricError && xErrorPlusColumn) + || (xErrorType == XYCurve::AsymmetricError && (xErrorPlusColumn || xErrorMinusColumn)) ) { + + int start =0; + int end = 0; + switch (d->rangeType) { + case CartesianPlot::RangeFree: + start = 0; + end = xColumn->rowCount(); + break; + case CartesianPlot::RangeLast: + start = xColumn->rowCount() - d->rangeLastValues; + end = xColumn->rowCount(); + break; + case CartesianPlot::RangeFirst: + start = 0; + end = d->rangeFirstValues; + break; + } + for (int i = start; i < end; ++i) { + if (!xColumn->isValid(i) || xColumn->isMasked(i)) + continue; + + if ( (xErrorPlusColumn && i >= xErrorPlusColumn->rowCount()) + || (xErrorMinusColumn && i >= xErrorMinusColumn->rowCount()) ) + continue; + + //determine the values for the errors + double errorPlus, errorMinus; + if (xErrorPlusColumn && xErrorPlusColumn->isValid(i) && !xErrorPlusColumn->isMasked(i)) + errorPlus = xErrorPlusColumn->valueAt(i); + else + errorPlus = 0; + + if (xErrorType == XYCurve::SymmetricError) + errorMinus = errorPlus; + else { + if (xErrorMinusColumn && xErrorMinusColumn->isValid(i) && !xErrorMinusColumn->isMasked(i)) + errorMinus = xErrorMinusColumn->valueAt(i); + else + errorMinus = 0; + } + + min = xColumn->valueAt(i) - errorMinus; + if (min < d->curvesXMin) + d->curvesXMin = min; + + max = xColumn->valueAt(i) + errorPlus; + if (max > d->curvesXMax) + d->curvesXMax = max; + } + } + } + } + + //loop over all histograms and determine the maximum and minimum x-values + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + if (!curve->dataColumn()) + continue; + + const double min = curve->getXMinimum(); + if (d->curvesXMin > min) + d->curvesXMin = min; + + const double max = curve->getXMaximum(); + if (max > d->curvesXMax) + d->curvesXMax = max; + } +} + +void CartesianPlot::calculateCurvesYMinMax() { + Q_D(CartesianPlot); + int count = 0; + switch (d->rangeType) { + case CartesianPlot::RangeFree: + count = 0; + break; + case CartesianPlot::RangeLast: + count = -d->rangeLastValues; + break; + case CartesianPlot::RangeFirst: + count = d->rangeFirstValues; + break; + } + + d->curvesYMin = INFINITY; + d->curvesYMax = -INFINITY; + + //loop over all xy-curves and determine the maximum and minimum y-values + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + + auto* yColumn = curve->yColumn(); + if (!yColumn) + continue; + + double min = curve->yColumn()->minimum(count); + if (min < d->curvesYMin) + d->curvesYMin = min; + + double max = curve->yColumn()->maximum(count); + if (max > d->curvesYMax) + d->curvesYMax = max; + + //take error bars into account + auto yErrorType = curve->yErrorType(); + if (yErrorType != XYCurve::NoError) { + //consider error bars only if error columns are set + auto* yErrorPlusColumn = curve->yErrorPlusColumn(); + auto* yErrorMinusColumn = curve->yErrorMinusColumn(); + if ( (yErrorType == XYCurve::SymmetricError && yErrorPlusColumn) + || (yErrorType == XYCurve::AsymmetricError && (yErrorPlusColumn || yErrorMinusColumn)) ) { + + int start =0; + int end = 0; + switch (d->rangeType) { + case CartesianPlot::RangeFree: + start = 0; + end = yColumn->rowCount(); + break; + case CartesianPlot::RangeLast: + start = yColumn->rowCount() - d->rangeLastValues; + end = yColumn->rowCount(); + break; + case CartesianPlot::RangeFirst: + start = 0; + end = d->rangeFirstValues; + break; + } + for (int i = start; i < end; ++i) { + if (!yColumn->isValid(i) || yColumn->isMasked(i)) + continue; + + if ( (yErrorPlusColumn && i >= yErrorPlusColumn->rowCount()) + || (yErrorMinusColumn && i >= yErrorMinusColumn->rowCount()) ) + continue; + + //determine the values for the errors + double errorPlus, errorMinus; + if (yErrorPlusColumn && yErrorPlusColumn->isValid(i) && !yErrorPlusColumn->isMasked(i)) + errorPlus = yErrorPlusColumn->valueAt(i); + else + errorPlus = 0; + + if (yErrorType == XYCurve::SymmetricError) + errorMinus = errorPlus; + else { + if (yErrorMinusColumn && yErrorMinusColumn->isValid(i) && !yErrorMinusColumn->isMasked(i)) + errorMinus = yErrorMinusColumn->valueAt(i); + else + errorMinus = 0; + } + + min = yColumn->valueAt(i) - errorMinus; + if (min < d->curvesYMin) + d->curvesYMin = min; + + max = yColumn->valueAt(i) + errorPlus; + if (max > d->curvesYMax) + d->curvesYMax = max; + } + } + } + } + + //loop over all histograms and determine the maximum y-value + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + + const double min = curve->getYMinimum(); + if (d->curvesYMin > min) + d->curvesYMin = min; + + const double max = curve->getYMaximum(); + if (max > d->curvesYMax) + d->curvesYMax = max; + } + + +} + void CartesianPlot::zoomIn() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->xMax - d->xMin); double newRange = (d->xMax - d->xMin) / m_zoomFactor; d->xMax = d->xMax + (newRange - oldRange) / 2; d->xMin = d->xMin - (newRange - oldRange) / 2; oldRange = (d->yMax - d->yMin); newRange = (d->yMax - d->yMin) / m_zoomFactor; d->yMax = d->yMax + (newRange - oldRange) / 2; d->yMin = d->yMin - (newRange - oldRange) / 2; d->retransformScales(); } void CartesianPlot::zoomOut() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; oldRange = (d->yMax-d->yMin); newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInX() { Q_D(CartesianPlot); setAutoScaleX(false); double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)/m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->yMax-d->yMin); double newRange = (d->yMax-d->yMin)/m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->yMax-d->yMin); double newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::shiftLeftX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); double offsetX = (d->xMax-d->xMin)*0.1; d->xMax -= offsetX; d->xMin -= offsetX; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); double offsetX = (d->xMax-d->xMin)*0.1; d->xMax += offsetX; d->xMin += offsetX; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double offsetY = (d->yMax-d->yMin)*0.1; d->yMax += offsetY; d->yMin += offsetY; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double offsetY = (d->yMax-d->yMin)*0.1; d->yMax -= offsetY; d->yMin -= offsetY; d->retransformScales(); } void CartesianPlot::cursor() { Q_D(CartesianPlot); d->retransformScales(); } void CartesianPlot::mousePressZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mousePressZoomSelectionMode(logicPos); } void CartesianPlot::mousePressCursorMode(int cursorNumber, QPointF logicPos) { Q_D(CartesianPlot); d->mousePressCursorMode(cursorNumber, logicPos); } void CartesianPlot::mouseMoveZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mouseMoveZoomSelectionMode(logicPos); } void CartesianPlot::mouseMoveCursorMode(int cursorNumber, QPointF logicPos) { Q_D(CartesianPlot); d->mouseMoveCursorMode(cursorNumber, logicPos); } void CartesianPlot::mouseReleaseZoomSelectionMode() { Q_D(CartesianPlot); d->mouseReleaseZoomSelectionMode(); } void CartesianPlot::mouseHoverZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mouseHoverZoomSelectionMode(logicPos); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlot::visibilityChanged() { Q_D(CartesianPlot); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) : AbstractPlotPrivate(plot), q(plot) { setData(0, WorksheetElement::NameCartesianPlot); m_cursor0Text.prepare(); m_cursor1Text.prepare(); } /*! updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales. The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc. and which can pose the parent item for several sub-items (like TextLabel). Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area. Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area. */ void CartesianPlotPrivate::retransform() { if (suppressRetransform) return; PERFTRACE("CartesianPlotPrivate::retransform()"); prepareGeometryChange(); setPos( rect.x()+rect.width()/2, rect.y()+rect.height()/2); updateDataRect(); retransformScales(); //plotArea position is always (0, 0) in parent's coordinates, don't need to update here q->plotArea()->setRect(rect); //call retransform() for the title and the legend (if available) //when a predefined position relative to the (Left, Centered etc.) is used, //the actual position needs to be updated on plot's geometry changes. if (q->title()) q->title()->retransform(); if (q->m_legend) q->m_legend->retransform(); WorksheetElementContainerPrivate::recalcShapeAndBoundingRect(); } void CartesianPlotPrivate::retransformScales() { DEBUG("CartesianPlotPrivate::retransformScales()"); DEBUG(" xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax); PERFTRACE("CartesianPlotPrivate::retransformScales()"); auto* plot = dynamic_cast(q); QVector scales; //check ranges for log-scales if (xScale != CartesianPlot::ScaleLinear) checkXRange(); //check whether we have x-range breaks - the first break, if available, should be valid bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid()); static const int breakGap = 20; double sceneStart, sceneEnd, logicalStart, logicalEnd; //create x-scales int plotSceneStart = dataRect.x(); int plotSceneEnd = dataRect.x() + dataRect.width(); if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = xMin; logicalEnd = xMax; //TODO: how should we handle the case sceneStart == sceneEnd? //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots) if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = xMin; for (const auto& rb : xRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range) sceneStart = sceneEndLast+breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = xMax; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setXScales(scales); //check ranges for log-scales if (yScale != CartesianPlot::ScaleLinear) checkYRange(); //check whether we have y-range breaks - the first break, if available, should be valid hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid()); //create y-scales scales.clear(); plotSceneStart = dataRect.y() + dataRect.height(); plotSceneEnd = dataRect.y(); if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = yMin; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = yMin; for (const auto& rb : yRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range) sceneStart = sceneEndLast-breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setYScales(scales); //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax double deltaXMin = 0; double deltaXMax = 0; double deltaYMin = 0; double deltaYMax = 0; if (xMin != xMinPrev) { deltaXMin = xMin - xMinPrev; emit plot->xMinChanged(xMin); } if (xMax != xMaxPrev) { deltaXMax = xMax - xMaxPrev; emit plot->xMaxChanged(xMax); } if (yMin != yMinPrev) { deltaYMin = yMin - yMinPrev; emit plot->yMinChanged(yMin); } if (yMax != yMaxPrev) { deltaYMax = yMax - yMaxPrev; emit plot->yMaxChanged(yMax); } xMinPrev = xMin; xMaxPrev = xMax; yMinPrev = yMin; yMaxPrev = yMax; //adjust auto-scale axes for (auto* axis : q->children()) { if (!axis->autoScale()) continue; if (axis->orientation() == Axis::AxisHorizontal) { if (deltaXMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(xMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaXMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(xMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaYMin != 0) { // axis->setOffset(axis->offset() + deltaYMin, false); // } } else { if (deltaYMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(yMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaYMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(yMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaXMin != 0) { // axis->setOffset(axis->offset() + deltaXMin, false); // } } } // call retransform() on the parent to trigger the update of all axes and curvesю //no need to do this on load since all plots are retransformed again after the project is loaded. if (!q->isLoading()) q->retransform(); } /* * calculates the rectangular of the are showing the actual data (plot's rect minus padding), * in plot's coordinates. */ void CartesianPlotPrivate::updateDataRect() { dataRect = mapRectFromScene(rect); double paddingLeft = horizontalPadding; double paddingRight = rightPadding; double paddingTop = verticalPadding; double paddingBottom = bottomPadding; if (symmetricPadding) { paddingRight = horizontalPadding; paddingBottom = verticalPadding; } dataRect.setX(dataRect.x() + paddingLeft); dataRect.setY(dataRect.y() + paddingTop); double newHeight = dataRect.height() - paddingBottom; if (newHeight < 0) newHeight = 0; dataRect.setHeight(newHeight); double newWidth = dataRect.width() - paddingRight; if (newWidth < 0) newWidth = 0; dataRect.setWidth(newWidth); } void CartesianPlotPrivate::rangeChanged() { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; if (autoScaleX && autoScaleY) q->scaleAuto(); else if (autoScaleX) q->scaleAutoX(); else if (autoScaleY) q->scaleAutoY(); } void CartesianPlotPrivate::xRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisHorizontal) axis->retransformTickLabelStrings(); } } void CartesianPlotPrivate::yRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisVertical) axis->retransformTickLabelStrings(); } } /*! * don't allow any negative values for the x range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkXRange() { double min = 0.01; if (xMin <= 0.0) { (min < xMax*min) ? xMin = min : xMin = xMax*min; emit q->xMinChanged(xMin); } else if (xMax <= 0.0) { (-min > xMin*min) ? xMax = -min : xMax = xMin*min; emit q->xMaxChanged(xMax); } } /*! * don't allow any negative values for the y range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkYRange() { double min = 0.01; if (yMin <= 0.0) { (min < yMax*min) ? yMin = min : yMin = yMax*min; emit q->yMinChanged(yMin); } else if (yMax <= 0.0) { (-min > yMin*min) ? yMax = -min : yMax = yMin*min; emit q->yMaxChanged(yMax); } } CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) { DEBUG("CartesianPlotPrivate::createScale() scene start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd); // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (std::numeric_limits::lowest(), std::numeric_limits::max()); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); else return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, type); } /*! * Reimplemented from QGraphicsItem. */ QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); emit q->rectChanged(newRect); } return QGraphicsItem::itemChange(change, value); } //############################################################################## //################################## Events ################################## //############################################################################## void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { emit q->mousePressZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos())); if (mouseMode == CartesianPlot::ZoomSelectionMode) m_selectionStart = event->pos(); else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionStart.setX(event->pos().x()); m_selectionStart.setY(dataRect.y()); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionStart.setX(dataRect.x()); m_selectionStart.setY(event->pos().y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } else if (mouseMode == CartesianPlot::Cursor) { setCursor(Qt::SizeHorCursor); QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); double cursorPenWidth2 = cursorPen.width()/2; if (cursorPenWidth2 < 10) cursorPenWidth2 = 10; if (cursor0Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) { selectedCursor = 0; } else if (cursor1Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2) { selectedCursor = 1; } else if (QApplication::keyboardModifiers() & Qt::ControlModifier){ cursor1Enable = true; selectedCursor = 1; emit q->cursor1EnableChanged(cursor1Enable); } else { cursor0Enable = true; selectedCursor = 0; emit q->cursor0EnableChanged(cursor0Enable); } emit q->mousePressCursorModeSignal(selectedCursor, logicalPos); } else { if (!locked && dataRect.contains(event->pos())) { panningStarted = true; m_panningStart = event->pos(); setCursor(Qt::ClosedHandCursor); } QGraphicsItem::mousePressEvent(event); } } void CartesianPlotPrivate::mousePressZoomSelectionMode(QPointF logicalPos) { if (mouseMode == CartesianPlot::ZoomSelectionMode) { if (logicalPos.x() < xMin) logicalPos.setX(xMin); if (logicalPos.x() > xMax) logicalPos.setX(xMax); if (logicalPos.y() < yMin) logicalPos.setY(yMin); if (logicalPos.y() > yMax) logicalPos.setY(yMax); m_selectionStart = cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionStart.setX(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping).x()); m_selectionStart.setY(dataRect.y()); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionStart.setX(dataRect.x()); m_selectionStart.setY(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping).y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } void CartesianPlotPrivate::mousePressCursorMode(int cursorNumber, QPointF logicalPos) { cursorNumber == 0 ? cursor0Enable = true : cursor1Enable = true; QPointF p1(logicalPos.x(), yMin); QPointF p2(logicalPos.x(), yMax); if (cursorNumber == 0) { cursor0Pos.setX(logicalPos.x()); cursor0Pos.setY(0); } else { cursor1Pos.setX(logicalPos.x()); cursor1Pos.setY(0); } update(); } void CartesianPlotPrivate::updateCursor() { update(); } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { if (panningStarted && dataRect.contains(event->pos()) ) { //don't retransform on small mouse movement deltas const int deltaXScene = (m_panningStart.x() - event->pos().x()); const int deltaYScene = (m_panningStart.y() - event->pos().y()); if (abs(deltaXScene) < 5 && abs(deltaYScene) < 5) return; const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos()); const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart); const float deltaX = (logicalStart.x() - logicalEnd.x()); const float deltaY = (logicalStart.y() - logicalEnd.y()); xMax += deltaX; xMin += deltaX; yMax += deltaY; yMin += deltaY; + + q->setUndoAware(false); + q->setAutoScaleX(false); + q->setAutoScaleY(false); + q->setUndoAware(true); + retransformScales(); m_panningStart = event->pos(); } else QGraphicsItem::mouseMoveEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { q->info(QString()); return; } emit q->mouseMoveZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), CartesianCoordinateSystem::MappingFlag::Limit)); } else if (mouseMode == CartesianPlot::Cursor) { QGraphicsItem::mouseMoveEvent(event); if (!boundingRect().contains(event->pos())) { q->info(i18n("Not inside of the bounding rect")); return; } QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); // updating treeview data and cursor position // updatign cursor position is done in Worksheet, because // multiple plots must be updated emit q->mouseMoveCursorModeSignal(selectedCursor, logicalPos); } } void CartesianPlotPrivate::mouseMoveZoomSelectionMode(QPointF logicalPos) { QString info; QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionEnd = cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); QPointF logicalEnd = logicalPos; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); info += QLatin1String(", "); if (yRangeFormat == CartesianPlot::Numeric) info += QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info += i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionEnd.setX(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).x());//event->pos().x()); m_selectionEnd.setY(dataRect.bottom()); QPointF logicalEnd = logicalPos; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionEnd.setX(dataRect.right()); logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionEnd.setY(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).y());//event->pos().y()); QPointF logicalEnd = logicalPos; if (yRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info = i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } q->info(info); update(); } void CartesianPlotPrivate::mouseMoveCursorMode(int cursorNumber, QPointF logicalPos) { QPointF p1(logicalPos.x(), 0); cursorNumber == 0 ? cursor0Pos = p1 : cursor1Pos = p1; QString info; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("x=") + QString::number(logicalPos.x()); else info = i18n("x=%1", QDateTime::fromMSecsSinceEpoch(logicalPos.x()).toString(xRangeDateTimeFormat)); q->info(info); update(); } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { setCursor(Qt::ArrowCursor); if (mouseMode == CartesianPlot::SelectionMode) { panningStarted = false; //TODO: why do we do this all the time?!?! const QPointF& itemPos = pos();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and set it QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); suppressRetransform = true; q->setRect(newRect); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { emit q->mouseReleaseZoomSelectionModeSignal(); } } void CartesianPlotPrivate::mouseReleaseZoomSelectionMode() { //don't zoom if very small region was selected, avoid occasional/unwanted zooming if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) { m_selectionBandIsShown = false; return; } //determine the new plot ranges QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::SuppressPageClipping); QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::SuppressPageClipping); if (m_selectionEnd.x() > m_selectionStart.x()) { xMin = logicalZoomStart.x(); xMax = logicalZoomEnd.x(); } else { xMin = logicalZoomEnd.x(); xMax = logicalZoomStart.x(); } if (m_selectionEnd.y() > m_selectionStart.y()) { yMin = logicalZoomEnd.y(); yMax = logicalZoomStart.y(); } else { yMin = logicalZoomStart.y(); yMax = logicalZoomEnd.y(); } m_selectionBandIsShown = false; retransformScales(); } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { if (locked) return; //determine first, which axes are selected and zoom only in the corresponding direction. //zoom the entire plot if no axes selected. bool zoomX = false; bool zoomY = false; for (auto* axis : q->children()) { if (!axis->graphicsItem()->isSelected()) continue; if (axis->orientation() == Axis::AxisHorizontal) zoomX = true; else zoomY = true; } if (event->delta() > 0) { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomIn(); } else { if (zoomX) q->zoomInX(); if (zoomY) q->zoomInY(); } } else { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomOut(); } else { if (zoomX) q->zoomOutX(); if (zoomY) q->zoomOutY(); } } } void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { QPointF point = event->pos(); QString info; if (dataRect.contains(point)) { QPointF logicalPoint = cSystem->mapSceneToLogical(point); - if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { + + if ((mouseMode == CartesianPlot::ZoomSelectionMode) || + mouseMode == CartesianPlot::SelectionMode) { info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); info += ", y="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); + } + + if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); - } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { + } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { info = "y="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::MouseMode::SelectionMode) { // hover the nearest curve to the mousepointer // hovering curves is implemented in the parent, because no ignoreEvent() exists // for it. Checking all curves and hover the first bool curve_hovered = false; QVector curves = q->children(); for (int i=curves.count() - 1; i >= 0; i--){ // because the last curve is above the other curves if (curve_hovered){ // if a curve is already hovered, disable hover for the rest curves[i]->setHover(false); continue; } if (curves[i]->activateCurve(event->pos())){ curves[i]->setHover(true); curve_hovered = true; continue; } curves[i]->setHover(false); } } else if (mouseMode == CartesianPlot::Cursor){ info = "x="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); double cursorPenWidth2 = cursorPen.width()/2; if (cursorPenWidth2 < 10) cursorPenWidth2 = 10; if ((cursor0Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) || (cursor1Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2)) setCursor(Qt::SizeHorCursor); else setCursor(Qt::ArrowCursor); update(); } } q->info(info); QGraphicsItem::hoverMoveEvent(event); } +void CartesianPlotPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { + QVector curves = q->children(); + for (auto* curve : curves) + curve->setHover(false); + + m_hovered = false; + QGraphicsItem::hoverLeaveEvent(event); +} + void CartesianPlotPrivate::mouseHoverZoomSelectionMode(QPointF logicPos) { if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { QPointF p1(logicPos.x(), yMin); QPointF p2(logicPos.x(), yMax); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit)); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { QPointF p1(xMin, logicPos.y()); QPointF p2(xMax, logicPos.y()); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit)); } update(); // because if previous another selection mode was selected, the lines must be deleted } void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (!m_printing) { painter->save(); painter->setPen(cursorPen); QFont font = painter->font(); font.setPointSize(font.pointSize() * 4); painter->setFont(font); QPointF p1 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)); if (cursor0Enable && p1 != QPointF(0,0)){ QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMax)); painter->drawLine(p1,p2); QPointF textPos = p2; textPos.setX(p2.x() - m_cursor0Text.size().width()/2); textPos.setY(p2.y() - m_cursor0Text.size().height()); + if (textPos.y() < boundingRect().y()) + textPos.setY(boundingRect().y()); painter->drawStaticText(textPos, m_cursor0Text); } p1 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)); if (cursor1Enable && p1 != QPointF(0,0)){ QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMax)); painter->drawLine(p1,p2); QPointF textPos = p2; + // TODO: Moving this stuff into other function to not calculate it every time textPos.setX(p2.x() - m_cursor1Text.size().width()/2); textPos.setY(p2.y() - m_cursor1Text.size().height()); + if (textPos.y() < boundingRect().y()) + textPos.setY(boundingRect().y()); painter->drawStaticText(textPos, m_cursor1Text); } painter->restore(); } painter->setPen(QPen(Qt::black, 3)); if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) && (!m_selectionBandIsShown)) painter->drawLine(m_selectionStartLine); if (m_selectionBandIsShown) { QPointF selectionStart = m_selectionStart; if (m_selectionStart.x() > dataRect.right()) selectionStart.setX(dataRect.right()); if (m_selectionStart.x() < dataRect.left()) selectionStart.setX(dataRect.left()); if (m_selectionStart.y() > dataRect.bottom()) selectionStart.setY(dataRect.bottom()); if (m_selectionStart.y() < dataRect.top()) selectionStart.setY(dataRect.top()); QPointF selectionEnd = m_selectionEnd; if (m_selectionEnd.x() > dataRect.right()) selectionEnd.setX(dataRect.right()); if (m_selectionEnd.x() < dataRect.left()) selectionEnd.setX(dataRect.left()); if (m_selectionEnd.y() > dataRect.bottom()) selectionEnd.setY(dataRect.bottom()); if (m_selectionEnd.y() < dataRect.top()) selectionEnd.setY(dataRect.top()); painter->save(); painter->setPen(QPen(Qt::black, 5)); painter->drawRect(QRectF(selectionStart, selectionEnd)); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(QRectF(selectionStart, selectionEnd)); painter->restore(); } float penWidth = 6.; QRectF rect = q->m_plotArea->graphicsItem()->boundingRect(); rect = QRectF(-rect.width()/2 - penWidth / 2, -rect.height()/2 - penWidth / 2, rect.width() + penWidth, rect.height() + penWidth); if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), penWidth, Qt::SolidLine)); painter->drawRect(rect); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), penWidth, Qt::SolidLine)); painter->drawRect(rect); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlot::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlot); writer->writeStartElement( "cartesianPlot" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //cursor writer->writeStartElement( "cursor" ); WRITE_QPEN(d->cursorPen); writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->rect.x()) ); writer->writeAttribute( "y", QString::number(d->rect.y()) ); writer->writeAttribute( "width", QString::number(d->rect.width()) ); writer->writeAttribute( "height", QString::number(d->rect.height()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //coordinate system and padding writer->writeStartElement( "coordinateSystem" ); writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) ); writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) ); writer->writeAttribute( "xMin", QString::number(d->xMin, 'g', 16)); writer->writeAttribute( "xMax", QString::number(d->xMax, 'g', 16) ); writer->writeAttribute( "yMin", QString::number(d->yMin, 'g', 16) ); writer->writeAttribute( "yMax", QString::number(d->yMax, 'g', 16) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "xRangeFormat", QString::number(d->xRangeFormat) ); writer->writeAttribute( "yRangeFormat", QString::number(d->yRangeFormat) ); writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) ); writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) ); writer->writeAttribute( "rightPadding", QString::number(d->rightPadding) ); writer->writeAttribute( "bottomPadding", QString::number(d->bottomPadding) ); writer->writeAttribute( "symmetricPadding", QString::number(d->symmetricPadding)); writer->writeEndElement(); //x-scale breaks if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) { writer->writeStartElement("xRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) ); for (const auto& rb : d->xRangeBreaks.list) { writer->writeStartElement("xRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //y-scale breaks if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) { writer->writeStartElement("yRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) ); for (const auto& rb : d->yRangeBreaks.list) { writer->writeStartElement("yRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //serialize all children (plot area, title text label, axes and curves) for (auto* elem : children(IncludeHidden)) elem->save(writer); writer->writeEndElement(); // close "cartesianPlot" section } //! Load from XML bool CartesianPlot::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlot); if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool titleLabelRead = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlot") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "cursor") { attribs = reader->attributes(); QPen pen; pen.setWidth(attribs.value("width").toInt()); pen.setStyle(static_cast(attribs.value("style").toInt())); QColor color; color.setRed(attribs.value("color_r").toInt()); color.setGreen(attribs.value("color_g").toInt()); color.setBlue(attribs.value("color_b").toInt()); pen.setColor(color); d->cursorPen = pen; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->rect.setX( str.toDouble() ); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->rect.setY( str.toDouble() ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else d->rect.setWidth( str.toDouble() ); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else d->rect.setHeight( str.toDouble() ); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "coordinateSystem") { attribs = reader->attributes(); READ_INT_VALUE("autoScaleX", autoScaleX, bool); READ_INT_VALUE("autoScaleY", autoScaleY, bool); str = attribs.value("xMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMin").toString()); else { d->xMin = str.toDouble(); d->xMinPrev = d->xMin; } str = attribs.value("xMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMax").toString()); else { d->xMax = str.toDouble(); d->xMaxPrev = d->xMax; } str = attribs.value("yMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMin").toString()); else { d->yMin = str.toDouble(); d->yMinPrev = d->yMin; } str = attribs.value("yMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMax").toString()); else { d->yMax = str.toDouble(); d->yMaxPrev = d->yMax; } READ_INT_VALUE("xScale", xScale, CartesianPlot::Scale); READ_INT_VALUE("yScale", yScale, CartesianPlot::Scale); READ_INT_VALUE("xRangeFormat", xRangeFormat, CartesianPlot::RangeFormat); READ_INT_VALUE("yRangeFormat", yRangeFormat, CartesianPlot::RangeFormat); READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding); READ_DOUBLE_VALUE("verticalPadding", verticalPadding); READ_DOUBLE_VALUE("rightPadding", rightPadding); READ_DOUBLE_VALUE("bottomPadding", bottomPadding); READ_INT_VALUE("symmetricPadding", symmetricPadding, bool); } else if (!preview && reader->name() == "xRangeBreaks") { //delete default rang break d->xRangeBreaks.list.clear(); attribs = reader->attributes(); READ_INT_VALUE("enabled", xRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "xRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->xRangeBreaks.list << b; } else if (!preview && reader->name() == "yRangeBreaks") { //delete default rang break d->yRangeBreaks.list.clear(); attribs = reader->attributes(); READ_INT_VALUE("enabled", yRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "yRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->yRangeBreaks.list << b; } else if (reader->name() == "textLabel") { if (!titleLabelRead) { //the first text label is always the title label m_title->load(reader, preview); titleLabelRead = true; //TODO: the name is read in m_title->load() but we overwrite it here //since the old projects don't have this " - Title" appendix yet that we add in init(). //can be removed in couple of releases m_title->setName(name() + QLatin1String(" - ") + i18n("Title")); } else { TextLabel* label = new TextLabel("text label"); if (label->load(reader, preview)) { addChildFast(label); label->setParentGraphicsItem(graphicsItem()); } else { delete label; return false; } } } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { Axis* axis = new Axis(QString()); if (axis->load(reader, preview)) addChildFast(axis); else { delete axis; return false; } } else if (reader->name() == "xyCurve") { XYCurve* curve = new XYCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = new XYEquationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = new XYDataReductionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = new XYDifferentiationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = new XYIntegrationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = new XYInterpolationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = new XYSmoothCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = new XYFitCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = new XYFourierFilterCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = new XYFourierTransformCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyConvolutionCurve") { XYConvolutionCurve* curve = new XYConvolutionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyCorrelationCurve") { XYCorrelationCurve* curve = new XYCorrelationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, QString()); if (m_legend->load(reader, preview)) addChildFast(m_legend); else { delete m_legend; return false; } } else if (reader->name() == "customPoint") { CustomPoint* point = new CustomPoint(this, QString()); if (point->load(reader, preview)) addChildFast(point); else { delete point; return false; } } else if (reader->name() == "Histogram") { Histogram* curve = new Histogram("Histogram"); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else { // unknown element reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; d->retransform(); //if a theme was used, initialize the color palette if (!d->theme.isEmpty()) { //TODO: check whether the theme config really exists KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig ); this->setColorPalette(config); } else { //initialize the color palette with default colors this->setColorPalette(KConfig()); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void CartesianPlot::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); loadThemeConfig(config); } void CartesianPlot::loadThemeConfig(const KConfig& config) { QString str = config.name(); // theme path is saved with UNIX dir separator str = str.right(str.length() - str.lastIndexOf(QLatin1Char('/')) - 1); DEBUG(" set theme to " << str.toStdString()); this->setTheme(str); //load the color palettes for the curves this->setColorPalette(config); //load the theme for all the children for (auto* child : children(AbstractAspect::IncludeHidden)) child->loadThemeConfig(config); Q_D(CartesianPlot); d->update(this->rect()); } void CartesianPlot::saveTheme(KConfig &config) { const QVector& axisElements = children(AbstractAspect::IncludeHidden); const QVector& plotAreaElements = children(AbstractAspect::IncludeHidden); const QVector& textLabelElements = children(AbstractAspect::IncludeHidden); axisElements.at(0)->saveThemeConfig(config); plotAreaElements.at(0)->saveThemeConfig(config); textLabelElements.at(0)->saveThemeConfig(config); for (auto *child : children(AbstractAspect::IncludeHidden)) child->saveThemeConfig(config); } //Generating colors from 5-color theme palette void CartesianPlot::setColorPalette(const KConfig& config) { if (config.hasGroup(QLatin1String("Theme"))) { KConfigGroup group = config.group(QLatin1String("Theme")); //read the five colors defining the palette m_themeColorPalette.clear(); m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor())); } else { //no theme is available, provide 5 "default colors" m_themeColorPalette.clear(); m_themeColorPalette.append(QColor(25, 25, 25)); m_themeColorPalette.append(QColor(0, 0, 127)); m_themeColorPalette.append(QColor(127 ,0, 0)); m_themeColorPalette.append(QColor(0, 127, 0)); m_themeColorPalette.append(QColor(85, 0, 127)); } //generate 30 additional shades if the color palette contains more than one color if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) { QColor c; //3 factors to create shades from theme's palette float fac[3] = {0.25f,0.45f,0.65f}; //Generate 15 lighter shades for (int i = 0; i < 5; i++) { for (int j = 1; j < 4; j++) { c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) ); c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) ); c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) ); m_themeColorPalette.append(c); } } //Generate 15 darker shades for (int i = 0; i < 5; i++) { for (int j = 4; j < 7; j++) { c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) ); c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) ); c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) ); m_themeColorPalette.append(c); } } } } const QList& CartesianPlot::themeColorPalette() const { return m_themeColorPalette; } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.h b/src/backend/worksheet/plots/cartesian/CartesianPlot.h index a2dab6605..eeb518f16 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.h @@ -1,322 +1,324 @@ /*************************************************************************** File : CartesianPlot.h Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2018 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2018 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef CARTESIANPLOT_H #define CARTESIANPLOT_H #include "backend/worksheet/plots/AbstractPlot.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include class QDropEvent; class QToolBar; class CartesianPlotPrivate; class CartesianPlotLegend; class AbstractColumn; class XYCurve; class XYEquationCurve; class XYDataReductionCurve; class XYDifferentiationCurve; class XYIntegrationCurve; class XYInterpolationCurve; class XYSmoothCurve; class XYFitCurve; class XYFourierFilterCurve; class XYFourierTransformCurve; class XYConvolutionCurve; class XYCorrelationCurve; class KConfig; class CartesianPlot : public AbstractPlot { Q_OBJECT public: explicit CartesianPlot(const QString &name); ~CartesianPlot() override; enum Scale {ScaleLinear, ScaleLog10, ScaleLog2, ScaleLn, ScaleLog10Abs, ScaleLog2Abs, ScaleLnAbs, ScaleSqrt, ScaleX2}; enum Type {FourAxes, TwoAxes, TwoAxesCentered, TwoAxesCenteredZero}; enum RangeFormat {Numeric, DateTime}; enum RangeType {RangeFree, RangeLast, RangeFirst}; enum RangeBreakStyle {RangeBreakSimple, RangeBreakVertical, RangeBreakSloped}; enum MouseMode {SelectionMode, ZoomSelectionMode, ZoomXSelectionMode, ZoomYSelectionMode, Cursor}; enum NavigationOperation {ScaleAuto, ScaleAutoX, ScaleAutoY, ZoomIn, ZoomOut, ZoomInX, ZoomOutX, ZoomInY, ZoomOutY, ShiftLeftX, ShiftRightX, ShiftUpY, ShiftDownY }; struct RangeBreak { RangeBreak() : start(NAN), end(NAN), position(0.5), style(RangeBreakSloped) {} bool isValid() const { return (!std::isnan(start) && !std::isnan(end)); } double start; double end; double position; RangeBreakStyle style; }; //simple wrapper for QList in order to get our macros working struct RangeBreaks { RangeBreaks() : lastChanged(-1) { RangeBreak b; list << b; }; QList list; int lastChanged; }; void initDefault(Type = FourAxes); QIcon icon() const override; QMenu* createContextMenu() override; QMenu* analysisMenu(); QVector dependsOn() const override; void setRect(const QRectF&) override; QRectF dataRect() const; void setMouseMode(const MouseMode); void setLocked(const bool); MouseMode mouseMode() const; void navigate(NavigationOperation); void setSuppressDataChangedSignal(bool); const QList& themeColorPalette() const; void processDropEvent(QDropEvent*) override; bool isPanningActive() const; void addLegend(CartesianPlotLegend*); int curveCount(); const XYCurve* getCurve(int index); double cursorPos(int cursorNumber); void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; void loadThemeConfig(const KConfig&) override; void saveTheme(KConfig& config); void mousePressZoomSelectionMode(QPointF logicPos); void mousePressCursorMode(int cursorNumber, QPointF logicPos); void mouseMoveZoomSelectionMode(QPointF logicPos); void mouseMoveCursorMode(int cursorNumber, QPointF logicPos); void mouseReleaseZoomSelectionMode(); - void mouseHoverZoomSelectionMode(QPointF logicPos); + void mouseHoverZoomSelectionMode(QPointF logicPos); BASIC_D_ACCESSOR_DECL(CartesianPlot::RangeFormat, xRangeFormat, XRangeFormat) BASIC_D_ACCESSOR_DECL(CartesianPlot::RangeFormat, yRangeFormat, YRangeFormat) const QString& xRangeDateTimeFormat() const; const QString& yRangeDateTimeFormat() const; BASIC_D_ACCESSOR_DECL(CartesianPlot::RangeType, rangeType, RangeType) BASIC_D_ACCESSOR_DECL(int, rangeLastValues, RangeLastValues) BASIC_D_ACCESSOR_DECL(int, rangeFirstValues, RangeFirstValues) BASIC_D_ACCESSOR_DECL(bool, autoScaleX, AutoScaleX) BASIC_D_ACCESSOR_DECL(bool, autoScaleY, AutoScaleY) BASIC_D_ACCESSOR_DECL(double, xMin, XMin) BASIC_D_ACCESSOR_DECL(double, xMax, XMax) BASIC_D_ACCESSOR_DECL(double, yMin, YMin) BASIC_D_ACCESSOR_DECL(double, yMax, YMax) BASIC_D_ACCESSOR_DECL(CartesianPlot::Scale, xScale, XScale) BASIC_D_ACCESSOR_DECL(CartesianPlot::Scale, yScale, YScale) BASIC_D_ACCESSOR_DECL(bool, xRangeBreakingEnabled, XRangeBreakingEnabled) BASIC_D_ACCESSOR_DECL(bool, yRangeBreakingEnabled, YRangeBreakingEnabled) CLASS_D_ACCESSOR_DECL(RangeBreaks, xRangeBreaks, XRangeBreaks) CLASS_D_ACCESSOR_DECL(RangeBreaks, yRangeBreaks, YRangeBreaks) CLASS_D_ACCESSOR_DECL(QPen, cursorPen, CursorPen); CLASS_D_ACCESSOR_DECL(bool, cursor0Enable, Cursor0Enable); CLASS_D_ACCESSOR_DECL(bool, cursor1Enable, Cursor1Enable); QString theme() const; typedef CartesianPlotPrivate Private; public slots: void setTheme(const QString&); private: void init(); void initActions(); void initMenus(); void setColorPalette(const KConfig&); const XYCurve* currentCurve() const; + void calculateCurvesXMinMax(); + void calculateCurvesYMinMax(); + CartesianPlotLegend* m_legend{nullptr}; double m_zoomFactor{1.2}; QList m_themeColorPalette; bool m_menusInitialized{false}; QAction* visibilityAction; //"add new" actions QAction* addCurveAction; QAction* addEquationCurveAction; QAction* addHistogramAction; QAction* addDataReductionCurveAction; QAction* addDifferentiationCurveAction; QAction* addIntegrationCurveAction; QAction* addInterpolationCurveAction; QAction* addSmoothCurveAction; QAction* addFitCurveAction; QAction* addFourierFilterCurveAction; QAction* addFourierTransformCurveAction; QAction* addConvolutionCurveAction; QAction* addCorrelationCurveAction; QAction* addHorizontalAxisAction; QAction* addVerticalAxisAction; QAction* addLegendAction; QAction* addTextLabelAction; QAction* addCustomPointAction; //scaling, zooming, navigation actions QAction* scaleAutoXAction; QAction* scaleAutoYAction; QAction* scaleAutoAction; QAction* zoomInAction; QAction* zoomOutAction; QAction* zoomInXAction; QAction* zoomOutXAction; QAction* zoomInYAction; QAction* zoomOutYAction; QAction* shiftLeftXAction; QAction* shiftRightXAction; QAction* shiftUpYAction; QAction* shiftDownYAction; QAction* cursorAction; //analysis menu actions QAction* addDataOperationAction; QAction* addDataReductionAction; QAction* addDifferentiationAction; QAction* addIntegrationAction; QAction* addInterpolationAction; QAction* addSmoothAction; QVector addFitAction; QAction* addFourierFilterAction; QAction* addConvolutionAction; QAction* addCorrelationAction; QMenu* addNewMenu{nullptr}; QMenu* zoomMenu{nullptr}; QMenu* dataAnalysisMenu{nullptr}; QMenu* themeMenu{nullptr}; Q_DECLARE_PRIVATE(CartesianPlot) public slots: void addHorizontalAxis(); void addVerticalAxis(); void addCurve(); void addHistogram(); void addEquationCurve(); void addDataReductionCurve(); void addDifferentiationCurve(); void addIntegrationCurve(); void addInterpolationCurve(); void addSmoothCurve(); void addFitCurve(); void addFourierFilterCurve(); void addFourierTransformCurve(); void addConvolutionCurve(); void addCorrelationCurve(); void addLegend(); void addTextLabel(); void addCustomPoint(); bool scaleAuto(); bool scaleAutoX(); bool scaleAutoY(); void zoomIn(); void zoomOut(); void zoomInX(); void zoomOutX(); void zoomInY(); void zoomOutY(); void shiftLeftX(); void shiftRightX(); void shiftUpY(); void shiftDownY(); void cursor(); void dataChanged(); void curveLinePenChanged(QPen); private slots: void updateLegend(); void childAdded(const AbstractAspect*); void childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child); void childHovered(); void xDataChanged(); void yDataChanged(); void curveVisibilityChanged(); - //SLOTs for changes triggered via QActions in the context menu void visibilityChanged(); void loadTheme(const QString&); protected: CartesianPlot(const QString &name, CartesianPlotPrivate *dd); signals: void rangeTypeChanged(CartesianPlot::RangeType); void xRangeFormatChanged(CartesianPlot::RangeFormat); void yRangeFormatChanged(CartesianPlot::RangeFormat); void rangeLastValuesChanged(int); void rangeFirstValuesChanged(int); void rectChanged(QRectF&); void xAutoScaleChanged(bool); void xMinChanged(double); void xMaxChanged(double); void xScaleChanged(int); void yAutoScaleChanged(bool); void yMinChanged(double); void yMaxChanged(double); void yScaleChanged(int); void xRangeBreakingEnabledChanged(bool); void xRangeBreaksChanged(const CartesianPlot::RangeBreaks&); void yRangeBreakingEnabledChanged(bool); void yRangeBreaksChanged(const CartesianPlot::RangeBreaks&); void themeChanged(const QString&); void mousePressZoomSelectionModeSignal(QPointF logicPos); void mousePressCursorModeSignal(int cursorNumber, QPointF logicPos); void mouseMoveZoomSelectionModeSignal(QPointF logicPos); void mouseMoveCursorModeSignal(int cursorNumber, QPointF logicPos); void mouseReleaseCursorModeSignal(); void mouseReleaseZoomSelectionModeSignal(); - void mouseHoverZoomSelectionModeSignal(QPointF logicalPoint); + void mouseHoverZoomSelectionModeSignal(QPointF logicalPoint); void cursorPosChanged(int cursorNumber, double xPos); void curveAdded(const XYCurve*); void curveRemoved(const XYCurve*); void curveLinePenChanged(QPen, QString curveName); void cursorPenChanged(QPen); void curveDataChanged(const XYCurve*); void curveVisibilityChangedSignal(); - void mouseModeChanged(); + void mouseModeChanged(MouseMode); void cursor0EnableChanged(bool enable); void cursor1EnableChanged(bool enable); }; #endif diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h index 5e97fe029..99db0218e 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h @@ -1,124 +1,125 @@ /*************************************************************************** File : CartesianPlotPrivate.h Project : LabPlot Description : Private members of CartesianPlot. -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) *******************************************************7*******************/ /*************************************************************************** * * * 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 CARTESIANPLOTPRIVATE_H #define CARTESIANPLOTPRIVATE_H #include "CartesianPlot.h" #include "CartesianCoordinateSystem.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include #include class CartesianPlotPrivate : public AbstractPlotPrivate { public: explicit CartesianPlotPrivate(CartesianPlot*); void retransform() override; void retransformScales(); void rangeChanged(); void xRangeFormatChanged(); void yRangeFormatChanged(); void mouseMoveZoomSelectionMode(QPointF logicalPos); void mouseMoveCursorMode(int cursorNumber, QPointF logicalPos); void mouseReleaseZoomSelectionMode(); void mouseHoverZoomSelectionMode(QPointF logicPos); void mousePressZoomSelectionMode(QPointF logicalPos); void mousePressCursorMode(int cursorNumber, QPointF logicalPos); void updateCursor(); QRectF dataRect; CartesianPlot::RangeType rangeType{CartesianPlot::RangeFree}; CartesianPlot::RangeFormat xRangeFormat{CartesianPlot::Numeric}; CartesianPlot::RangeFormat yRangeFormat{CartesianPlot::Numeric}; QString xRangeDateTimeFormat; QString yRangeDateTimeFormat; int rangeFirstValues{1000}; int rangeLastValues{1000}; double xMin{0.0}, xMax{1.0}, yMin{0.0}, yMax{1.0}; float xMinPrev{0.0}, xMaxPrev{1.0}, yMinPrev{0.0}, yMaxPrev{1.0}; bool autoScaleX{true}, autoScaleY{true}; float autoScaleOffsetFactor{0.0f}; CartesianPlot::Scale xScale{CartesianPlot::ScaleLinear}, yScale{CartesianPlot::ScaleLinear}; bool xRangeBreakingEnabled{false}; bool yRangeBreakingEnabled{false}; CartesianPlot::RangeBreaks xRangeBreaks; CartesianPlot::RangeBreaks yRangeBreaks; QString theme; //cached values of minimum and maximum for all visible curves bool curvesXMinMaxIsDirty{false}, curvesYMinMaxIsDirty{false}; double curvesXMin{INFINITY}, curvesXMax{-INFINITY}, curvesYMin{INFINITY}, curvesYMax{-INFINITY}; CartesianPlot* const q; CartesianPlot::MouseMode mouseMode{CartesianPlot::SelectionMode}; CartesianCoordinateSystem* cSystem{nullptr}; bool suppressRetransform{false}; bool panningStarted{false}; bool locked{false}; // Cursor bool cursor0Enable{false}; int selectedCursor{0}; QPointF cursor0Pos{QPointF(NAN, NAN)}; bool cursor1Enable{false}; QPointF cursor1Pos{QPointF(NAN, NAN)}; QPen cursorPen{QPen(Qt::red, 5, Qt::SolidLine)}; signals: void mousePressZoomSelectionModeSignal(QPointF logicalPos); void mousePressCursorModeSignal(QPointF logicalPos); private: QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; void mousePressEvent(QGraphicsSceneMouseEvent*) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; void wheelEvent(QGraphicsSceneWheelEvent*) override; void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr) override; void updateDataRect(); void checkXRange(); void checkYRange(); CartesianScale* createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd); bool m_selectionBandIsShown{false}; QPointF m_selectionStart; QPointF m_selectionEnd; QLineF m_selectionStartLine; QPointF m_panningStart; QStaticText m_cursor0Text{"1"}; QStaticText m_cursor1Text{"2"}; }; #endif diff --git a/src/commonfrontend/ProjectExplorer.cpp b/src/commonfrontend/ProjectExplorer.cpp index 440a49233..16b849c46 100644 --- a/src/commonfrontend/ProjectExplorer.cpp +++ b/src/commonfrontend/ProjectExplorer.cpp @@ -1,894 +1,888 @@ /*************************************************************************** File : ProjectExplorer.cpp Project : LabPlot Description : A tree view for displaying and editing an AspectTreeModel. -------------------------------------------------------------------- Copyright : (C) 2007-2008 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2010-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ProjectExplorer.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/AbstractPart.h" #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/core/PartMdiView.h" #include #include #include #include #include #include #include #include -#include #include #include #include #include /*! \class ProjectExplorer \brief A tree view for displaying and editing an AspectTreeModel. In addition to the functionality of QTreeView, ProjectExplorer allows the usage of the context menus provided by AspectTreeModel and propagates the item selection in the view to the model. Furthermore, features for searching and filtering in the model are provided. \ingroup commonfrontend */ ProjectExplorer::ProjectExplorer(QWidget* parent) : m_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) { - showColumnsSignalMapper = new QSignalMapper(this); 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, - showColumnsSignalMapper, static_cast(&QSignalMapper::map)); - - showColumnsSignalMapper->setMapping(showColumnAction, i); + this, [=] { ProjectExplorer::toggleColumn(i); }); } - connect(showColumnsSignalMapper, static_cast(&QSignalMapper::mapped), - this, &ProjectExplorer::toggleColumn); } 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::resizeHeader); m_project = project; //for newly created projects, resize the header to fit the size of the header section names. //for projects loaded from a file, this function will be called laterto fit the sizes //of the content once the project is loaded resizeHeader(); } QModelIndex ProjectExplorer::currentIndex() const { return m_treeView->currentIndex(); } /*! handles the contextmenu-event of the horizontal header in the tree view. Provides a menu for selective showing and hiding of columns. */ bool ProjectExplorer::eventFilter(QObject* obj, QEvent* event) { if (obj == m_treeView->header() && event->type() == QEvent::ContextMenu) { //Menu for showing/hiding the columns in the tree view QMenu* columnsMenu = new QMenu(m_treeView->header()); columnsMenu->addSection(i18n("Columns")); columnsMenu->addAction(showAllColumnsAction); columnsMenu->addSeparator(); for (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 #################################### //############################################################################## /*! expand the aspect \c aspect (the tree index corresponding to it) in the tree view and makes it visible and selected. Called when a new aspect is added to the project. */ void ProjectExplorer::aspectAdded(const AbstractAspect* aspect) { if (m_project->isLoading()) return; //don't do anything if hidden aspects were added if (aspect->hidden()) return; //don't do anything for newly added data spreadsheets of data picker curves if (aspect->inherits(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) { 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") { attribs = reader->attributes(); 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()) 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); 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/commonfrontend/core/PartMdiView.cpp b/src/commonfrontend/core/PartMdiView.cpp index bcdc27ad1..0c74c1fa5 100644 --- a/src/commonfrontend/core/PartMdiView.cpp +++ b/src/commonfrontend/core/PartMdiView.cpp @@ -1,88 +1,96 @@ /*************************************************************************** File : PartMdiView.cpp Project : LabPlot Description : QMdiSubWindow wrapper for aspect views. -------------------------------------------------------------------- Copyright : (C) 2013-2019 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) ***************************************************************************/ /*************************************************************************** * * * 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 "commonfrontend/core/PartMdiView.h" #include "backend/core/AbstractPart.h" #include "backend/worksheet/Worksheet.h" #include #include /*! * \class PartMdiView * * \brief QMdiSubWindow wrapper for aspect views. * * In addition to the functionality provided by QMdiSubWindow, * this class automatically updates the window title when AbstractAspect::caption() is changed * and holds the connection to the actual data visualized in this window via the pointer to \c AbstractPart. */ PartMdiView::PartMdiView(AbstractPart* part) : QMdiSubWindow(nullptr), m_part(part) { setWindowIcon(m_part->icon()); setWidget(m_part->view()); setAttribute(Qt::WA_DeleteOnClose); handleAspectDescriptionChanged(m_part); //resize the MDI sub window to fit the content of the view resize(m_part->view()->size()); connect(m_part, &AbstractPart::aspectDescriptionChanged, this, &PartMdiView::handleAspectDescriptionChanged); connect(m_part, &AbstractPart::aspectAboutToBeRemoved, this, &PartMdiView::handleAspectAboutToBeRemoved); connect(this, &QMdiSubWindow::windowStateChanged, this, &PartMdiView::windowStateChanged); } +PartMdiView::~PartMdiView() { + m_closing = true; +} + AbstractPart* PartMdiView::part() const { return m_part; } void PartMdiView::handleAspectDescriptionChanged(const AbstractAspect* aspect) { if (aspect != m_part) return; setWindowTitle(m_part->name()); } void PartMdiView::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { if (aspect != m_part) return; close(); } void PartMdiView::closeEvent(QCloseEvent *event) { m_part->deleteView(); event->accept(); } void PartMdiView::windowStateChanged(Qt::WindowStates oldState, Qt::WindowStates newState) { Q_UNUSED(oldState); + + if (m_closing) + return; + if (newState.testFlag(Qt::WindowActive) || newState.testFlag(Qt::WindowMaximized)) m_part->registerShortcuts(); - else if (!newState.testFlag(Qt::WindowNoState)) + else m_part->unregisterShortcuts(); } diff --git a/src/commonfrontend/core/PartMdiView.h b/src/commonfrontend/core/PartMdiView.h index 6af653973..394491d33 100644 --- a/src/commonfrontend/core/PartMdiView.h +++ b/src/commonfrontend/core/PartMdiView.h @@ -1,55 +1,57 @@ /*************************************************************************** File : PartMdiView.h Project : LabPlot Description : QMdiSubWindow wrapper for aspect views. -------------------------------------------------------------------- Copyright : (C) 2013-2019 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007,2008 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 PART_MDI_VIEW_H #define PART_MDI_VIEW_H #include class AbstractAspect; class AbstractPart; class PartMdiView : public QMdiSubWindow { Q_OBJECT public: explicit PartMdiView(AbstractPart* part); + ~PartMdiView() override; AbstractPart* part() const; private: void closeEvent(QCloseEvent*) override; AbstractPart* m_part; + bool m_closing{false}; private slots: void handleAspectDescriptionChanged(const AbstractAspect*); void handleAspectAboutToBeRemoved(const AbstractAspect*); void windowStateChanged(Qt::WindowStates oldState, Qt::WindowStates newState); }; #endif // ifndef PART_MDI_VIEW_H diff --git a/src/commonfrontend/datapicker/DatapickerImageView.cpp b/src/commonfrontend/datapicker/DatapickerImageView.cpp index 5d743a5a4..aa0fda324 100644 --- a/src/commonfrontend/datapicker/DatapickerImageView.cpp +++ b/src/commonfrontend/datapicker/DatapickerImageView.cpp @@ -1,801 +1,815 @@ /*************************************************************************** File : DatapickerImageView.cpp Project : LabPlot Description : DatapickerImage view for datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "commonfrontend/datapicker/DatapickerImageView.h" #include "backend/worksheet/Worksheet.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/datapicker/Datapicker.h" #include "backend/datapicker/Transform.h" #include "backend/datapicker/DatapickerCurve.h" #include "backend/datapicker/DatapickerImage.h" #include #include #include #include #include #include #include #include #include -#include +#include #include /** * \class DatapickerImageView * \brief Datapicker/DatapickerImage view */ /*! Constructur of the class. Creates a view for the DatapickerImage \c image and initializes the internal model. */ DatapickerImageView::DatapickerImageView(DatapickerImage* image) : QGraphicsView(), m_image(image), m_datapicker(dynamic_cast(m_image->parentAspect())), m_transform(new Transform()) { setScene(m_image->scene()); setRenderHint(QPainter::Antialiasing); setRubberBandSelectionMode(Qt::ContainsItemBoundingRect); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorViewCenter); setMinimumSize(16, 16); setFocusPolicy(Qt::StrongFocus); viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); viewport()->setAttribute( Qt::WA_NoSystemBackground ); setCacheMode(QGraphicsView::CacheBackground); initActions(); initMenus(); selectAndEditModeAction->setChecked(true); m_image->setSegmentsHoverEvent(true); setInteractive(true); changeZoom(zoomOriginAction); currentZoomAction = zoomInViewAction; if (m_image->plotPointsType() == DatapickerImage::AxisPoints) setAxisPointsAction->setChecked(true); else if (m_image->plotPointsType() == DatapickerImage::CurvePoints) setCurvePointsAction->setChecked(true); else selectSegmentAction->setChecked(true); handleImageActions(); changeRotationAngle(); //signal/slot connections //for general actions connect( m_image, SIGNAL(requestProjectContextMenu(QMenu*)), this, SLOT(createContextMenu(QMenu*)) ); connect( m_image, SIGNAL(requestUpdate()), this, SLOT(updateBackground()) ); connect( m_image, SIGNAL(requestUpdateActions()), this, SLOT(handleImageActions()) ); connect( m_datapicker, SIGNAL(requestUpdateActions()), this, SLOT(handleImageActions()) ); connect( m_image, SIGNAL(rotationAngleChanged(float)), this, SLOT(changeRotationAngle()) ); } DatapickerImageView::~DatapickerImageView() { delete m_transform; } void DatapickerImageView::initActions() { auto* zoomActionGroup = new QActionGroup(this); auto* mouseModeActionGroup = new QActionGroup(this); auto* plotPointsTypeActionGroup = new QActionGroup(this); navigationActionGroup = new QActionGroup(this); magnificationActionGroup = new QActionGroup(this); //Zoom actions zoomInViewAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), zoomActionGroup); zoomInViewAction->setShortcut(Qt::CTRL+Qt::Key_Plus); zoomOutViewAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), zoomActionGroup); zoomOutViewAction->setShortcut(Qt::CTRL+Qt::Key_Minus); zoomOriginAction = new QAction(QIcon::fromTheme("zoom-original"), i18n("Original Size"), zoomActionGroup); zoomOriginAction->setShortcut(Qt::CTRL+Qt::Key_1); zoomFitPageHeightAction = new QAction(QIcon::fromTheme("zoom-fit-height"), i18n("Fit to Height"), zoomActionGroup); zoomFitPageWidthAction = new QAction(QIcon::fromTheme("zoom-fit-width"), i18n("Fit to Width"), zoomActionGroup); // Mouse mode actions selectAndEditModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Edit"), mouseModeActionGroup); selectAndEditModeAction->setCheckable(true); navigationModeAction = new QAction(QIcon::fromTheme("input-mouse"), i18n("Navigate"), mouseModeActionGroup); navigationModeAction->setCheckable(true); zoomSelectionModeAction = new QAction(QIcon::fromTheme("page-zoom"), i18n("Select and Zoom"), mouseModeActionGroup); zoomSelectionModeAction->setCheckable(true); selectAndMoveModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Move"), mouseModeActionGroup); selectAndMoveModeAction->setCheckable(true); setAxisPointsAction = new QAction(QIcon::fromTheme("labplot-plot-axis-points"), i18n("Set Axis Points"), plotPointsTypeActionGroup); setAxisPointsAction->setCheckable(true); setCurvePointsAction = new QAction(QIcon::fromTheme("labplot-xy-curve-points"), i18n("Set Curve Points"), plotPointsTypeActionGroup); setCurvePointsAction->setCheckable(true); selectSegmentAction = new QAction(QIcon::fromTheme("labplot-xy-curve-segments"), i18n("Select Curve Segments"), plotPointsTypeActionGroup); selectSegmentAction->setCheckable(true); addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("New Curve"), this); shiftLeftAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left"), navigationActionGroup); shiftLeftAction->setShortcut(Qt::Key_Right); shiftRightAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right"), navigationActionGroup); shiftRightAction->setShortcut(Qt::Key_Left); shiftUpAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Up"), navigationActionGroup); shiftUpAction->setShortcut(Qt::Key_Up); shiftDownAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Down"), navigationActionGroup); shiftDownAction->setShortcut(Qt::Key_Down); noMagnificationAction = new QAction(QIcon::fromTheme("labplot-1x-zoom"), i18n("No Magnification"), magnificationActionGroup); noMagnificationAction->setCheckable(true); noMagnificationAction->setChecked(true); twoTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-2x-zoom"), i18n("2x Magnification"), magnificationActionGroup); twoTimesMagnificationAction->setCheckable(true); threeTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-3x-zoom"), i18n("3x Magnification"), magnificationActionGroup); threeTimesMagnificationAction->setCheckable(true); fourTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-4x-zoom"), i18n("4x Magnification"), magnificationActionGroup); fourTimesMagnificationAction->setCheckable(true); fiveTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-5x-zoom"), i18n("5x Magnification"), magnificationActionGroup); fiveTimesMagnificationAction->setCheckable(true); connect( mouseModeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(mouseModeChanged(QAction*)) ); connect( zoomActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeZoom(QAction*)) ); connect( plotPointsTypeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changePointsType(QAction*)) ); connect( addCurveAction, SIGNAL(triggered()), this, SLOT(addCurve()) ); connect( navigationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeSelectedItemsPosition(QAction*)) ); connect( magnificationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(magnificationChanged(QAction*)) ); } void DatapickerImageView::initMenus() { m_viewMouseModeMenu = new QMenu(i18n("Mouse Mode"), this); m_viewMouseModeMenu->setIcon(QIcon::fromTheme("input-mouse")); m_viewMouseModeMenu->addAction(selectAndEditModeAction); m_viewMouseModeMenu->addAction(navigationModeAction); m_viewMouseModeMenu->addAction(zoomSelectionModeAction); m_viewMouseModeMenu->addAction(selectAndMoveModeAction); m_viewImageMenu = new QMenu(i18n("Data Entry Mode"), this); m_viewImageMenu->addAction(setAxisPointsAction); m_viewImageMenu->addAction(setCurvePointsAction); m_viewImageMenu->addAction(selectSegmentAction); m_zoomMenu = new QMenu(i18n("Zoom View"), this); m_zoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); m_zoomMenu->addAction(zoomInViewAction); m_zoomMenu->addAction(zoomOutViewAction); m_zoomMenu->addAction(zoomOriginAction); m_zoomMenu->addAction(zoomFitPageHeightAction); m_zoomMenu->addAction(zoomFitPageWidthAction); m_navigationMenu = new QMenu(i18n("Move Last Point"), this); m_navigationMenu->addAction(shiftLeftAction); m_navigationMenu->addAction(shiftRightAction); m_navigationMenu->addAction(shiftUpAction); m_navigationMenu->addAction(shiftDownAction); m_magnificationMenu = new QMenu(i18n("Magnification"), this); m_magnificationMenu->setIcon(QIcon::fromTheme("labplot-zoom")); m_magnificationMenu->addAction(noMagnificationAction); m_magnificationMenu->addAction(twoTimesMagnificationAction); m_magnificationMenu->addAction(threeTimesMagnificationAction); m_magnificationMenu->addAction(fourTimesMagnificationAction); m_magnificationMenu->addAction(fiveTimesMagnificationAction); } /*! * Populates the menu \c menu with the image and image-view relevant actions. * The menu is used * - as the context menu in DatapickerImageView * - as the "datapicker menu" in the main menu-bar (called form MainWin) * - as a part of the image context menu in project explorer */ void DatapickerImageView::createContextMenu(QMenu* menu) const { Q_ASSERT(menu); QAction* firstAction = nullptr; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size()>1) firstAction = menu->actions().at(1); menu->insertMenu(firstAction, m_viewImageMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, addCurveAction); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_navigationMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_viewMouseModeMenu); menu->insertMenu(firstAction, m_zoomMenu); menu->insertMenu(firstAction, m_magnificationMenu); menu->insertSeparator(firstAction); } void DatapickerImageView::fillToolBar(QToolBar* toolBar) { toolBar->addSeparator(); toolBar->addAction(setAxisPointsAction); toolBar->addAction(setCurvePointsAction); toolBar->addAction(selectSegmentAction); toolBar->addSeparator(); toolBar->addAction(addCurveAction); toolBar->addSeparator(); toolBar->addAction(noMagnificationAction); toolBar->addAction(twoTimesMagnificationAction); toolBar->addAction(threeTimesMagnificationAction); toolBar->addAction(fourTimesMagnificationAction); toolBar->addAction(fiveTimesMagnificationAction); toolBar->addSeparator(); toolBar->addAction(shiftRightAction); toolBar->addAction(shiftLeftAction); toolBar->addAction(shiftUpAction); toolBar->addAction(shiftDownAction); toolBar->addSeparator(); toolBar->addAction(selectAndEditModeAction); toolBar->addAction(navigationModeAction); toolBar->addAction(zoomSelectionModeAction); toolBar->addAction(selectAndMoveModeAction); tbZoom = new QToolButton(toolBar); tbZoom->setPopupMode(QToolButton::MenuButtonPopup); tbZoom->setMenu(m_zoomMenu); tbZoom->setDefaultAction(currentZoomAction); toolBar->addWidget(tbZoom); } void DatapickerImageView::setScene(QGraphicsScene* scene) { QGraphicsView::setScene(scene); setTransform(QTransform()); } void DatapickerImageView::drawForeground(QPainter* painter, const QRectF& rect) { if (m_mouseMode == ZoomSelectionMode && m_selectionBandIsShown) { painter->save(); const QRectF& selRect = mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect(); painter->setPen(QPen(Qt::black, 5/transform().m11())); painter->drawRect(selRect); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(selRect); painter->restore(); } QGraphicsView::drawForeground(painter, rect); } void DatapickerImageView::drawBackground(QPainter* painter, const QRectF& rect) { painter->save(); QRectF scene_rect = sceneRect(); if (!scene_rect.contains(rect)) painter->fillRect(rect, Qt::lightGray); - //shadow - int shadowSize = scene_rect.width()*0.02; - QRectF rightShadowRect(scene_rect.right(), scene_rect.top() + shadowSize, - shadowSize, scene_rect.height()); - QRectF bottomShadowRect(scene_rect.left() + shadowSize, scene_rect.bottom(), - scene_rect.width(), shadowSize); - - painter->fillRect(rightShadowRect.intersected(rect), Qt::darkGray); - painter->fillRect(bottomShadowRect.intersected(rect), Qt::darkGray); - - // canvas if (m_image->isLoaded) { if (m_image->plotImageType() == DatapickerImage::OriginalImage) { QImage todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(scene_rect.topLeft(), todraw); } else if (m_image->plotImageType() == DatapickerImage::ProcessedImage) { QImage todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(scene_rect.topLeft(), todraw); } else { painter->fillRect(scene_rect, Qt::white); } } else { painter->setBrush(QBrush(Qt::gray)); painter->drawRect(scene_rect); } invalidateScene(rect, QGraphicsScene::BackgroundLayer); painter->restore(); } //############################################################################## //#################################### Events ############################### //############################################################################## -void DatapickerImageView::wheelEvent(QWheelEvent *event) { - if (m_mouseMode == ZoomSelectionMode) { - if (event->delta() > 0) - scale(1.2, 1.2); - else if (event->delta() < 0) - scale(1.0/1.2, 1.0/1.2); - } else { +void DatapickerImageView::wheelEvent(QWheelEvent* event) { + //https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView + if (m_mouseMode == ZoomSelectionMode || (QApplication::keyboardModifiers() & Qt::ControlModifier)) { + int numDegrees = event->delta() / 8; + int numSteps = numDegrees / 15; // see QWheelEvent documentation + zoom(numSteps); + } else QGraphicsView::wheelEvent(event); - } +} + +void DatapickerImageView::zoom(int numSteps) { + m_numScheduledScalings += numSteps; + if (m_numScheduledScalings * numSteps < 0) // if user moved the wheel in another direction, we reset previously scheduled scalings + m_numScheduledScalings = numSteps; + + auto* anim = new QTimeLine(350, this); + anim->setUpdateInterval(20); + + connect(anim, &QTimeLine::valueChanged, this, &DatapickerImageView::scalingTime); + connect(anim, &QTimeLine::finished, this, &DatapickerImageView::animFinished); + anim->start(); +} + +void DatapickerImageView::scalingTime() { + qreal factor = 1.0 + qreal(m_numScheduledScalings) / 300.0; + scale(factor, factor); +} + +void DatapickerImageView::animFinished() { + if (m_numScheduledScalings > 0) + m_numScheduledScalings--; + else + m_numScheduledScalings++; + sender()->~QObject(); } void DatapickerImageView::mousePressEvent(QMouseEvent* event) { //prevent the deselection of items when context menu event //was triggered (right button click) if (event->button() == Qt::RightButton) { event->accept(); return; } if (event->button() == Qt::LeftButton && m_mouseMode == ZoomSelectionMode) { m_selectionStart = event->pos(); m_selectionBandIsShown = true; return; } QPointF eventPos = mapToScene(event->pos()); if ( m_mouseMode == SelectAndEditMode && m_image->isLoaded && sceneRect().contains(eventPos) ) { if ( m_image->plotPointsType() == DatapickerImage::AxisPoints ) { int childCount = m_image->childCount(AbstractAspect::IncludeHidden); if (childCount < 3) m_datapicker->addNewPoint(eventPos, m_image); } else if ( m_image->plotPointsType() == DatapickerImage::CurvePoints && m_datapicker->activeCurve() ) { m_datapicker->addNewPoint(eventPos, m_datapicker->activeCurve()); } } // make sure the datapicker (or its currently active curve) is selected in the project explorer if the view was clicked. // We need this for the case when we change from the project-node in the project explorer to the datapicker node by clicking the view. if (m_datapicker->activeCurve() && m_image->plotPointsType() != DatapickerImage::AxisPoints) { m_datapicker->setSelectedInView(false); m_datapicker->activeCurve()->setSelectedInView(true); } else { if (m_datapicker->activeCurve()) m_datapicker->activeCurve()->setSelectedInView(false); m_datapicker->setSelectedInView(true); } QGraphicsView::mousePressEvent(event); } void DatapickerImageView::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && m_mouseMode == ZoomSelectionMode) { m_selectionBandIsShown = false; viewport()->repaint(QRect(m_selectionStart, m_selectionEnd).normalized()); //don't zoom if very small region was selected, avoid occasional/unwanted zooming m_selectionEnd = event->pos(); if ( abs(m_selectionEnd.x()-m_selectionStart.x())>20 && abs(m_selectionEnd.y()-m_selectionStart.y())>20 ) fitInView(mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect(), Qt::KeepAspectRatio); } QGraphicsView::mouseReleaseEvent(event); } void DatapickerImageView::mouseMoveEvent(QMouseEvent* event) { if ( m_mouseMode == SelectAndEditMode || m_mouseMode == ZoomSelectionMode ) { if (m_image->isLoaded) setCursor(Qt::CrossCursor); else setCursor(Qt::ArrowCursor); } else { setCursor(Qt::ArrowCursor); } //show the selection band if (m_selectionBandIsShown) { QRect rect = QRect(m_selectionStart, m_selectionEnd).normalized(); m_selectionEnd = event->pos(); rect = rect.united(QRect(m_selectionStart, m_selectionEnd).normalized()); int penWidth = 5/transform().m11(); rect.setX(rect.x()-penWidth); rect.setY(rect.y()-penWidth); rect.setHeight(rect.height()+2*penWidth); rect.setWidth(rect.width()+2*penWidth); viewport()->repaint(rect); return; } QPointF pos = mapToScene(event->pos()); //show the current coordinates under the mouse cursor in the status bar if (m_image->plotPointsType() == DatapickerImage::CurvePoints) { QVector3D logicalPos = m_transform->mapSceneToLogical(pos, m_image->axisPoints()); if (m_image->axisPoints().type == DatapickerImage::Ternary) { emit statusInfo( "a =" + QString::number(logicalPos.x()) + ", b =" + QString::number(logicalPos.y()) + ", c =" + QString::number(logicalPos.z())); } else { QString xLabel('x'); QString yLabel('y'); if (m_image->axisPoints().type == DatapickerImage::PolarInDegree) { xLabel = 'r'; yLabel = "y(deg)"; } else if (m_image->axisPoints().type == DatapickerImage::PolarInRadians) { xLabel = 'r'; yLabel = "y(rad)"; } if (m_datapicker->activeCurve()) { QString statusText = i18n("%1, active curve \"%2\": %3=%4, %5=%6", m_datapicker->name(), m_datapicker->activeCurve()->name(), xLabel, QString::number(logicalPos.x()), yLabel, QString::number(logicalPos.y())); emit statusInfo(statusText); } } } //show the magnification window if ( magnificationFactor && m_mouseMode == SelectAndEditMode && m_image->isLoaded && sceneRect().contains(pos) && m_image->plotPointsType() != DatapickerImage::SegmentPoints ) { if (!m_image->m_magnificationWindow) { // m_image->m_magnificationWindow = new QGraphicsPixmapItem(0, scene()); m_image->m_magnificationWindow = new QGraphicsPixmapItem; scene()->addItem(m_image->m_magnificationWindow); m_image->m_magnificationWindow->setZValue(std::numeric_limits::max()); } m_image->m_magnificationWindow->setVisible(false); //copy the part of the view to be shown magnified const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Centimeter)/transform().m11(); const QRectF copyRect(pos.x() - size/(2*magnificationFactor), pos.y() - size/(2*magnificationFactor), size/magnificationFactor, size/magnificationFactor); QPixmap px = grab(mapFromScene(copyRect).boundingRect()); px = px.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); //draw the bounding rect QPainter painter(&px); const QPen pen = QPen(Qt::lightGray, 2/transform().m11()); painter.setPen(pen); QRect rect = px.rect(); rect.setWidth(rect.width()-pen.widthF()/2); rect.setHeight(rect.height()-pen.widthF()/2); painter.drawRect(rect); //set the pixmap m_image->m_magnificationWindow->setPixmap(px); m_image->m_magnificationWindow->setPos(pos.x()- px.width()/2, pos.y()- px.height()/2); m_image->m_magnificationWindow->setVisible(true); } else if (m_image->m_magnificationWindow) { m_image->m_magnificationWindow->setVisible(false); } QGraphicsView::mouseMoveEvent(event); } void DatapickerImageView::contextMenuEvent(QContextMenuEvent* e) { Q_UNUSED(e); //no need to propagate the event to the scene and graphics items QMenu *menu = new QMenu(this); this->createContextMenu(menu); menu->exec(QCursor::pos()); } //############################################################################## //#################################### SLOTs ############################### //############################################################################## void DatapickerImageView::changePointsType(QAction* action) { if (action == setAxisPointsAction) m_image->setPlotPointsType(DatapickerImage::AxisPoints); else if (action == setCurvePointsAction) m_image->setPlotPointsType(DatapickerImage::CurvePoints); else if (action == selectSegmentAction) m_image->setPlotPointsType(DatapickerImage::SegmentPoints); } void DatapickerImageView::changeZoom(QAction* action) { - if (action == zoomInViewAction) { - scale(1.2, 1.2); - } else if (action == zoomOutViewAction) { - scale(1.0/1.2, 1.0/1.2); - } else if (action == zoomOriginAction) { + if (action == zoomInViewAction) + zoom(1); + else if (action == zoomOutViewAction) + zoom(-1); + else if (action == zoomOriginAction) { static const float hscale = QApplication::desktop()->physicalDpiX()/(25.4*Worksheet::convertToSceneUnits(1,Worksheet::Millimeter)); static const float vscale = QApplication::desktop()->physicalDpiY()/(25.4*Worksheet::convertToSceneUnits(1,Worksheet::Millimeter)); setTransform(QTransform::fromScale(hscale, vscale)); m_rotationAngle = 0; } else if (action == zoomFitPageWidthAction) { float scaleFactor = viewport()->width()/scene()->sceneRect().width(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); m_rotationAngle = 0; } else if (action == zoomFitPageHeightAction) { float scaleFactor = viewport()->height()/scene()->sceneRect().height(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); m_rotationAngle = 0; } currentZoomAction = action; if (tbZoom) tbZoom->setDefaultAction(action); //change and set angle if tranform reset changeRotationAngle(); } void DatapickerImageView::changeSelectedItemsPosition(QAction* action) { if (scene()->selectedItems().isEmpty()) return; QPointF shift(0, 0); if (action == shiftLeftAction) shift.setX(1); else if (action == shiftRightAction) shift.setX(-1); else if (action == shiftUpAction) shift.setY(-1); else if (action == shiftDownAction) shift.setY(1); m_image->beginMacro(i18n("%1: change position of selected DatapickerPoints.", m_image->name())); const QVector axisPoints = m_image->children(AbstractAspect::IncludeHidden); for (auto* point : axisPoints) { if (!point->graphicsItem()->isSelected()) continue; QPointF newPos = point->position(); newPos = newPos + shift; point->setPosition(newPos); int pointIndex = m_image->indexOfChild(point, AbstractAspect::IncludeHidden); if (pointIndex == -1) continue; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.scenePos[pointIndex].setX(point->position().x()); points.scenePos[pointIndex].setY(point->position().y()); m_image->setUndoAware(false); m_image->setAxisPoints(points); m_image->setUndoAware(true); } for (auto* curve : m_image->parentAspect()->children()) { for (auto* point : curve->children(AbstractAspect::IncludeHidden)) { if (!point->graphicsItem()->isSelected()) continue; QPointF newPos = point->position(); newPos = newPos + shift; point->setPosition(newPos); } } m_image->endMacro(); } void DatapickerImageView::mouseModeChanged(QAction* action) { if (action == selectAndEditModeAction) { m_mouseMode = SelectAndEditMode; setInteractive(true); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(true); } else if (action == navigationModeAction) { m_mouseMode = NavigationMode; setInteractive(false); setDragMode(QGraphicsView::ScrollHandDrag); m_image->setSegmentsHoverEvent(false); } else if (action == zoomSelectionModeAction) { m_mouseMode = ZoomSelectionMode; setInteractive(false); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(false); } else { m_mouseMode = SelectAndMoveMode; setInteractive(true); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(false); } } void DatapickerImageView::magnificationChanged(QAction* action) { if (action == noMagnificationAction) magnificationFactor = 0; else if (action == twoTimesMagnificationAction) magnificationFactor = 2; else if (action == threeTimesMagnificationAction) magnificationFactor = 3; else if (action == fourTimesMagnificationAction) magnificationFactor = 4; else if (action == fiveTimesMagnificationAction) magnificationFactor = 5; } void DatapickerImageView::addCurve() { m_datapicker->beginMacro(i18n("%1: add new curve.", m_datapicker->name())); DatapickerCurve* curve = new DatapickerCurve(i18n("Curve")); curve->addDatasheet(m_image->axisPoints().type); m_datapicker->addChild(curve); m_datapicker->endMacro(); } void DatapickerImageView::changeRotationAngle() { this->rotate(m_rotationAngle); this->rotate(-m_image->rotationAngle()); m_rotationAngle = m_image->rotationAngle(); updateBackground(); } void DatapickerImageView::handleImageActions() { if (m_image->isLoaded) { magnificationActionGroup->setEnabled(true); setAxisPointsAction->setEnabled(true); int pointsCount = m_image->childCount(AbstractAspect::IncludeHidden); if (pointsCount > 0) navigationActionGroup->setEnabled(true); else navigationActionGroup->setEnabled(false); if (pointsCount > 2) { addCurveAction->setEnabled(true); if (m_datapicker->activeCurve()) { setCurvePointsAction->setEnabled(true); selectSegmentAction->setEnabled(true); } else { setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); } } else { addCurveAction->setEnabled(false); setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); if (m_image->plotPointsType() != DatapickerImage::AxisPoints) { m_image->setUndoAware(false); m_image->setPlotPointsType(DatapickerImage::AxisPoints); m_image->setUndoAware(true); } } } else { navigationActionGroup->setEnabled(false); magnificationActionGroup->setEnabled(false); setAxisPointsAction->setEnabled(false); addCurveAction->setEnabled(false); setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); } } void DatapickerImageView::exportToFile(const QString& path, const WorksheetView::ExportFormat format, const int resolution) { QRectF sourceRect; sourceRect = scene()->sceneRect(); //print if (format == WorksheetView::Pdf) { QPrinter printer(QPrinter::HighResolution); printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName(path); int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); printer.setPaperSize( QSizeF(w, h), QPrinter::Millimeter); printer.setPageMargins(0,0,0,0, QPrinter::Millimeter); printer.setPrintRange(QPrinter::PageRange); printer.setCreator( QLatin1String("LabPlot ") + LVERSION ); QPainter painter(&printer); painter.setRenderHint(QPainter::Antialiasing); QRectF targetRect(0, 0, painter.device()->width(),painter.device()->height()); painter.begin(&printer); exportPaint(&painter, targetRect, sourceRect); painter.end(); } else if (format == WorksheetView::Svg) { QSvgGenerator generator; generator.setFileName(path); int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); w = w*QApplication::desktop()->physicalDpiX()/25.4; h = h*QApplication::desktop()->physicalDpiY()/25.4; generator.setSize(QSize(w, h)); QRectF targetRect(0, 0, w, h); generator.setViewBox(targetRect); QPainter painter; painter.begin(&generator); exportPaint(&painter, targetRect, sourceRect); painter.end(); } else { //PNG //TODO add all formats supported by Qt in QImage int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); w = w*resolution/25.4; h = h*resolution/25.4; QImage image(QSize(w, h), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); QRectF targetRect(0, 0, w, h); QPainter painter; painter.begin(&image); painter.setRenderHint(QPainter::Antialiasing); exportPaint(&painter, targetRect, sourceRect); painter.end(); image.save(path, "png"); } } void DatapickerImageView::exportPaint(QPainter* painter, const QRectF& targetRect, const QRectF& sourceRect) { painter->save(); painter->scale(targetRect.width()/sourceRect.width(), targetRect.height()/sourceRect.height()); drawBackground(painter, sourceRect); painter->restore(); m_image->setPrinting(true); scene()->render(painter, QRectF(), sourceRect); m_image->setPrinting(false); } void DatapickerImageView::print(QPrinter* printer) { const QRectF scene_rect = sceneRect(); int w = Worksheet::convertFromSceneUnits(scene_rect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(scene_rect.height(), Worksheet::Millimeter); printer->setPaperSize( QSizeF(w, h), QPrinter::Millimeter); printer->setPageMargins(0,0,0,0, QPrinter::Millimeter); printer->setPrintRange(QPrinter::PageRange); printer->setCreator( QString("LabPlot ") + LVERSION ); QPainter painter(printer); QRectF targetRect(0, 0, painter.device()->width(),painter.device()->height()); painter.setRenderHint(QPainter::Antialiasing); painter.begin(printer); painter.save(); painter.scale(targetRect.width()/scene_rect.width(), targetRect.height()/scene_rect.height()); // canvas if (m_image->isLoaded) { if (m_image->plotImageType() == DatapickerImage::OriginalImage) { QImage todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter.drawImage(scene_rect.topLeft(), todraw); } else if (m_image->plotImageType() == DatapickerImage::ProcessedImage) { QImage todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter.drawImage(scene_rect.topLeft(), todraw); } else { painter.fillRect(scene_rect, Qt::white); } } else { painter.setBrush(QBrush(Qt::gray)); painter.drawRect(scene_rect); } painter.restore(); m_image->setPrinting(true); scene()->render(&painter, QRectF(), scene_rect); m_image->setPrinting(false); painter.end(); } void DatapickerImageView::updateBackground() { invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } diff --git a/src/commonfrontend/datapicker/DatapickerImageView.h b/src/commonfrontend/datapicker/DatapickerImageView.h index 0c4101cc4..c3a0d567e 100644 --- a/src/commonfrontend/datapicker/DatapickerImageView.h +++ b/src/commonfrontend/datapicker/DatapickerImageView.h @@ -1,143 +1,148 @@ /*************************************************************************** File : DatapickerImageView.h Project : LabPlot Description : DatapickerImage view for datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef DATAPICKERIMAGEVIEW_H #define DATAPICKERIMAGEVIEW_H #include "commonfrontend/worksheet/WorksheetView.h" class QMenu; class QToolBar; class QToolButton; class QWheelEvent; class AbstractAspect; class DatapickerImage; class Datapicker; class Transform; class QActionGroup; class QPrinter; class DatapickerImageView : public QGraphicsView { Q_OBJECT public: explicit DatapickerImageView(DatapickerImage* image); ~DatapickerImageView() override; void setScene(QGraphicsScene*); void exportToFile(const QString&, const WorksheetView::ExportFormat, const int); private: enum MouseMode {SelectAndEditMode, NavigationMode, ZoomSelectionMode, SelectAndMoveMode}; void initActions(); void initMenus(); void drawForeground(QPainter*, const QRectF&) override; void drawBackground(QPainter*, const QRectF&) override; void exportPaint(QPainter* painter, const QRectF& targetRect, const QRectF& sourceRect); //events void contextMenuEvent(QContextMenuEvent*) override; void wheelEvent(QWheelEvent*) override; void mousePressEvent(QMouseEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; DatapickerImage* m_image; Datapicker* m_datapicker; Transform* m_transform; MouseMode m_mouseMode{SelectAndEditMode}; bool m_selectionBandIsShown{false}; QPoint m_selectionStart; QPoint m_selectionEnd; int magnificationFactor{0}; float m_rotationAngle{0.0}; + int m_numScheduledScalings{0}; //Menus QMenu* m_zoomMenu; QMenu* m_viewMouseModeMenu; QMenu* m_viewImageMenu; QMenu* m_navigationMenu; QMenu* m_magnificationMenu; QToolButton* tbZoom{nullptr}; QAction* currentZoomAction; //Actions QAction* zoomInViewAction; QAction* zoomOutViewAction; QAction* zoomOriginAction; QAction* zoomFitPageHeightAction; QAction* zoomFitPageWidthAction; QAction* setAxisPointsAction; QAction* setCurvePointsAction; QAction* selectSegmentAction; QAction* addCurveAction; QAction* navigationModeAction; QAction* zoomSelectionModeAction; QAction* selectAndEditModeAction; QAction* selectAndMoveModeAction; QActionGroup* navigationActionGroup; QAction* shiftLeftAction; QAction* shiftRightAction; QAction* shiftDownAction; QAction* shiftUpAction; QActionGroup* magnificationActionGroup; QAction* noMagnificationAction; QAction* twoTimesMagnificationAction; QAction* threeTimesMagnificationAction; QAction* fourTimesMagnificationAction; QAction* fiveTimesMagnificationAction; public slots: void createContextMenu(QMenu*) const; void fillToolBar(QToolBar*); void print(QPrinter*); private slots: void mouseModeChanged(QAction*); void magnificationChanged(QAction*); void changeZoom(QAction*); void changeSelectedItemsPosition(QAction*); void changePointsType(QAction*); void handleImageActions(); void updateBackground(); void addCurve(); void changeRotationAngle(); + void zoom(int); + void scalingTime(); + void animFinished(); + signals: void statusInfo(const QString&); }; #endif diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp index 44349474b..dce9f7bc6 100644 --- a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp +++ b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp @@ -1,2943 +1,2965 @@ /*************************************************************************** File : SpreadsheetView.cpp Project : LabPlot Description : View class for Spreadsheet -------------------------------------------------------------------- Copyright : (C) 2011-2019 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 by Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "SpreadsheetView.h" #include "backend/spreadsheet/SpreadsheetModel.h" #include "backend/spreadsheet/Spreadsheet.h" #include "commonfrontend/spreadsheet/SpreadsheetItemDelegate.h" #include "commonfrontend/spreadsheet/SpreadsheetHeaderView.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "backend/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" #include "backend/core/datatypes/SimpleCopyThroughFilter.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/String2DoubleFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/datatypes/String2DateTimeFilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "kdefrontend/spreadsheet/AddSubtractValueDialog.h" #include "kdefrontend/spreadsheet/DropValuesDialog.h" #include "kdefrontend/spreadsheet/SortDialog.h" #include "kdefrontend/spreadsheet/RandomValuesDialog.h" #include "kdefrontend/spreadsheet/EquidistantValuesDialog.h" #include "kdefrontend/spreadsheet/FunctionValuesDialog.h" #include "kdefrontend/spreadsheet/StatisticsDialog.h" #include //for std::reverse /*! \class SpreadsheetView \brief View class for Spreadsheet \ingroup commonfrontend */ SpreadsheetView::SpreadsheetView(Spreadsheet* spreadsheet, bool readOnly) : QWidget(), m_tableView(new QTableView(this)), m_spreadsheet(spreadsheet), m_model(new SpreadsheetModel(spreadsheet)), m_readOnly(readOnly) { auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_tableView); if (m_readOnly) m_tableView->setEditTriggers(QTableView::NoEditTriggers); init(); //resize the view to show alls columns and the first 50 rows. //no need to resize the view when the project is being opened, //all views will be resized to the stored values at the end if (!m_spreadsheet->isLoading()) { int w = m_tableView->verticalHeader()->width(); int h = m_horizontalHeader->height(); for (int i = 0; i < m_horizontalHeader->count(); ++i) w += m_horizontalHeader->sectionSize(i); if (m_tableView->verticalHeader()->count() > 50 || m_tableView->verticalHeader()->count() < 10) h += m_tableView->verticalHeader()->sectionSize(0)*50; else h += m_tableView->verticalHeader()->sectionSize(0)*m_tableView->verticalHeader()->count(); resize(w+50, h); } KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet")); showComments(group.readEntry(QLatin1String("ShowComments"), false)); } SpreadsheetView::~SpreadsheetView() { delete m_model; } void SpreadsheetView::init() { initActions(); initMenus(); m_tableView->setModel(m_model); m_tableView->setItemDelegate(new SpreadsheetItemDelegate(this)); m_tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); //horizontal header m_horizontalHeader = new SpreadsheetHeaderView(this); m_horizontalHeader->setSectionsClickable(true); m_horizontalHeader->setHighlightSections(true); m_tableView->setHorizontalHeader(m_horizontalHeader); m_horizontalHeader->setSectionsMovable(true); m_horizontalHeader->installEventFilter(this); resizeHeader(); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionMoved, this, &SpreadsheetView::handleHorizontalSectionMoved); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionDoubleClicked, this, &SpreadsheetView::handleHorizontalHeaderDoubleClicked); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionResized, this, &SpreadsheetView::handleHorizontalSectionResized); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionClicked, this, &SpreadsheetView::columnClicked); // vertical header QHeaderView* v_header = m_tableView->verticalHeader(); v_header->setSectionResizeMode(QHeaderView::Fixed); v_header->setSectionsMovable(false); v_header->installEventFilter(this); setFocusPolicy(Qt::StrongFocus); setFocus(); installEventFilter(this); connectActions(); showComments(false); connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::updateHeaderGeometry); connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::handleHeaderDataChanged); connect(m_spreadsheet, &Spreadsheet::aspectAdded, this, &SpreadsheetView::handleAspectAdded); connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeRemoved,this, &SpreadsheetView::handleAspectAboutToBeRemoved); connect(m_spreadsheet, &Spreadsheet::requestProjectContextMenu, this, &SpreadsheetView::createContextMenu); for (auto* column : m_spreadsheet->children()) connect(column, &Column::requestProjectContextMenu, this, &SpreadsheetView::createColumnContextMenu); //selection relevant connections QItemSelectionModel* sel_model = m_tableView->selectionModel(); connect(sel_model, &QItemSelectionModel::currentColumnChanged, this, &SpreadsheetView::currentColumnChanged); connect(sel_model, &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged); connect(sel_model, &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged); connect(m_spreadsheet, &Spreadsheet::columnSelected, this, &SpreadsheetView::selectColumn); connect(m_spreadsheet, &Spreadsheet::columnDeselected, this, &SpreadsheetView::deselectColumn); } /*! set the column sizes to the saved values or resize to content if no size was saved yet */ void SpreadsheetView::resizeHeader() { DEBUG("SpreadsheetView::resizeHeader()"); for (int i = 0; i < m_spreadsheet->children().size(); ++i) { Column* col = m_spreadsheet->child(i); if (col->width() == 0) m_tableView->resizeColumnToContents(i); else m_tableView->setColumnWidth(i, col->width()); } } void SpreadsheetView::initActions() { // selection related actions action_cut_selection = new QAction(QIcon::fromTheme("edit-cut"), i18n("Cu&t"), this); action_copy_selection = new QAction(QIcon::fromTheme("edit-copy"), i18n("&Copy"), this); action_paste_into_selection = new QAction(QIcon::fromTheme("edit-paste"), i18n("Past&e"), this); action_mask_selection = new QAction(QIcon::fromTheme("edit-node"), i18n("&Mask Selection"), this); action_unmask_selection = new QAction(QIcon::fromTheme("format-remove-node"), i18n("&Unmask Selection"), this); action_clear_selection = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Selection"), this); action_select_all = new QAction(QIcon::fromTheme("edit-select-all"), i18n("Select All"), this); // action_set_formula = new QAction(QIcon::fromTheme(QString()), i18n("Assign &Formula"), this); // action_recalculate = new QAction(QIcon::fromTheme(QString()), i18n("Recalculate"), this); action_fill_sel_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); action_fill_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); action_fill_random = new QAction(QIcon::fromTheme(QString()), i18n("Uniform Random Values"), this); action_fill_random_nonuniform = new QAction(QIcon::fromTheme(QString()), i18n("Random Values"), this); action_fill_equidistant = new QAction(QIcon::fromTheme(QString()), i18n("Equidistant Values"), this); action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this); action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this); //spreadsheet related actions action_toggle_comments = new QAction(QIcon::fromTheme("document-properties"), i18n("Show Comments"), this); action_clear_spreadsheet = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Spreadsheet"), this); action_clear_masks = new QAction(QIcon::fromTheme("format-remove-node"), i18n("Clear Masks"), this); action_sort_spreadsheet = new QAction(QIcon::fromTheme("view-sort-ascending"), i18n("&Sort Spreadsheet"), this); action_go_to_cell = new QAction(QIcon::fromTheme("go-jump"), i18n("&Go to Cell"), this); action_statistics_all_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this ); // column related actions action_insert_column_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Column Left"), this); action_insert_column_right = new QAction(QIcon::fromTheme("edit-table-insert-column-right"), i18n("Insert Column Right"), this); action_insert_columns_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Multiple Columns Left"), this); action_insert_columns_right = new QAction(QIcon::fromTheme("edit-table-insert-column-right"), i18n("Insert Multiple Columns Right"), this); action_remove_columns = new QAction(QIcon::fromTheme("edit-table-delete-column"), i18n("Remove Selected Columns"), this); action_clear_columns = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Selected Columns"), this); action_set_as_none = new QAction(i18n("None"), this); action_set_as_none->setData(AbstractColumn::NoDesignation); action_set_as_x = new QAction("X", this); action_set_as_x->setData(AbstractColumn::X); action_set_as_y = new QAction("Y", this); action_set_as_y->setData(AbstractColumn::Y); action_set_as_z = new QAction("Z", this); action_set_as_z->setData(AbstractColumn::Z); action_set_as_xerr = new QAction(i18n("X-error"), this); action_set_as_xerr->setData(AbstractColumn::XError); action_set_as_xerr_minus = new QAction(i18n("X-error minus"), this); action_set_as_xerr_minus->setData(AbstractColumn::XErrorMinus); action_set_as_xerr_plus = new QAction(i18n("X-error plus"), this); action_set_as_xerr_plus->setData(AbstractColumn::XErrorPlus); action_set_as_yerr = new QAction(i18n("Y-error"), this); action_set_as_yerr->setData(AbstractColumn::YError); action_set_as_yerr_minus = new QAction(i18n("Y-error minus"), this); action_set_as_yerr_minus->setData(AbstractColumn::YErrorMinus); action_set_as_yerr_plus = new QAction(i18n("Y-error plus"), this); action_set_as_yerr_plus->setData(AbstractColumn::YErrorPlus); //data manipulation action_add_value = new QAction(i18n("Add Value"), this); action_add_value->setData(AddSubtractValueDialog::Add); action_subtract_value = new QAction(i18n("Subtract Value"), this); action_subtract_value->setData(AddSubtractValueDialog::Subtract); action_multiply_value = new QAction(i18n("Multiply by Value"), this); action_multiply_value->setData(AddSubtractValueDialog::Multiply); action_divide_value = new QAction(i18n("Divide by Value"), this); action_divide_value->setData(AddSubtractValueDialog::Divide); action_drop_values = new QAction(QIcon::fromTheme(QString()), i18n("Drop Values"), this); action_mask_values = new QAction(QIcon::fromTheme(QString()), i18n("Mask Values"), this); action_reverse_columns = new QAction(QIcon::fromTheme(QString()), i18n("Reverse"), this); // action_join_columns = new QAction(QIcon::fromTheme(QString()), i18n("Join"), this); action_normalize_columns = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize"), this); action_normalize_selection = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize Selection"), this); //sort and statistics action_sort_columns = new QAction(QIcon::fromTheme(QString()), i18n("&Selected Columns"), this); action_sort_asc_column = new QAction(QIcon::fromTheme("view-sort-ascending"), i18n("&Ascending"), this); action_sort_desc_column = new QAction(QIcon::fromTheme("view-sort-descending"), i18n("&Descending"), this); action_statistics_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Column Statisti&cs"), this); // row related actions action_insert_row_above = new QAction(QIcon::fromTheme("edit-table-insert-row-above") ,i18n("Insert Row Above"), this); action_insert_row_below = new QAction(QIcon::fromTheme("edit-table-insert-row-below"), i18n("Insert Row Below"), this); action_insert_rows_above = new QAction(QIcon::fromTheme("edit-table-insert-row-above") ,i18n("Insert Multiple Rows Above"), this); action_insert_rows_below = new QAction(QIcon::fromTheme("edit-table-insert-row-below"), i18n("Insert Multiple Rows Below"), this); action_remove_rows = new QAction(QIcon::fromTheme("edit-table-delete-row"), i18n("Remo&ve Selected Rows"), this); action_clear_rows = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Selected Rows"), this); action_statistics_rows = new QAction(QIcon::fromTheme("view-statistics"), i18n("Row Statisti&cs"), this); //plot data action action_plot_data_xycurve = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-Curve"), this); action_plot_data_xycurve->setData(PlotDataDialog::PlotXYCurve); action_plot_data_histogram = new QAction(QIcon::fromTheme("labplot-histogram"), i18n("Histogram"), this); action_plot_data_histogram->setData(PlotDataDialog::PlotHistogram); //Analyze and plot menu actions addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Reduce Data"), this); // addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-data-reduction-curve"), i18n("Reduce Data"), this); addDataReductionAction->setData(PlotDataDialog::DataReduction); addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiate"), this); // addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-differentiation-curve"), i18n("Differentiate"), this); addDifferentiationAction->setData(PlotDataDialog::Differentiation); addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integrate"), this); // addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-integration-curve"), i18n("Integrate"), this); addIntegrationAction->setData(PlotDataDialog::Integration); addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this); addInterpolationAction->setData(PlotDataDialog::Interpolation); addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this); addSmoothAction->setData(PlotDataDialog::Smoothing); QAction* fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Power"), this); fitAction->setData(PlotDataDialog::FitPower); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Exponential (degree 1)"), this); fitAction->setData(PlotDataDialog::FitExp1); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Exponential (degree 2)"), this); fitAction->setData(PlotDataDialog::FitExp2); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Inverse Exponential"), this); fitAction->setData(PlotDataDialog::FitInvExp); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Gauss"), this); fitAction->setData(PlotDataDialog::FitGauss); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Cauchy-Lorentz"), this); fitAction->setData(PlotDataDialog::FitCauchyLorentz); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Arc Tangent"), this); fitAction->setData(PlotDataDialog::FitTan); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Hyperbolic Tangent"), this); fitAction->setData(PlotDataDialog::FitTanh); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Error Function"), this); fitAction->setData(PlotDataDialog::FitErrFunc); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Custom"), this); fitAction->setData(PlotDataDialog::FitCustom); addFitAction.append(fitAction); addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this); addFourierFilterAction->setData(PlotDataDialog::FourierFilter); } void SpreadsheetView::initMenus() { //Selection menu m_selectionMenu = new QMenu(i18n("Selection"), this); m_selectionMenu->setIcon(QIcon::fromTheme("selection")); QMenu* submenu = nullptr; if (!m_readOnly) { submenu = new QMenu(i18n("Fi&ll Selection With"), this); submenu->setIcon(QIcon::fromTheme("select-rectangle")); submenu->addAction(action_fill_sel_row_numbers); submenu->addAction(action_fill_const); m_selectionMenu->addMenu(submenu); m_selectionMenu->addSeparator(); m_selectionMenu->addAction(action_cut_selection); } m_selectionMenu->addAction(action_copy_selection); if (!m_readOnly) { m_selectionMenu->addAction(action_paste_into_selection); m_selectionMenu->addAction(action_clear_selection); m_selectionMenu->addSeparator(); m_selectionMenu->addAction(action_mask_selection); m_selectionMenu->addAction(action_unmask_selection); m_selectionMenu->addSeparator(); m_selectionMenu->addAction(action_normalize_selection); } //plot data menu m_plotDataMenu = new QMenu(i18n("Plot Data"), this); m_plotDataMenu->addAction(action_plot_data_xycurve); m_plotDataMenu->addAction(action_plot_data_histogram); // Column menu m_columnMenu = new QMenu(this); m_columnMenu->addMenu(m_plotDataMenu); // Data manipulation sub-menu QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation"), this); dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); dataManipulationMenu->addAction(addDataReductionAction); // Data fit sub-menu QMenu* dataFitMenu = new QMenu(i18n("Fit"), this); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); dataFitMenu->addAction(addFitAction.at(2)); dataFitMenu->addAction(addFitAction.at(3)); dataFitMenu->addAction(addFitAction.at(4)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(5)); dataFitMenu->addAction(addFitAction.at(6)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(7)); dataFitMenu->addAction(addFitAction.at(8)); dataFitMenu->addAction(addFitAction.at(9)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(10)); //analyze and plot data menu m_analyzePlotMenu = new QMenu(i18n("Analyze and Plot Data"), this); m_analyzePlotMenu->insertMenu(nullptr, dataManipulationMenu); m_analyzePlotMenu->addSeparator(); m_analyzePlotMenu->addAction(addDifferentiationAction); m_analyzePlotMenu->addAction(addIntegrationAction); m_analyzePlotMenu->addSeparator(); m_analyzePlotMenu->addAction(addInterpolationAction); m_analyzePlotMenu->addAction(addSmoothAction); m_analyzePlotMenu->addAction(addFourierFilterAction); m_analyzePlotMenu->addSeparator(); m_analyzePlotMenu->addMenu(dataFitMenu); m_columnMenu->addMenu(m_analyzePlotMenu); m_columnSetAsMenu = new QMenu(i18n("Set Column As"), this); m_columnMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_x); m_columnSetAsMenu->addAction(action_set_as_y); m_columnSetAsMenu->addAction(action_set_as_z); m_columnSetAsMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_xerr); m_columnSetAsMenu->addAction(action_set_as_xerr_minus); m_columnSetAsMenu->addAction(action_set_as_xerr_plus); m_columnSetAsMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_yerr); m_columnSetAsMenu->addAction(action_set_as_yerr_minus); m_columnSetAsMenu->addAction(action_set_as_yerr_plus); m_columnSetAsMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_none); m_columnMenu->addMenu(m_columnSetAsMenu); if (!m_readOnly) { m_columnGenerateDataMenu = new QMenu(i18n("Generate Data"), this); m_columnGenerateDataMenu->addAction(action_fill_row_numbers); m_columnGenerateDataMenu->addAction(action_fill_const); m_columnGenerateDataMenu->addAction(action_fill_equidistant); m_columnGenerateDataMenu->addAction(action_fill_random_nonuniform); m_columnGenerateDataMenu->addAction(action_fill_function); m_columnMenu->addSeparator(); m_columnMenu->addMenu(m_columnGenerateDataMenu); m_columnMenu->addSeparator(); m_columnManipulateDataMenu = new QMenu(i18n("Manipulate Data"), this); m_columnManipulateDataMenu->addAction(action_add_value); m_columnManipulateDataMenu->addAction(action_subtract_value); m_columnManipulateDataMenu->addAction(action_multiply_value); m_columnManipulateDataMenu->addAction(action_divide_value); m_columnManipulateDataMenu->addSeparator(); m_columnManipulateDataMenu->addAction(action_reverse_columns); m_columnManipulateDataMenu->addSeparator(); m_columnManipulateDataMenu->addAction(action_drop_values); m_columnManipulateDataMenu->addAction(action_mask_values); m_columnManipulateDataMenu->addSeparator(); // m_columnManipulateDataMenu->addAction(action_join_columns); m_columnManipulateDataMenu->addAction(action_normalize_columns); m_columnMenu->addMenu(m_columnManipulateDataMenu); m_columnMenu->addSeparator(); m_columnSortMenu = new QMenu(i18n("Sort"), this); m_columnSortMenu->setIcon(QIcon::fromTheme("view-sort-ascending")); m_columnSortMenu->addAction(action_sort_asc_column); m_columnSortMenu->addAction(action_sort_desc_column); m_columnSortMenu->addAction(action_sort_columns); m_columnMenu->addSeparator(); m_columnMenu->addMenu(m_columnSortMenu); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_insert_column_left); m_columnMenu->addAction(action_insert_column_right); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_insert_columns_left); m_columnMenu->addAction(action_insert_columns_right); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_remove_columns); m_columnMenu->addAction(action_clear_columns); } m_columnMenu->addSeparator(); m_columnMenu->addAction(action_toggle_comments); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_statistics_columns); //Spreadsheet menu m_spreadsheetMenu = new QMenu(this); m_spreadsheetMenu->addMenu(m_plotDataMenu); m_spreadsheetMenu->addMenu(m_analyzePlotMenu); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addMenu(m_selectionMenu); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_select_all); if (!m_readOnly) { m_spreadsheetMenu->addAction(action_clear_spreadsheet); m_spreadsheetMenu->addAction(action_clear_masks); m_spreadsheetMenu->addAction(action_sort_spreadsheet); } m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_go_to_cell); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_toggle_comments); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_statistics_all_columns); //Row menu m_rowMenu = new QMenu(this); if (!m_readOnly) { submenu = new QMenu(i18n("Fi&ll Selection With"), this); submenu->addAction(action_fill_sel_row_numbers); submenu->addAction(action_fill_const); m_rowMenu->addMenu(submenu); m_rowMenu->addSeparator(); m_rowMenu->addAction(action_insert_row_above); m_rowMenu->addAction(action_insert_row_below); m_rowMenu->addSeparator(); m_rowMenu->addAction(action_insert_rows_above); m_rowMenu->addAction(action_insert_rows_below); m_rowMenu->addSeparator(); m_rowMenu->addAction(action_remove_rows); m_rowMenu->addAction(action_clear_rows); } m_rowMenu->addSeparator(); m_rowMenu->addAction(action_statistics_rows); action_statistics_rows->setVisible(false); } void SpreadsheetView::connectActions() { connect(action_cut_selection, &QAction::triggered, this, &SpreadsheetView::cutSelection); connect(action_copy_selection, &QAction::triggered, this, &SpreadsheetView::copySelection); connect(action_paste_into_selection, &QAction::triggered, this, &SpreadsheetView::pasteIntoSelection); connect(action_mask_selection, &QAction::triggered, this, &SpreadsheetView::maskSelection); connect(action_unmask_selection, &QAction::triggered, this, &SpreadsheetView::unmaskSelection); connect(action_clear_selection, &QAction::triggered, this, &SpreadsheetView::clearSelectedCells); // connect(action_recalculate, &QAction::triggered, this, &SpreadsheetView::recalculateSelectedCells); connect(action_fill_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillWithRowNumbers); connect(action_fill_sel_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRowNumbers); // connect(action_fill_random, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRandomNumbers); connect(action_fill_random_nonuniform, &QAction::triggered, this, &SpreadsheetView::fillWithRandomValues); connect(action_fill_equidistant, &QAction::triggered, this, &SpreadsheetView::fillWithEquidistantValues); connect(action_fill_function, &QAction::triggered, this, &SpreadsheetView::fillWithFunctionValues); connect(action_fill_const, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithConstValues); connect(action_select_all, &QAction::triggered, m_tableView, &QTableView::selectAll); connect(action_clear_spreadsheet, &QAction::triggered, m_spreadsheet, &Spreadsheet::clear); connect(action_clear_masks, &QAction::triggered, m_spreadsheet, &Spreadsheet::clearMasks); connect(action_sort_spreadsheet, &QAction::triggered, this, &SpreadsheetView::sortSpreadsheet); connect(action_go_to_cell, &QAction::triggered, this, static_cast(&SpreadsheetView::goToCell)); connect(action_insert_column_left, &QAction::triggered, this, &SpreadsheetView::insertColumnLeft); connect(action_insert_column_right, &QAction::triggered, this, &SpreadsheetView::insertColumnRight); connect(action_insert_columns_left, &QAction::triggered, this, static_cast(&SpreadsheetView::insertColumnsLeft)); connect(action_insert_columns_right, &QAction::triggered, this, static_cast(&SpreadsheetView::insertColumnsRight)); connect(action_remove_columns, &QAction::triggered, this, &SpreadsheetView::removeSelectedColumns); connect(action_clear_columns, &QAction::triggered, this, &SpreadsheetView::clearSelectedColumns); connect(action_set_as_none, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_x, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_y, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_z, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_xerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_xerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_xerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_yerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_yerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_yerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); //data manipulation connect(action_add_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_subtract_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_multiply_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_divide_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_reverse_columns, &QAction::triggered, this, &SpreadsheetView::reverseColumns); connect(action_drop_values, &QAction::triggered, this, &SpreadsheetView::dropColumnValues); connect(action_mask_values, &QAction::triggered, this, &SpreadsheetView::maskColumnValues); // connect(action_join_columns, &QAction::triggered, this, &SpreadsheetView::joinColumns); connect(action_normalize_columns, &QAction::triggered, this, &SpreadsheetView::normalizeSelectedColumns); connect(action_normalize_selection, &QAction::triggered, this, &SpreadsheetView::normalizeSelection); //sort connect(action_sort_columns, &QAction::triggered, this, &SpreadsheetView::sortSelectedColumns); connect(action_sort_asc_column, &QAction::triggered, this, &SpreadsheetView::sortColumnAscending); connect(action_sort_desc_column, &QAction::triggered, this, &SpreadsheetView::sortColumnDescending); //statistics connect(action_statistics_columns, &QAction::triggered, this, &SpreadsheetView::showColumnStatistics); connect(action_statistics_all_columns, &QAction::triggered, this, &SpreadsheetView::showAllColumnsStatistics); connect(action_insert_row_above, &QAction::triggered, this, &SpreadsheetView::insertRowAbove); connect(action_insert_row_below, &QAction::triggered, this, &SpreadsheetView::insertRowBelow); connect(action_insert_rows_above, &QAction::triggered, this, static_cast(&SpreadsheetView::insertRowsAbove)); connect(action_insert_rows_below, &QAction::triggered, this, static_cast(&SpreadsheetView::insertRowsBelow)); connect(action_remove_rows, &QAction::triggered, this, &SpreadsheetView::removeSelectedRows); connect(action_clear_rows, &QAction::triggered, this, &SpreadsheetView::clearSelectedRows); connect(action_statistics_rows, &QAction::triggered, this, &SpreadsheetView::showRowStatistics); connect(action_toggle_comments, &QAction::triggered, this, &SpreadsheetView::toggleComments); connect(action_plot_data_xycurve, &QAction::triggered, this, &SpreadsheetView::plotData); connect(action_plot_data_histogram, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addDataReductionAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addDifferentiationAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addIntegrationAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addInterpolationAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addSmoothAction, &QAction::triggered, this, &SpreadsheetView::plotData); for (const auto& action : addFitAction) connect(action, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addFourierFilterAction, &QAction::triggered,this, &SpreadsheetView::plotData); } void SpreadsheetView::fillToolBar(QToolBar* toolBar) { if (!m_readOnly) { toolBar->addAction(action_insert_row_above); toolBar->addAction(action_insert_row_below); toolBar->addAction(action_remove_rows); } toolBar->addAction(action_statistics_rows); toolBar->addSeparator(); if (!m_readOnly) { toolBar->addAction(action_insert_column_left); toolBar->addAction(action_insert_column_right); toolBar->addAction(action_remove_columns); } toolBar->addAction(action_statistics_columns); if (!m_readOnly) { toolBar->addSeparator(); toolBar->addAction(action_sort_asc_column); toolBar->addAction(action_sort_desc_column); } } /*! * Populates the menu \c menu with the spreadsheet and spreadsheet view relevant actions. * The menu is used * - as the context menu in SpreadsheetView * - as the "spreadsheet menu" in the main menu-bar (called form MainWin) * - as a part of the spreadsheet context menu in project explorer */ void SpreadsheetView::createContextMenu(QMenu* menu) { Q_ASSERT(menu); checkSpreadsheetMenu(); QAction* firstAction = nullptr; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size()>1) firstAction = menu->actions().at(1); if (m_spreadsheet->columnCount() > 0 && m_spreadsheet->rowCount() > 0) { menu->insertMenu(firstAction, m_plotDataMenu); menu->insertSeparator(firstAction); } menu->insertMenu(firstAction, m_selectionMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_select_all); if (!m_readOnly) { menu->insertAction(firstAction, action_clear_spreadsheet); menu->insertAction(firstAction, action_clear_masks); menu->insertAction(firstAction, action_sort_spreadsheet); menu->insertSeparator(firstAction); } menu->insertAction(firstAction, action_go_to_cell); menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_toggle_comments); menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_statistics_all_columns); menu->insertSeparator(firstAction); } /*! * adds column specific actions in SpreadsheetView to the context menu shown in the project explorer. */ void SpreadsheetView::createColumnContextMenu(QMenu* menu) { const Column* column = dynamic_cast(QObject::sender()); if (!column) return; //should never happen, since the sender is always a Column if (column->isNumeric()) { QAction* firstAction = menu->actions().at(1); menu->insertMenu(firstAction, m_columnSetAsMenu); const bool hasValues = column->hasValues(); if (!m_readOnly) { menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_columnGenerateDataMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_columnManipulateDataMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_columnSortMenu); action_sort_asc_column->setVisible(true); action_sort_desc_column->setVisible(true); action_sort_columns->setVisible(false); //in case no cells are available, deactivate the actions that only make sense in the presence of cells const bool hasCells = m_spreadsheet->rowCount() > 0; m_columnGenerateDataMenu->setEnabled(hasCells); //in case no valid numerical values are available, deactivate the actions that only make sense in the presence of values action_reverse_columns->setEnabled(hasValues); action_drop_values->setEnabled(hasValues); action_mask_values->setEnabled(hasValues); action_normalize_columns->setEnabled(hasValues); m_columnSortMenu->setEnabled(hasValues); } menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_statistics_columns); action_statistics_columns->setEnabled(hasValues); } } //SLOTS void SpreadsheetView::handleAspectAdded(const AbstractAspect* aspect) { const Column* col = dynamic_cast(aspect); if (!col || col->parentAspect() != m_spreadsheet) return; int index = m_spreadsheet->indexOfChild(col); if (col->width() == 0) m_tableView->resizeColumnToContents(index); else m_tableView->setColumnWidth(index, col->width()); connect(col, &Column::requestProjectContextMenu, this, &SpreadsheetView::createColumnContextMenu); } void SpreadsheetView::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const Column* col = dynamic_cast(aspect); if (!col || col->parentAspect() != m_spreadsheet) return; disconnect(col, nullptr, this, nullptr); } void SpreadsheetView::handleHorizontalSectionResized(int logicalIndex, int oldSize, int newSize) { Q_UNUSED(logicalIndex); Q_UNUSED(oldSize); //save the new size in the column Column* col = m_spreadsheet->child(logicalIndex); col->setWidth(newSize); } void SpreadsheetView::goToCell(int row, int col) { QModelIndex index = m_model->index(row, col); m_tableView->scrollTo(index); m_tableView->setCurrentIndex(index); } void SpreadsheetView::handleHorizontalSectionMoved(int index, int from, int to) { Q_UNUSED(index); static bool inside = false; if (inside) return; Q_ASSERT(index == from); inside = true; m_tableView->horizontalHeader()->moveSection(to, from); inside = false; m_spreadsheet->moveColumn(from, to); } //TODO Implement the "change of the column name"-mode upon a double click void SpreadsheetView::handleHorizontalHeaderDoubleClicked(int index) { Q_UNUSED(index); } /*! Returns whether comments are shown currently or not */ bool SpreadsheetView::areCommentsShown() const { return m_horizontalHeader->areCommentsShown(); } /*! toggles the column comment in the horizontal header */ void SpreadsheetView::toggleComments() { showComments(!areCommentsShown()); //TODO if (areCommentsShown()) action_toggle_comments->setText(i18n("Hide Comments")); else action_toggle_comments->setText(i18n("Show Comments")); } //! Shows (\c on=true) or hides (\c on=false) the column comments in the horizontal header void SpreadsheetView::showComments(bool on) { m_horizontalHeader->showComments(on); } void SpreadsheetView::currentColumnChanged(const QModelIndex & current, const QModelIndex & previous) { Q_UNUSED(previous); int col = current.column(); if (col < 0 || col >= m_spreadsheet->columnCount()) return; } //TODO void SpreadsheetView::handleHeaderDataChanged(Qt::Orientation orientation, int first, int last) { if (orientation != Qt::Horizontal) return; QItemSelectionModel * sel_model = m_tableView->selectionModel(); int col = sel_model->currentIndex().column(); if (col < first || col > last) return; } /*! Returns the number of selected columns. If \c full is \c true, this function only returns the number of fully selected columns. */ int SpreadsheetView::selectedColumnCount(bool full) const { int count = 0; const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) if (isColumnSelected(i, full)) count++; return count; } /*! Returns the number of (at least partly) selected columns with the plot designation \param pd. */ int SpreadsheetView::selectedColumnCount(AbstractColumn::PlotDesignation pd) const{ int count = 0; const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) if ( isColumnSelected(i, false) && (m_spreadsheet->column(i)->plotDesignation() == pd) ) count++; return count; } /*! Returns \c true if column \param col is selected, otherwise returns \c false. If \param full is \c true, this function only returns true if the whole column is selected. */ bool SpreadsheetView::isColumnSelected(int col, bool full) const { if (full) return m_tableView->selectionModel()->isColumnSelected(col, QModelIndex()); else return m_tableView->selectionModel()->columnIntersectsSelection(col, QModelIndex()); } /*! Returns all selected columns. If \param full is true, this function only returns a column if the whole column is selected. */ QVector SpreadsheetView::selectedColumns(bool full) const { QVector columns; const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) if (isColumnSelected(i, full)) columns << m_spreadsheet->column(i); return columns; } /*! Returns \c true if row \param row is selected; otherwise returns \c false If \param full is \c true, this function only returns \c true if the whole row is selected. */ bool SpreadsheetView::isRowSelected(int row, bool full) const { if (full) return m_tableView->selectionModel()->isRowSelected(row, QModelIndex()); else return m_tableView->selectionModel()->rowIntersectsSelection(row, QModelIndex()); } /*! Return the index of the first selected column. If \param full is \c true, this function only looks for fully selected columns. */ int SpreadsheetView::firstSelectedColumn(bool full) const { const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) { if (isColumnSelected(i, full)) return i; } return -1; } /*! Return the index of the last selected column. If \param full is \c true, this function only looks for fully selected columns. */ int SpreadsheetView::lastSelectedColumn(bool full) const { const int cols = m_spreadsheet->columnCount(); for (int i = cols - 1; i >= 0; i--) if (isColumnSelected(i, full)) return i; return -2; } /*! Return the index of the first selected row. If \param full is \c true, this function only looks for fully selected rows. */ int SpreadsheetView::firstSelectedRow(bool full) const{ QModelIndexList indexes; if (!full) indexes = m_tableView->selectionModel()->selectedIndexes(); else indexes = m_tableView->selectionModel()->selectedRows(); if (!indexes.empty()) return indexes.first().row(); else return -1; } /*! Return the index of the last selected row. If \param full is \c true, this function only looks for fully selected rows. */ int SpreadsheetView::lastSelectedRow(bool full) const { QModelIndexList indexes; if (!full) indexes = m_tableView->selectionModel()->selectedIndexes(); else indexes = m_tableView->selectionModel()->selectedRows(); if (!indexes.empty()) return indexes.last().row(); else return -2; } /*! Return whether a cell is selected */ bool SpreadsheetView::isCellSelected(int row, int col) const { if (row < 0 || col < 0 || row >= m_spreadsheet->rowCount() || col >= m_spreadsheet->columnCount()) return false; return m_tableView->selectionModel()->isSelected(m_model->index(row, col)); } /*! Get the complete set of selected rows. */ IntervalAttribute SpreadsheetView::selectedRows(bool full) const { IntervalAttribute result; const int rows = m_spreadsheet->rowCount(); for (int i = 0; i < rows; i++) if (isRowSelected(i, full)) result.setValue(i, true); return result; } /*! Select/Deselect a cell. */ void SpreadsheetView::setCellSelected(int row, int col, bool select) { m_tableView->selectionModel()->select(m_model->index(row, col), select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); } /*! Select/Deselect a range of cells. */ void SpreadsheetView::setCellsSelected(int first_row, int first_col, int last_row, int last_col, bool select) { QModelIndex top_left = m_model->index(first_row, first_col); QModelIndex bottom_right = m_model->index(last_row, last_col); m_tableView->selectionModel()->select(QItemSelection(top_left, bottom_right), select ? QItemSelectionModel::SelectCurrent : QItemSelectionModel::Deselect); } /*! Determine the current cell (-1 if no cell is designated as the current). */ void SpreadsheetView::getCurrentCell(int* row, int* col) const { QModelIndex index = m_tableView->selectionModel()->currentIndex(); if (index.isValid()) { *row = index.row(); *col = index.column(); } else { *row = -1; *col = -1; } } bool SpreadsheetView::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::ContextMenu) { auto* cm_event = static_cast(event); const QPoint global_pos = cm_event->globalPos(); if (watched == m_tableView->verticalHeader()) { bool onlyNumeric = true; for (int i = 0; i < m_spreadsheet->columnCount(); ++i) { if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::Numeric) { onlyNumeric = false; break; } } action_statistics_rows->setVisible(onlyNumeric); m_rowMenu->exec(global_pos); } else if (watched == m_horizontalHeader) { const int col = m_horizontalHeader->logicalIndexAt(cm_event->pos()); if (!isColumnSelected(col, true)) { QItemSelectionModel* sel_model = m_tableView->selectionModel(); sel_model->clearSelection(); sel_model->select(QItemSelection(m_model->index(0, col, QModelIndex()), m_model->index(m_model->rowCount()-1, col, QModelIndex())), QItemSelectionModel::Select); } if (selectedColumns().size() == 1) { action_sort_columns->setVisible(false); action_sort_asc_column->setVisible(true); action_sort_desc_column->setVisible(true); } else { action_sort_columns->setVisible(true); action_sort_asc_column->setVisible(false); action_sort_desc_column->setVisible(false); } //check whether we have non-numeric columns selected and deactivate actions for numeric columns bool numeric = true; bool plottable = true; bool datetime = false; for (const Column* col : selectedColumns()) { if ( !(col->columnMode() == AbstractColumn::Numeric || col->columnMode() == AbstractColumn::Integer) ) { datetime = (col->columnMode() == AbstractColumn::DateTime); if (!datetime) plottable = false; numeric = false; break; } } m_plotDataMenu->setEnabled(plottable); m_analyzePlotMenu->setEnabled(numeric); m_columnSetAsMenu->setEnabled(numeric); if (!m_readOnly) { m_columnGenerateDataMenu->setEnabled(numeric); m_columnManipulateDataMenu->setEnabled(numeric || datetime); m_columnSortMenu->setEnabled(numeric); } action_statistics_columns->setEnabled(numeric); if (numeric) { bool hasValues = false; for (const Column* col : selectedColumns()) { if (col->hasValues()) { hasValues = true; break; } } if (!m_readOnly) { //in case no cells are available, deactivate the actions that only make sense in the presence of cells const bool hasCells = m_spreadsheet->rowCount() > 0; m_columnGenerateDataMenu->setEnabled(hasCells); //in case no valid numerical values are available, deactivate the actions that only make sense in the presence of values m_columnManipulateDataMenu->setEnabled(hasValues); m_columnSortMenu->setEnabled(hasValues); } action_statistics_columns->setEnabled(hasValues); } m_columnMenu->exec(global_pos); } else if (watched == this) { checkSpreadsheetMenu(); m_spreadsheetMenu->exec(global_pos); } return true; } else if (event->type() == QEvent::KeyPress) { auto* key_event = static_cast(event); if (key_event->matches(QKeySequence::Copy)) copySelection(); else if (key_event->matches(QKeySequence::Paste)) pasteIntoSelection(); } return QWidget::eventFilter(watched, event); } /*! * disables cell data relevant actions in the spreadsheet menu if there're no cells available */ void SpreadsheetView::checkSpreadsheetMenu() { const bool cellsAvail = m_spreadsheet->columnCount()>0 && m_spreadsheet->rowCount()>0; m_plotDataMenu->setEnabled(cellsAvail); m_selectionMenu->setEnabled(cellsAvail); action_select_all->setEnabled(cellsAvail); action_clear_spreadsheet->setEnabled(cellsAvail); action_clear_masks->setEnabled(cellsAvail); action_sort_spreadsheet->setEnabled(cellsAvail); action_go_to_cell->setEnabled(cellsAvail); action_statistics_all_columns->setEnabled(cellsAvail); } bool SpreadsheetView::formulaModeActive() const { return m_model->formulaModeActive(); } void SpreadsheetView::activateFormulaMode(bool on) { m_model->activateFormulaMode(on); } void SpreadsheetView::goToNextColumn() { if (m_spreadsheet->columnCount() == 0) return; QModelIndex idx = m_tableView->currentIndex(); int col = idx.column()+1; if (col >= m_spreadsheet->columnCount()) col = 0; m_tableView->setCurrentIndex(idx.sibling(idx.row(), col)); } void SpreadsheetView::goToPreviousColumn() { if (m_spreadsheet->columnCount() == 0) return; QModelIndex idx = m_tableView->currentIndex(); int col = idx.column()-1; if (col < 0) col = m_spreadsheet->columnCount()-1; m_tableView->setCurrentIndex(idx.sibling(idx.row(), col)); } void SpreadsheetView::cutSelection() { int first = firstSelectedRow(); if ( first < 0 ) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: cut selected cells", m_spreadsheet->name())); copySelection(); clearSelectedCells(); m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::copySelection() { PERFTRACE("copy selected cells"); const int first_col = firstSelectedColumn(); if (first_col == -1) return; const int last_col = lastSelectedColumn(); if (last_col == -2) return; const int first_row = firstSelectedRow(); if (first_row == -1) return; const int last_row = lastSelectedRow(); if (last_row == -2) return; const int cols = last_col - first_col + 1; const int rows = last_row - first_row + 1; WAIT_CURSOR; QString output_str; QVector columns; QVector formats; for (int c = 0; c < cols; c++) { Column* col = m_spreadsheet->column(first_col + c); columns << col; const Double2StringFilter* out_fltr = static_cast(col->outputFilter()); formats << out_fltr->numericFormat(); } QLocale locale; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { const Column* col_ptr = columns.at(c); if (isCellSelected(first_row + r, first_col + c)) { // if (formulaModeActive()) // output_str += col_ptr->formula(first_row + r); // else if (col_ptr->columnMode() == AbstractColumn::Numeric) output_str += locale.toString(col_ptr->valueAt(first_row + r), formats.at(c), 16); // copy with max. precision else output_str += col_ptr->asStringColumn()->textAt(first_row + r); } if (c < cols-1) output_str += '\t'; } if (r < rows-1) output_str += '\n'; } QApplication::clipboard()->setText(output_str); RESET_CURSOR; } void SpreadsheetView::pasteIntoSelection() { if (m_spreadsheet->columnCount() < 1 || m_spreadsheet->rowCount() < 1) return; const QMimeData* mime_data = QApplication::clipboard()->mimeData(); if (!mime_data->hasFormat("text/plain")) return; PERFTRACE("paste selected cells"); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: paste from clipboard", m_spreadsheet->name())); int first_col = firstSelectedColumn(); int last_col = lastSelectedColumn(); int first_row = firstSelectedRow(); int last_row = lastSelectedRow(); int input_row_count = 0; int input_col_count = 0; QString input_str = QString(mime_data->data("text/plain")).trimmed(); QVector cellTexts; QStringList input_rows(input_str.split(QLatin1Char('\n'))); input_row_count = input_rows.count(); input_col_count = 0; for (int i = 0; i < input_row_count; i++) { cellTexts.append(input_rows.at(i).split(QRegExp(QLatin1String("\\s+")))); if (cellTexts.at(i).count() > input_col_count) input_col_count = cellTexts.at(i).count(); } + //independent of the current default locale, set the locale to C + //if we have point as the decimal separator in the text to be copied + QLocale::Language lang = QLocale::AnyLanguage; + if (input_str.indexOf(QChar('.')) != -1) + lang = QLocale::C; + QLocale locale(lang); + if ( (first_col == -1 || first_row == -1) || (last_row == first_row && last_col == first_col) ) { // if there is no selection or only one cell selected, the // selection will be expanded to the needed size from the current cell int current_row, current_col; getCurrentCell(¤t_row, ¤t_col); if (current_row == -1) current_row = 0; if (current_col == -1) current_col = 0; setCellSelected(current_row, current_col); first_col = current_col; first_row = current_row; last_row = first_row + input_row_count -1; last_col = first_col + input_col_count -1; //add columns if necessary const int columnCount = m_spreadsheet->columnCount(); if (last_col >= columnCount) { for (int c = 0; c < last_col - (columnCount - 1); ++c) { const int curCol = columnCount - 1 + c; //first non-empty value in the column to paste determines the column mode/type of the new column to be added QString nonEmptyValue; for (auto r : cellTexts) { if (!r.at(curCol).isEmpty()) { nonEmptyValue = r.at(curCol); break; } } const AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(nonEmptyValue, - QLatin1String("yyyy-dd-MM hh:mm:ss:zzz"), QLocale::AnyLanguage); + QLatin1String("yyyy-dd-MM hh:mm:ss:zzz"), lang); Column* new_col = new Column(QString::number(curCol), mode); new_col->setPlotDesignation(AbstractColumn::Y); new_col->insertRows(0, m_spreadsheet->rowCount()); m_spreadsheet->addChild(new_col); } } //add rows if necessary if (last_row >= m_spreadsheet->rowCount()) m_spreadsheet->appendRows(last_row + 1 - m_spreadsheet->rowCount()); // select the rectangle to be pasted in setCellsSelected(first_row, first_col, last_row, last_col); } const int rows = last_row - first_row + 1; const int cols = last_col - first_col + 1; - QLocale locale; for (int c = 0; c < cols && c < input_col_count; c++) { Column* col = m_spreadsheet->column(first_col + c); col->setSuppressDataChangedSignal(true); if (col->columnMode() == AbstractColumn::Numeric) { if (rows == m_spreadsheet->rowCount() && rows == cellTexts.size()) { QVector new_data(rows); for (int r = 0; r < rows; ++r) new_data[r] = locale.toDouble(cellTexts.at(r).at(c)); col->replaceValues(0, new_data); } else { for (int r = 0; r < rows && r < input_row_count; r++) { if ( isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count()) ) { if (!cellTexts.at(r).at(c).isEmpty()) col->setValueAt(first_row + r, locale.toDouble(cellTexts.at(r).at(c))); else col->setValueAt(first_row + r, NAN); } } } } else { for (int r = 0; r < rows && r < input_row_count; r++) { if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count()) ) { // if (formulaModeActive()) // col->setFormula(first_row + r, cellTexts.at(r).at(c)); // else col->asStringColumn()->setTextAt(first_row + r, cellTexts.at(r).at(c)); } } } col->setSuppressDataChangedSignal(false); col->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::maskSelection() { int first = firstSelectedRow(); if (first < 0) return; int last = lastSelectedRow(); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: mask selected cells", m_spreadsheet->name())); for (auto* column : selectedColumns()) { int col = m_spreadsheet->indexOfChild(column); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) column->setMasked(row); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::unmaskSelection() { int first = firstSelectedRow(); if (first < 0) return; int last = lastSelectedRow(); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: unmask selected cells", m_spreadsheet->name())); for (auto* column : selectedColumns()) { int col = m_spreadsheet->indexOfChild(column); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) column->setMasked(row, false); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::plotData() { const QAction* action = dynamic_cast(QObject::sender()); PlotDataDialog::PlotType type = PlotDataDialog::PlotXYCurve; if (action == action_plot_data_xycurve || action == action_plot_data_histogram) type = (PlotDataDialog::PlotType)action->data().toInt(); auto* dlg = new PlotDataDialog(m_spreadsheet, type); if (action != action_plot_data_xycurve && action != action_plot_data_histogram) { PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); dlg->setAnalysisAction(type); } dlg->exec(); } void SpreadsheetView::fillSelectedCellsWithRowNumbers() { if (selectedColumnCount() < 1) return; int first = firstSelectedRow(); if (first < 0) return; int last = lastSelectedRow(); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: fill cells with row numbers", m_spreadsheet->name())); for (auto* col_ptr : selectedColumns()) { int col = m_spreadsheet->indexOfChild(col_ptr); col_ptr->setSuppressDataChangedSignal(true); switch (col_ptr->columnMode()) { case AbstractColumn::Numeric: { QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = row + 1; else results[row-first] = col_ptr->valueAt(row); col_ptr->replaceValues(first, results); break; } case AbstractColumn::Integer: { QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = row + 1; else results[row-first] = col_ptr->integerAt(row); col_ptr->replaceInteger(first, results); break; } case AbstractColumn::Text: { QVector results; for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results << QString::number(row+1); else results << col_ptr->textAt(row); col_ptr->replaceTexts(first, results); break; } //TODO: handle other modes case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; } col_ptr->setSuppressDataChangedSignal(false); col_ptr->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::fillWithRowNumbers() { if (selectedColumnCount() < 1) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: fill column with row numbers", "%1: fill columns with row numbers", m_spreadsheet->name(), selectedColumnCount())); const int rows = m_spreadsheet->rowCount(); QVector int_data(rows); for (int i = 0; i < rows; ++i) int_data[i] = i + 1; for (auto* col : selectedColumns()) { switch (col->columnMode()) { case AbstractColumn::Numeric: col->setColumnMode(AbstractColumn::Integer); col->replaceInteger(0, int_data); break; case AbstractColumn::Integer: col->replaceInteger(0, int_data); break; case AbstractColumn::Text: case AbstractColumn::DateTime: case AbstractColumn::Day: case AbstractColumn::Month: break; } } m_spreadsheet->endMacro(); RESET_CURSOR; } //TODO: this function is not used currently. void SpreadsheetView::fillSelectedCellsWithRandomNumbers() { if (selectedColumnCount() < 1) return; int first = firstSelectedRow(); int last = lastSelectedRow(); if (first < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: fill cells with random values", m_spreadsheet->name())); qsrand(QTime::currentTime().msec()); for (auto* col_ptr : selectedColumns()) { int col = m_spreadsheet->indexOfChild(col_ptr); col_ptr->setSuppressDataChangedSignal(true); switch (col_ptr->columnMode()) { case AbstractColumn::Numeric: { QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = double(qrand())/double(RAND_MAX); else results[row-first] = col_ptr->valueAt(row); col_ptr->replaceValues(first, results); break; } case AbstractColumn::Integer: { QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = qrand(); else results[row-first] = col_ptr->integerAt(row); col_ptr->replaceInteger(first, results); break; } case AbstractColumn::Text: { QVector results; for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results << QString::number(double(qrand())/double(RAND_MAX)); else results << col_ptr->textAt(row); col_ptr->replaceTexts(first, results); break; } case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: { QVector results; QDate earliestDate(1, 1, 1); QDate latestDate(2999, 12, 31); QTime midnight(0, 0, 0, 0); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results << QDateTime( earliestDate.addDays(((double)qrand())*((double)earliestDate.daysTo(latestDate))/((double)RAND_MAX)), midnight.addMSecs(((qint64)qrand())*1000*60*60*24/RAND_MAX)); else results << col_ptr->dateTimeAt(row); col_ptr->replaceDateTimes(first, results); break; } } col_ptr->setSuppressDataChangedSignal(false); col_ptr->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::fillWithRandomValues() { if (selectedColumnCount() < 1) return; auto* dlg = new RandomValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::fillWithEquidistantValues() { if (selectedColumnCount() < 1) return; auto* dlg = new EquidistantValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::fillWithFunctionValues() { if (selectedColumnCount() < 1) return; auto* dlg = new FunctionValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::fillSelectedCellsWithConstValues() { if (selectedColumnCount() < 1) return; int first = firstSelectedRow(); int last = lastSelectedRow(); if (first < 0) return; bool doubleOk = false; bool intOk = false; bool stringOk = false; double doubleValue = 0; int intValue = 0; QString stringValue; m_spreadsheet->beginMacro(i18n("%1: fill cells with const values", m_spreadsheet->name())); for (auto* col_ptr : selectedColumns()) { int col = m_spreadsheet->indexOfChild(col_ptr); col_ptr->setSuppressDataChangedSignal(true); switch (col_ptr->columnMode()) { case AbstractColumn::Numeric: if (!doubleOk) doubleValue = QInputDialog::getDouble(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 6, &doubleOk); if (doubleOk) { WAIT_CURSOR; QVector results(last-first+1); for (int row = first; row <= last; row++) { if (isCellSelected(row, col)) results[row-first] = doubleValue; else results[row-first] = col_ptr->valueAt(row); } col_ptr->replaceValues(first, results); RESET_CURSOR; } break; case AbstractColumn::Integer: if (!intOk) intValue = QInputDialog::getInt(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 1, &intOk); if (intOk) { WAIT_CURSOR; QVector results(last-first+1); for (int row = first; row <= last; row++) { if (isCellSelected(row, col)) results[row-first] = intValue; else results[row-first] = col_ptr->integerAt(row); } col_ptr->replaceInteger(first, results); RESET_CURSOR; } break; case AbstractColumn::Text: if (!stringOk) stringValue = QInputDialog::getText(this, i18n("Fill the selection with constant value"), i18n("Value"), QLineEdit::Normal, nullptr, &stringOk); if (stringOk && !stringValue.isEmpty()) { WAIT_CURSOR; QVector results; for (int row = first; row <= last; row++) { if (isCellSelected(row, col)) results << stringValue; else results << col_ptr->textAt(row); } col_ptr->replaceTexts(first, results); RESET_CURSOR; } break; //TODO: handle other modes case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; } col_ptr->setSuppressDataChangedSignal(false); col_ptr->setChanged(); } m_spreadsheet->endMacro(); } /*! Open the sort dialog for all columns. */ void SpreadsheetView::sortSpreadsheet() { sortDialog(m_spreadsheet->children()); } /*! Insert an empty column left to the firt selected column */ void SpreadsheetView::insertColumnLeft() { insertColumnsLeft(1); } /*! Insert multiple empty columns left to the firt selected column */ void SpreadsheetView::insertColumnsLeft() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert empty columns"), i18n("Enter the number of columns to insert"), 1/*value*/, 1/*min*/, 1000/*max*/, 1/*step*/, &ok); if (!ok) return; insertColumnsLeft(count); } /*! * private helper function doing the actual insertion of columns to the left */ void SpreadsheetView::insertColumnsLeft(int count) { WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty column", "%1: insert empty columns", m_spreadsheet->name(), count )); const int first = firstSelectedColumn(); if (first >= 0) { //determine the first selected column Column* firstCol = m_spreadsheet->child(first); for (int i = 0; i < count; ++i) { Column* newCol = new Column(QString::number(i + 1), AbstractColumn::Numeric); newCol->setPlotDesignation(AbstractColumn::Y); //resize the new column and insert it before the first selected column newCol->insertRows(0, m_spreadsheet->rowCount()); m_spreadsheet->insertChildBefore(newCol, firstCol); } } else { if (m_spreadsheet->columnCount()>0) { //columns available but no columns selected -> prepend the new column at the very beginning Column* firstCol = m_spreadsheet->child(0); for (int i = 0; i < count; ++i) { Column* newCol = new Column(QString::number(i + 1), AbstractColumn::Numeric); newCol->setPlotDesignation(AbstractColumn::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); m_spreadsheet->insertChildBefore(newCol, firstCol); } } else { //no columns available anymore -> resize the spreadsheet and the new column to the default size KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet")); const int rows = group.readEntry(QLatin1String("RowCount"), 100); m_spreadsheet->setRowCount(rows); for (int i = 0; i < count; ++i) { Column* newCol = new Column(QString::number(i + 1), AbstractColumn::Numeric); (i == 0) ? newCol->setPlotDesignation(AbstractColumn::X) : newCol->setPlotDesignation(AbstractColumn::Y); newCol->insertRows(0, rows); //add/append a new column m_spreadsheet->addChild(newCol); } } } m_spreadsheet->endMacro(); RESET_CURSOR; } /*! Insert an empty column right to the last selected column */ void SpreadsheetView::insertColumnRight() { insertColumnsRight(1); } /*! Insert multiple empty columns right to the last selected column */ void SpreadsheetView::insertColumnsRight() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert empty columns"), i18n("Enter the number of columns to insert"), 1/*value*/, 1/*min*/, 1000/*max*/, 1/*step*/, &ok); if (!ok) return; insertColumnsRight(count); } /*! * private helper function doing the actual insertion of columns to the right */ void SpreadsheetView::insertColumnsRight(int count) { WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty column", "%1: insert empty columns", m_spreadsheet->name(), count )); const int last = lastSelectedColumn(); if (last >= 0) { if (last < m_spreadsheet->columnCount() - 1) { //determine the column next to the last selected column Column* nextCol = m_spreadsheet->child(last + 1); for (int i = 0; i < count; ++i) { Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); newCol->setPlotDesignation(AbstractColumn::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); //insert the new column before the column next to the last selected column m_spreadsheet->insertChildBefore(newCol, nextCol); } } else { for (int i = 0; i < count; ++i) { Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); newCol->setPlotDesignation(AbstractColumn::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); //last column selected, no next column available -> add/append a new column m_spreadsheet->addChild(newCol); } } } else { if (m_spreadsheet->columnCount()>0) { for (int i = 0; i < count; ++i) { Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); newCol->setPlotDesignation(AbstractColumn::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); //columns available but no columns selected -> append the new column at the very end m_spreadsheet->addChild(newCol); } } else { //no columns available anymore -> resize the spreadsheet and the new column to the default size KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet")); const int rows = group.readEntry(QLatin1String("RowCount"), 100); m_spreadsheet->setRowCount(rows); for (int i = 0; i < count; ++i) { Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); (i == 0) ? newCol->setPlotDesignation(AbstractColumn::X) : newCol->setPlotDesignation(AbstractColumn::Y); newCol->insertRows(0, rows); //add/append a new column m_spreadsheet->addChild(newCol); } } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::removeSelectedColumns() { WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: remove selected columns", m_spreadsheet->name())); for (auto* column : selectedColumns()) m_spreadsheet->removeChild(column); m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::clearSelectedColumns() { WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: clear selected columns", m_spreadsheet->name())); if (formulaModeActive()) { for (auto* col : selectedColumns()) { col->setSuppressDataChangedSignal(true); col->clearFormulas(); col->setSuppressDataChangedSignal(false); col->setChanged(); } } else { for (auto* col : selectedColumns()) { col->setSuppressDataChangedSignal(true); col->clear(); col->setSuppressDataChangedSignal(false); col->setChanged(); } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::setSelectionAs() { QVector columns = selectedColumns(); if (!columns.size()) return; m_spreadsheet->beginMacro(i18n("%1: set plot designation", m_spreadsheet->name())); QAction* action = dynamic_cast(QObject::sender()); if (!action) return; AbstractColumn::PlotDesignation pd = (AbstractColumn::PlotDesignation)action->data().toInt(); for (auto* col : columns) col->setPlotDesignation(pd); m_spreadsheet->endMacro(); } /*! * add, subtract, multiply, divide */ void SpreadsheetView::modifyValues() { if (selectedColumnCount() < 1) return; const QAction* action = dynamic_cast(QObject::sender()); AddSubtractValueDialog::Operation op = (AddSubtractValueDialog::Operation)action->data().toInt(); auto* dlg = new AddSubtractValueDialog(m_spreadsheet, op); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::reverseColumns() { WAIT_CURSOR; QVector cols = selectedColumns(); m_spreadsheet->beginMacro(i18np("%1: reverse column", "%1: reverse columns", m_spreadsheet->name(), cols.size())); for (auto* col : cols) { if (col->columnMode() != AbstractColumn::Numeric) continue; auto* data = static_cast* >(col->data()); QVector new_data(*data); std::reverse(new_data.begin(), new_data.end()); col->replaceValues(0, new_data); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::dropColumnValues() { if (selectedColumnCount() < 1) return; auto* dlg = new DropValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::maskColumnValues() { if (selectedColumnCount() < 1) return; auto* dlg = new DropValuesDialog(m_spreadsheet, true); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::joinColumns() { //TODO } void SpreadsheetView::normalizeSelectedColumns() { WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: normalize columns", m_spreadsheet->name())); for (auto* col : selectedColumns()) { if (col->columnMode() == AbstractColumn::Numeric) { col->setSuppressDataChangedSignal(true); double max = col->maximum(); if (max != 0.0) {// avoid division by zero for (int row = 0; row < col->rowCount(); row++) col->setValueAt(row, col->valueAt(row) / max); } col->setSuppressDataChangedSignal(false); col->setChanged(); } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::normalizeSelection() { WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: normalize selection", m_spreadsheet->name())); double max = 0.0; for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++) if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::Numeric) for (int row = 0; row < m_spreadsheet->rowCount(); row++) { if (isCellSelected(row, col) && m_spreadsheet->column(col)->valueAt(row) > max) max = m_spreadsheet->column(col)->valueAt(row); } if (max != 0.0) { // avoid division by zero //TODO setSuppressDataChangedSignal for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++) if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::Numeric) for (int row = 0; row < m_spreadsheet->rowCount(); row++) { if (isCellSelected(row, col)) m_spreadsheet->column(col)->setValueAt(row, m_spreadsheet->column(col)->valueAt(row) / max); } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::sortSelectedColumns() { sortDialog(selectedColumns()); } void SpreadsheetView::showAllColumnsStatistics() { showColumnStatistics(true); } void SpreadsheetView::showColumnStatistics(bool forAll) { QString dlgTitle(m_spreadsheet->name() + " column statistics"); auto* dlg = new StatisticsDialog(dlgTitle); QVector columns; if (!forAll) dlg->setColumns(selectedColumns()); else if (forAll) { for (int col = 0; col < m_spreadsheet->columnCount(); ++col) { if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::Numeric) columns << m_spreadsheet->column(col); } dlg->setColumns(columns); } if (dlg->exec() == QDialog::Accepted) { if (forAll) columns.clear(); } } void SpreadsheetView::showRowStatistics() { QString dlgTitle(m_spreadsheet->name() + " row statistics"); auto* dlg = new StatisticsDialog(dlgTitle); QVector columns; for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { if (isRowSelected(i)) { QVector rowValues; for (int j = 0; j < m_spreadsheet->columnCount(); ++j) rowValues << m_spreadsheet->column(j)->valueAt(i); columns << new Column(QString::number(i+1), rowValues); } } dlg->setColumns(columns); if (dlg->exec() == QDialog::Accepted) { qDeleteAll(columns); columns.clear(); } } /*! Insert an empty row above(=before) the first selected row */ void SpreadsheetView::insertRowAbove() { insertRowsAbove(1); } /*! Insert multiple empty rows above(=before) the first selected row */ void SpreadsheetView::insertRowsAbove() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert multiple rows"), i18n("Enter the number of rows to insert"), 1/*value*/, 1/*min*/, 1000000/*max*/, 1/*step*/, &ok); if (ok) insertRowsAbove(count); } /*! * private helper function doing the actual insertion of rows above */ void SpreadsheetView::insertRowsAbove(int count) { int first = firstSelectedRow(); if (first < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count )); m_spreadsheet->insertRows(first, count); m_spreadsheet->endMacro(); RESET_CURSOR; } /*! Insert an empty row below the last selected row */ void SpreadsheetView::insertRowBelow() { insertRowsBelow(1); } /*! Insert an empty row below the last selected row */ void SpreadsheetView::insertRowsBelow() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert multiple rows"), i18n("Enter the number of rows to insert"), 1/*value*/, 1/*min*/, 1000000/*max*/, 1/*step*/, &ok); if (ok) insertRowsBelow(count); } /*! * private helper function doing the actual insertion of rows below */ void SpreadsheetView::insertRowsBelow(int count) { int last = lastSelectedRow(); if (last < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count )); if (last < m_spreadsheet->rowCount() - 1) m_spreadsheet->insertRows(last + 1, count); //insert before the next to the last selected row else m_spreadsheet->appendRows(count); //append new rows at the end m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::removeSelectedRows() { if (firstSelectedRow() < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: remove selected rows", m_spreadsheet->name())); //TODO setSuppressDataChangedSignal for (const auto& i : selectedRows().intervals()) m_spreadsheet->removeRows(i.start(), i.size()); m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::clearSelectedRows() { if (firstSelectedRow() < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: clear selected rows", m_spreadsheet->name())); for (auto* col : selectedColumns()) { col->setSuppressDataChangedSignal(true); if (formulaModeActive()) { for (const auto& i : selectedRows().intervals()) col->setFormula(i, QString()); } else { for (const auto& i : selectedRows().intervals()) { if (i.end() == col->rowCount()-1) col->removeRows(i.start(), i.size()); else { QVector empties; for (int j = 0; j < i.size(); j++) empties << QString(); col->asStringColumn()->replaceTexts(i.start(), empties); } } } col->setSuppressDataChangedSignal(false); col->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; //selected rows were deleted but the view selection is still in place -> reset the selection in the view m_tableView->clearSelection(); } void SpreadsheetView::clearSelectedCells() { int first = firstSelectedRow(); int last = lastSelectedRow(); if (first < 0) return; + //don't try to clear values if the selected cells don't have any values at all + bool empty = true; + for (auto* column : selectedColumns()) { + for (int row = last; row >= first; row--) { + if (column->isValid(row)) { + empty = false; + break; + } + } + if (!empty) + break; + } + + if (empty) + return; + WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: clear selected cells", m_spreadsheet->name())); for (auto* column : selectedColumns()) { column->setSuppressDataChangedSignal(true); if (formulaModeActive()) { int col = m_spreadsheet->indexOfChild(column); for (int row = last; row >= first; row--) if (isCellSelected(row, col)) column->setFormula(row, QString()); } else { int col = m_spreadsheet->indexOfChild(column); for (int row = last; row >= first; row--) if (isCellSelected(row, col)) { if (row < column->rowCount()) column->asStringColumn()->setTextAt(row, QString()); } } column->setSuppressDataChangedSignal(false); column->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::goToCell() { bool ok; int col = QInputDialog::getInt(nullptr, i18n("Go to Cell"), i18n("Enter column"), 1, 1, m_spreadsheet->columnCount(), 1, &ok); if (!ok) return; int row = QInputDialog::getInt(nullptr, i18n("Go to Cell"), i18n("Enter row"), 1, 1, m_spreadsheet->rowCount(), 1, &ok); if (!ok) return; goToCell(row-1, col-1); } //! Open the sort dialog for the given columns void SpreadsheetView::sortDialog(QVector cols) { if (cols.isEmpty()) return; for (auto* col : cols) col->setSuppressDataChangedSignal(true); auto* dlg = new SortDialog(); connect(dlg, SIGNAL(sort(Column*,QVector,bool)), m_spreadsheet, SLOT(sortColumns(Column*,QVector,bool))); dlg->setColumns(cols); int rc = dlg->exec(); for (auto* col : cols) { col->setSuppressDataChangedSignal(false); if (rc == QDialog::Accepted) col->setChanged(); } } void SpreadsheetView::sortColumnAscending() { QVector cols = selectedColumns(); for (auto* col : cols) col->setSuppressDataChangedSignal(true); m_spreadsheet->sortColumns(cols.first(), cols, true); for (auto* col : cols) { col->setSuppressDataChangedSignal(false); col->setChanged(); } } void SpreadsheetView::sortColumnDescending() { QVector cols = selectedColumns(); for (auto* col : cols) col->setSuppressDataChangedSignal(true); m_spreadsheet->sortColumns(cols.first(), cols, false); for (auto* col : cols) { col->setSuppressDataChangedSignal(false); col->setChanged(); } } /*! Cause a repaint of the header. */ void SpreadsheetView::updateHeaderGeometry(Qt::Orientation o, int first, int last) { Q_UNUSED(first) Q_UNUSED(last) //TODO if (o != Qt::Horizontal) return; m_tableView->horizontalHeader()->setStretchLastSection(true); // ugly hack (flaw in Qt? Does anyone know a better way?) m_tableView->horizontalHeader()->updateGeometry(); m_tableView->horizontalHeader()->setStretchLastSection(false); // ugly hack part 2 } /*! selects the column \c column in the speadsheet view . */ void SpreadsheetView::selectColumn(int column) { QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount()-1, column) ); m_suppressSelectionChangedEvent = true; m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); m_suppressSelectionChangedEvent = false; } /*! deselects the column \c column in the speadsheet view . */ void SpreadsheetView::deselectColumn(int column) { QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount()-1, column) ); m_suppressSelectionChangedEvent = true; m_tableView->selectionModel()->select(selection, QItemSelectionModel::Deselect); m_suppressSelectionChangedEvent = false; } /*! called when a column in the speadsheet view was clicked (click in the header). Propagates the selection of the column to the \c Spreadsheet object (a click in the header always selects the column). */ void SpreadsheetView::columnClicked(int column) { m_spreadsheet->setColumnSelectedInView(column, true); } /*! called on selections changes. Propagates the selection/deselection of columns to the \c Spreadsheet object. */ void SpreadsheetView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(selected); Q_UNUSED(deselected); if (m_suppressSelectionChangedEvent) return; QItemSelectionModel* selModel = m_tableView->selectionModel(); for (int i = 0; i < m_spreadsheet->columnCount(); i++) m_spreadsheet->setColumnSelectedInView(i, selModel->isColumnSelected(i, QModelIndex())); } bool SpreadsheetView::exportView() { auto* dlg = new ExportSpreadsheetDialog(this); dlg->setFileName(m_spreadsheet->name()); dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table")); for (int i = 0; i < m_spreadsheet->columnCount(); ++i) { if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::Numeric) { dlg->setExportToImage(false); break; } } if (selectedColumnCount() == 0) dlg->setExportSelection(false); bool ret; if ((ret = dlg->exec()) == QDialog::Accepted) { const QString path = dlg->path(); const bool exportHeader = dlg->exportHeader(); WAIT_CURSOR; switch (dlg->format()) { case ExportSpreadsheetDialog::ASCII: { const QString separator = dlg->separator(); const QLocale::Language format = dlg->numberFormat(); exportToFile(path, exportHeader, separator, format); break; } case ExportSpreadsheetDialog::Binary: break; case ExportSpreadsheetDialog::LaTeX: { const bool exportLatexHeader = dlg->exportLatexHeader(); const bool gridLines = dlg->gridLines(); const bool captions = dlg->captions(); const bool skipEmptyRows = dlg->skipEmptyRows(); const bool exportEntire = dlg->entireSpreadheet(); exportToLaTeX(path, exportHeader, gridLines, captions, exportLatexHeader, skipEmptyRows, exportEntire); break; } case ExportSpreadsheetDialog::FITS: { const int exportTo = dlg->exportToFits(); const bool commentsAsUnits = dlg->commentsAsUnitsFits(); exportToFits(path, exportTo, commentsAsUnits); break; } case ExportSpreadsheetDialog::SQLite: exportToSQLite(path); break; } RESET_CURSOR; } delete dlg; return ret; } bool SpreadsheetView::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, this); dlg->setWindowTitle(i18nc("@title:window", "Print Spreadsheet")); bool ret; if ((ret = dlg->exec()) == QDialog::Accepted) { print(&printer); } delete dlg; return ret; } bool SpreadsheetView::printPreview() { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(this); connect(dlg, &QPrintPreviewDialog::paintRequested, this, &SpreadsheetView::print); return dlg->exec(); } /*! prints the complete spreadsheet to \c printer. */ void SpreadsheetView::print(QPrinter* printer) const { WAIT_CURSOR; QPainter painter (printer); const int dpiy = printer->logicalDpiY(); const int margin = (int) ( (1/2.54)*dpiy ); // 1 cm margins QHeaderView *hHeader = m_tableView->horizontalHeader(); QHeaderView *vHeader = m_tableView->verticalHeader(); const int rows = m_spreadsheet->rowCount(); const int cols = m_spreadsheet->columnCount(); int height = margin; const int vertHeaderWidth = vHeader->width(); int right = margin + vertHeaderWidth; int columnsPerTable = 0; int headerStringWidth = 0; int firstRowStringWidth = 0; bool tablesNeeded = false; for (int col = 0; col < cols; ++col) { headerStringWidth += m_tableView->columnWidth(col); firstRowStringWidth += m_spreadsheet->column(col)->asStringColumn()->textAt(0).length(); if ((headerStringWidth >= printer->pageRect().width() -2*margin) || (firstRowStringWidth >= printer->pageRect().width() - 2*margin)) { tablesNeeded = true; break; } columnsPerTable++; } int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0; const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols; if (!tablesNeeded) { tablesCount = 1; columnsPerTable = cols; } if (remainingColumns > 0) tablesCount++; //Paint the horizontal header first for (int table = 0; table < tablesCount; ++table) { right = margin + vertHeaderWidth; painter.setFont(hHeader->font()); QString headerString = m_tableView->model()->headerData(0, Qt::Horizontal).toString(); QRect br; br = painter.boundingRect(br, Qt::AlignCenter, headerString); QRect tr(br); if (table != 0) height += tr.height(); painter.drawLine(right, height, right, height+br.height()); int i = table * columnsPerTable; int toI = table * columnsPerTable + columnsPerTable; if ((remainingColumns > 0) && (table == tablesCount-1)) { i = (tablesCount-1)*columnsPerTable; toI = (tablesCount-1)* columnsPerTable + remainingColumns; } for (; imodel()->headerData(i, Qt::Horizontal).toString(); const int w = m_tableView->columnWidth(i); tr.setTopLeft(QPoint(right,height)); tr.setWidth(w); tr.setHeight(br.height()); painter.drawText(tr, Qt::AlignCenter, headerString); right += w; painter.drawLine(right, height, right, height+tr.height()); } painter.drawLine(margin + vertHeaderWidth, height, right-1, height);//first horizontal line height += tr.height(); painter.drawLine(margin, height, right-1, height); // print table values QString cellText; for (i = 0; i < rows; ++i) { right = margin; cellText = m_tableView->model()->headerData(i, Qt::Vertical).toString()+'\t'; tr = painter.boundingRect(tr, Qt::AlignCenter, cellText); painter.drawLine(right, height, right, height+tr.height()); br.setTopLeft(QPoint(right,height)); br.setWidth(vertHeaderWidth); br.setHeight(tr.height()); painter.drawText(br, Qt::AlignCenter, cellText); right += vertHeaderWidth; painter.drawLine(right, height, right, height+tr.height()); int j = table * columnsPerTable; int toJ = table * columnsPerTable + columnsPerTable; if ((remainingColumns > 0) && (table == tablesCount-1)) { j = (tablesCount-1)*columnsPerTable; toJ = (tablesCount-1)* columnsPerTable + remainingColumns; } for (; j < toJ; j++) { int w = m_tableView->columnWidth(j); cellText = m_spreadsheet->column(j)->isValid(i) ? m_spreadsheet->text(i,j)+'\t': QLatin1String("- \t"); tr = painter.boundingRect(tr,Qt::AlignCenter,cellText); br.setTopLeft(QPoint(right,height)); br.setWidth(w); br.setHeight(tr.height()); painter.drawText(br, Qt::AlignCenter, cellText); right += w; painter.drawLine(right, height, right, height+tr.height()); } height += br.height(); painter.drawLine(margin, height, right-1, height); if (height >= printer->height() - margin) { printer->newPage(); height = margin; painter.drawLine(margin, height, right, height); } } } RESET_CURSOR; } void SpreadsheetView::registerShortcuts() { action_clear_selection->setShortcut(QKeySequence::Delete); } void SpreadsheetView::unregisterShortcuts() { action_clear_selection->setShortcut(QKeySequence()); } void SpreadsheetView::exportToFile(const QString& path, const bool exportHeader, const QString& separator, QLocale::Language language) const { QFile file(path); if (!file.open(QFile::WriteOnly | QFile::Truncate)) return; PERFTRACE("export spreadsheet to file"); QTextStream out(&file); const int cols = m_spreadsheet->columnCount(); QString sep = separator; sep = sep.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive); sep = sep.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); //export header (column names) if (exportHeader) { for (int j = 0; j < cols; ++j) { out << m_spreadsheet->column(j)->name(); if (j != cols - 1) out << sep; } out << '\n'; } //export values QLocale locale(language); for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { for (int j = 0; j < cols; ++j) { Column* col = m_spreadsheet->column(j); if (col->columnMode() == AbstractColumn::Numeric) { const Double2StringFilter* out_fltr = static_cast(col->outputFilter()); out << locale.toString(col->valueAt(i), out_fltr->numericFormat(), 16); // export with max. precision } else out << col->asStringColumn()->textAt(i); if (j != cols - 1) out << sep; } out << '\n'; } } void SpreadsheetView::exportToLaTeX(const QString & path, const bool exportHeaders, const bool gridLines, const bool captions, const bool latexHeaders, const bool skipEmptyRows, const bool exportEntire) const { QFile file(path); if (!file.open(QFile::WriteOnly | QFile::Truncate)) return; QList toExport; int cols; int totalRowCount = 0; if (exportEntire) { cols = const_cast(this)->m_spreadsheet->columnCount(); totalRowCount = m_spreadsheet->rowCount(); for (int col = 0; col < cols; ++col) toExport << m_spreadsheet->column(col); } else { cols = const_cast(this)->selectedColumnCount(); const int firtsSelectedCol = const_cast(this)->firstSelectedColumn(); bool rowsCalculated = false; for (int col = firtsSelectedCol; col < firtsSelectedCol + cols; ++col) { QVector textData; for (int row = 0; row < m_spreadsheet->rowCount(); ++row) { if (const_cast(this)->isRowSelected(row)) { textData << m_spreadsheet->column(col)->asStringColumn()->textAt(row); if (!rowsCalculated) totalRowCount++; } } if (!rowsCalculated) rowsCalculated = true; Column* column = new Column(m_spreadsheet->column(col)->name(), textData); toExport << column; } } int columnsStringSize = 0; int columnsPerTable = 0; for (int i = 0; i < cols; ++i) { int maxSize = -1; for (int j = 0; j < toExport.at(i)->asStringColumn()->rowCount(); ++j) { if (toExport.at(i)->asStringColumn()->textAt(j).size() > maxSize) maxSize = toExport.at(i)->asStringColumn()->textAt(j).size(); } columnsStringSize += maxSize; if (!toExport.at(i)->isValid(0)) columnsStringSize += 3; if (columnsStringSize > 65) break; ++columnsPerTable; } const int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0; const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols; bool columnsSeparating = (cols > columnsPerTable); QTextStream out(&file); QProcess tex; tex.start("latex", QStringList() << "--version", QProcess::ReadOnly); tex.waitForFinished(500); QString texVersionOutput = QString(tex.readAllStandardOutput()); texVersionOutput = texVersionOutput.split('\n')[0]; int yearidx = -1; for (int i = texVersionOutput.size() - 1; i >= 0; --i) { if (texVersionOutput.at(i) == QChar('2')) { yearidx = i; break; } } if (texVersionOutput.at(yearidx+1) == QChar('/')) yearidx -= 3; bool ok; texVersionOutput.midRef(yearidx, 4).toInt(&ok); int version = -1; if (ok) version = texVersionOutput.midRef(yearidx, 4).toInt(&ok); if (latexHeaders) { out << QLatin1String("\\documentclass[11pt,a4paper]{article} \n"); out << QLatin1String("\\usepackage{geometry} \n"); out << QLatin1String("\\usepackage{xcolor,colortbl} \n"); if (version >= 2015) out << QLatin1String("\\extrafloats{1280} \n"); out << QLatin1String("\\definecolor{HeaderBgColor}{rgb}{0.81,0.81,0.81} \n"); out << QLatin1String("\\geometry{ \n"); out << QLatin1String("a4paper, \n"); out << QLatin1String("total={170mm,257mm}, \n"); out << QLatin1String("left=10mm, \n"); out << QLatin1String("top=10mm } \n"); out << QLatin1String("\\begin{document} \n"); out << QLatin1String("\\title{LabPlot Spreadsheet Export to \\LaTeX{} } \n"); out << QLatin1String("\\author{LabPlot} \n"); out << QLatin1String("\\date{\\today} \n"); } QString endTabularTable ("\\end{tabular} \n \\end{table} \n"); QString tableCaption ("\\caption{"+ m_spreadsheet->name()+ "} \n"); QString beginTable ("\\begin{table}[ht] \n"); int rowCount = 0; const int maxRows = 45; bool captionRemoved = false; if (columnsSeparating) { QVector emptyRowIndices; for (int table = 0; table < tablesCount; ++table) { QStringList textable; captionRemoved = false; textable << beginTable; if (captions) textable << tableCaption; textable << QLatin1String("\\centering \n"); textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); for (int i = 0; i < columnsPerTable; ++i) textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); textable << QLatin1String("} \n"); if (gridLines) textable << QLatin1String("\\hline \n"); if (exportHeaders) { if (latexHeaders) textable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) { textable << toExport.at(col)->name(); if (col != ((table * columnsPerTable)+ columnsPerTable)-1) textable << QLatin1String(" & "); } textable << QLatin1String("\\\\ \n"); if (gridLines) textable << QLatin1String("\\hline \n"); } for (const auto& s : textable) out << s; QStringList values; for (int row = 0; row < totalRowCount; ++row) { values.clear(); bool notEmpty = false; for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col ) { if (toExport.at(col)->isValid(row)) { notEmpty = true; values << toExport.at(col)->asStringColumn()->textAt(row); } else values << QLatin1String("-"); if (col != ((table * columnsPerTable)+ columnsPerTable)-1) values << QLatin1String(" & "); } if (!notEmpty && skipEmptyRows) { if (!emptyRowIndices.contains(row)) emptyRowIndices << row; } if (emptyRowIndices.contains(row) && notEmpty) emptyRowIndices.remove(emptyRowIndices.indexOf(row)); if (notEmpty || !skipEmptyRows) { for (const auto& s : values) out << s; out << QLatin1String("\\\\ \n"); if (gridLines) out << QLatin1String("\\hline \n"); rowCount++; if (rowCount == maxRows) { out << endTabularTable; out << QLatin1String("\\newpage \n"); if (captions) if (!captionRemoved) textable.removeAt(1); for (const auto& s : textable) out << s; rowCount = 0; if (!captionRemoved) captionRemoved = true; } } } out << endTabularTable; } //new table for the remaining columns QStringList remainingTable; remainingTable << beginTable; if (captions) remainingTable << tableCaption; remainingTable << QLatin1String("\\centering \n"); remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); for (int c = 0; c < remainingColumns; ++c) remainingTable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); remainingTable << QLatin1String("} \n"); if (gridLines) remainingTable << QLatin1String("\\hline \n"); if (exportHeaders) { if (latexHeaders) remainingTable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); for (int col = 0; col < remainingColumns; ++col) { remainingTable << toExport.at(col + (tablesCount * columnsPerTable))->name(); if (col != remainingColumns-1) remainingTable << QLatin1String(" & "); } remainingTable << QLatin1String("\\\\ \n"); if (gridLines) remainingTable << QLatin1String("\\hline \n"); } for (const auto& s : remainingTable) out << s; QStringList values; captionRemoved = false; for (int row = 0; row < totalRowCount; ++row) { values.clear(); bool notEmpty = false; for (int col = 0; col < remainingColumns; ++col ) { if (toExport.at(col + (tablesCount * columnsPerTable))->isValid(row)) { notEmpty = true; values << toExport.at(col + (tablesCount * columnsPerTable))->asStringColumn()->textAt(row); } else values << QLatin1String("-"); if (col != remainingColumns-1) values << QLatin1String(" & "); } if (!emptyRowIndices.contains(row) && !notEmpty) notEmpty = true; if (notEmpty || !skipEmptyRows) { for (const auto& s : values) out << s; out << QLatin1String("\\\\ \n"); if (gridLines) out << QLatin1String("\\hline \n"); rowCount++; if (rowCount == maxRows) { out << endTabularTable; out << QLatin1String("\\pagebreak[4] \n"); if (captions) if (!captionRemoved) remainingTable.removeAt(1); for (const auto& s : remainingTable) out << s; rowCount = 0; if (!captionRemoved) captionRemoved = true; } } } out << endTabularTable; } else { QStringList textable; textable << beginTable; if (captions) textable << tableCaption; textable << QLatin1String("\\centering \n"); textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); for (int c = 0; c < cols; ++c) textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); textable << QLatin1String("} \n"); if (gridLines) textable << QLatin1String("\\hline \n"); if (exportHeaders) { if (latexHeaders) textable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); for (int col = 0; col < cols; ++col) { textable << toExport.at(col)->name(); if (col != cols-1) textable << QLatin1String(" & "); } textable << QLatin1String("\\\\ \n"); if (gridLines) textable << QLatin1String("\\hline \n"); } for (const auto& s : textable) out << s; QStringList values; captionRemoved = false; for (int row = 0; row < totalRowCount; ++row) { values.clear(); bool notEmpty = false; for (int col = 0; col < cols; ++col ) { if (toExport.at(col)->isValid(row)) { notEmpty = true; values << toExport.at(col)->asStringColumn()->textAt(row); } else values << "-"; if (col != cols-1) values << " & "; } if (notEmpty || !skipEmptyRows) { for (const auto& s : values) out << s; out << QLatin1String("\\\\ \n"); if (gridLines) out << QLatin1String("\\hline \n"); rowCount++; if (rowCount == maxRows) { out << endTabularTable; out << QLatin1String("\\newpage \n"); if (captions) if (!captionRemoved) textable.removeAt(1); for (const auto& s : textable) out << s; rowCount = 0; if (!captionRemoved) captionRemoved = true; } } } out << endTabularTable; } if (latexHeaders) out << QLatin1String("\\end{document} \n"); if (!exportEntire) { qDeleteAll(toExport); toExport.clear(); } else toExport.clear(); } void SpreadsheetView::exportToFits(const QString &fileName, const int exportTo, const bool commentsAsUnits) const { auto* filter = new FITSFilter; filter->setExportTo(exportTo); filter->setCommentsAsUnits(commentsAsUnits); filter->write(fileName, m_spreadsheet); delete filter; } void SpreadsheetView::exportToSQLite(const QString& path) const { QFile file(path); if (!file.open(QFile::WriteOnly | QFile::Truncate)) return; PERFTRACE("export spreadsheet to SQLite database"); QApplication::processEvents(QEventLoop::AllEvents, 0); //create database const QStringList& drivers = QSqlDatabase::drivers(); QString driver; if (drivers.contains(QLatin1String("QSQLITE3"))) driver = QLatin1String("QSQLITE3"); else driver = QLatin1String("QSQLITE"); QSqlDatabase db = QSqlDatabase::addDatabase(driver); db.setDatabaseName(path); if (!db.open()) { RESET_CURSOR; KMessageBox::error(nullptr, i18n("Couldn't create the SQLite database %1.", path)); } //create table const int cols = m_spreadsheet->columnCount(); QString query = QLatin1String("create table ") + m_spreadsheet->name() + QLatin1String(" ("); for (int i = 0; i < cols; ++i) { Column* col = m_spreadsheet->column(i); if (i != 0) query += QLatin1String(", "); query += QLatin1String("\"") + col->name() + QLatin1String("\" "); switch (col->columnMode()) { case AbstractColumn::Numeric: query += QLatin1String("REAL"); break; case AbstractColumn::Integer: query += QLatin1String("INTEGER"); break; case AbstractColumn::Text: case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: query += QLatin1String("TEXT"); break; } } query += QLatin1Char(')'); QSqlQuery q; if (!q.exec(query)) { RESET_CURSOR; KMessageBox::error(nullptr, i18n("Failed to create table in the SQLite database %1.", path) + "\n" + q.lastError().databaseText()); db.close(); return; } //create bulk insert statement { PERFTRACE("Create the bulk insert statement"); q.exec(QLatin1String("BEGIN TRANSACTION;")); query = "INSERT INTO '" + m_spreadsheet->name() + "' ("; for (int i = 0; i < cols; ++i) { if (i != 0) query += QLatin1String(", "); query += QLatin1Char('\'') + m_spreadsheet->column(i)->name() + QLatin1Char('\''); } query += QLatin1String(") VALUES "); for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { if (i != 0) query += QLatin1String(","); query += QLatin1Char('('); for (int j = 0; j < cols; ++j) { Column* col = m_spreadsheet->column(j); if (j != 0) query += QLatin1String(", "); query += QLatin1Char('\'') + col->asStringColumn()->textAt(i) + QLatin1Char('\''); } query += QLatin1String(")"); } query += QLatin1Char(';'); } //insert values if (!q.exec(query)) { RESET_CURSOR; KMessageBox::error(nullptr, i18n("Failed to insert values into the table.")); QDEBUG("bulk insert error " << q.lastError().databaseText()); } else q.exec(QLatin1String("COMMIT TRANSACTION;")); //close the database db.close(); } diff --git a/src/kdefrontend/GuiObserver.cpp b/src/kdefrontend/GuiObserver.cpp index f00ce8d25..9144b629b 100644 --- a/src/kdefrontend/GuiObserver.cpp +++ b/src/kdefrontend/GuiObserver.cpp @@ -1,459 +1,452 @@ /*************************************************************************** File : GuiObserver.cpp Project : LabPlot Description : GUI observer -------------------------------------------------------------------- Copyright : (C) 2010-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2015-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2016 Garvit Khatri (garvitdelhi@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "kdefrontend/GuiObserver.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/AbstractAspect.h" #include "backend/datasources/LiveDataSource.h" #include "backend/matrix/Matrix.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/TextLabel.h" #ifdef HAVE_CANTOR_LIBS #include "backend/cantorWorksheet/CantorWorksheet.h" #endif #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include "backend/datasources/MQTTSubscription.h" #include "backend/datasources/MQTTTopic.h" #endif #include "backend/core/Project.h" #include "backend/datapicker/Datapicker.h" #include "backend/datapicker/DatapickerImage.h" #include "backend/datapicker/DatapickerCurve.h" #include "commonfrontend/ProjectExplorer.h" #include "kdefrontend/MainWin.h" #include "kdefrontend/dockwidgets/AxisDock.h" #include "kdefrontend/dockwidgets/NoteDock.h" #include "kdefrontend/dockwidgets/CursorDock.h" #include "kdefrontend/dockwidgets/CartesianPlotDock.h" #include "kdefrontend/dockwidgets/CartesianPlotLegendDock.h" #include "kdefrontend/dockwidgets/ColumnDock.h" #include "kdefrontend/dockwidgets/LiveDataDock.h" #include "kdefrontend/dockwidgets/MatrixDock.h" #include "kdefrontend/dockwidgets/ProjectDock.h" #include "kdefrontend/dockwidgets/SpreadsheetDock.h" #include "kdefrontend/dockwidgets/XYCurveDock.h" #include "kdefrontend/dockwidgets/HistogramDock.h" #include "kdefrontend/dockwidgets/XYEquationCurveDock.h" #include "kdefrontend/dockwidgets/XYDataReductionCurveDock.h" #include "kdefrontend/dockwidgets/XYDifferentiationCurveDock.h" #include "kdefrontend/dockwidgets/XYIntegrationCurveDock.h" #include "kdefrontend/dockwidgets/XYInterpolationCurveDock.h" #include "kdefrontend/dockwidgets/XYSmoothCurveDock.h" #include "kdefrontend/dockwidgets/XYFitCurveDock.h" #include "kdefrontend/dockwidgets/XYFourierFilterCurveDock.h" #include "kdefrontend/dockwidgets/XYFourierTransformCurveDock.h" #include "kdefrontend/dockwidgets/XYConvolutionCurveDock.h" #include "kdefrontend/dockwidgets/XYCorrelationCurveDock.h" #include "kdefrontend/dockwidgets/CustomPointDock.h" #include "kdefrontend/dockwidgets/WorksheetDock.h" #ifdef HAVE_CANTOR_LIBS #include "kdefrontend/dockwidgets/CantorWorksheetDock.h" #endif #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/widgets/DatapickerImageWidget.h" #include "kdefrontend/widgets/DatapickerCurveWidget.h" #include #include #include #include #include /*! \class GuiObserver \brief The GUI observer looks for the selection changes in the main window and shows/hides the correspondings dock widgets, toolbars etc. This class is intended to simplify (or not to overload) the code in MainWin. \ingroup kdefrontend */ namespace GuiObserverHelper { template bool raiseDock(T*& dock, QStackedWidget* parent) { const bool generated = !dock; if (generated) { dock = new T(parent); parent->addWidget(dock); } parent->setCurrentWidget(dock); return generated; } template void raiseDockConnect(T*& dock, QStatusBar* statusBar, QStackedWidget* parent) { if (raiseDock(dock, parent)) QObject::connect(dock, &T::info, [=](const QString& text){ statusBar->showMessage(text); }); } template void raiseDockSetupConnect(T*& dock, QStatusBar* statusBar, QStackedWidget* parent) { if (raiseDock(dock, parent)) { dock->setupGeneral(); QObject::connect(dock, &T::info, [=](const QString& text){ statusBar->showMessage(text); }); } } template QList castList(QList& selectedAspects) { QList list; for (auto* aspect : selectedAspects) list << static_cast(aspect); return list; } } using namespace GuiObserverHelper; GuiObserver::GuiObserver(MainWin* mainWin) { connect(mainWin->m_projectExplorer, &ProjectExplorer::selectedAspectsChanged, this, &GuiObserver::selectedAspectsChanged); connect(mainWin->m_projectExplorer, &ProjectExplorer::hiddenAspectSelected, this, &GuiObserver::hiddenAspectSelected); m_mainWindow = mainWin; } /*! called on selection changes in the project explorer. Determines the type of the currently selected objects (aspects) and activates the corresponding dockwidgets, toolbars etc. */ void GuiObserver::selectedAspectsChanged(QList& selectedAspects) const { auto clearDock = [&](){ if (m_mainWindow->stackedWidget->currentWidget()) m_mainWindow->stackedWidget->currentWidget()->hide(); m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); }; if (selectedAspects.isEmpty()) { clearDock(); return; } // update cursor dock AbstractAspect* parent = selectedAspects[0]->parent(AspectType::Worksheet); if (selectedAspects[0]->inherits(AspectType::Worksheet)) { - Worksheet* worksheet = static_cast(selectedAspects[0]); - if (m_mainWindow->cursorWidget) { - m_mainWindow->cursorWidget->setCursorTreeViewModel(worksheet->cursorModel()); - QVector plots = worksheet->children(); - if (!plots.isEmpty()) - m_mainWindow->cursorWidget->setPlots(plots); + Worksheet* worksheet = static_cast(selectedAspects[0]); + m_mainWindow->cursorWidget->setWorksheet(worksheet); } } else if (parent) { if (m_mainWindow->cursorWidget) { - Worksheet* worksheet = static_cast(parent); - QVector plots = worksheet->children(); - m_mainWindow->cursorWidget->setCursorTreeViewModel(worksheet->cursorModel()); - m_mainWindow->cursorWidget->setPlots(plots); + Worksheet* worksheet = static_cast(parent); + m_mainWindow->cursorWidget->setWorksheet(worksheet); } - } const AspectType type = selectedAspects.front()->type(); // Check, whether objects of different types were selected. // Don't show any dock widgets in this case. for (auto* aspect : selectedAspects) { if (aspect->type() != type) { clearDock(); return; } } switch (type) { case AspectType::Spreadsheet: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Spreadsheet")); raiseDockConnect(m_mainWindow->spreadsheetDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->spreadsheetDock->setSpreadsheets(castList(selectedAspects)); break; case AspectType::Column: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Column")); raiseDockConnect(m_mainWindow->columnDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->columnDock->setColumns(castList(selectedAspects)); break; case AspectType::Matrix: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Matrix")); raiseDockConnect(m_mainWindow->matrixDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->matrixDock->setMatrices(castList(selectedAspects)); break; case AspectType::Worksheet: { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Worksheet")); if (!m_mainWindow->worksheetDock) { m_mainWindow->worksheetDock = new WorksheetDock(m_mainWindow->stackedWidget); connect(m_mainWindow->worksheetDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); m_mainWindow->stackedWidget->addWidget(m_mainWindow->worksheetDock); } raiseDockConnect(m_mainWindow->worksheetDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->worksheetDock->setWorksheets(castList(selectedAspects)); break; } case AspectType::CartesianPlot: { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Cartesian Plot")); raiseDockConnect(m_mainWindow->cartesianPlotDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->cartesianPlotDock->setPlots(castList(selectedAspects)); break; } case AspectType::CartesianPlotLegend: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Legend")); raiseDockConnect(m_mainWindow->cartesianPlotLegendDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->cartesianPlotLegendDock->setLegends(castList(selectedAspects)); break; case AspectType::Axis: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Axis")); raiseDockConnect(m_mainWindow->axisDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->axisDock->setAxes(castList(selectedAspects)); break; case AspectType::XYCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "xy-Curve")); raiseDockSetupConnect(m_mainWindow->xyCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYEquationCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "xy-Equation")); raiseDockSetupConnect(m_mainWindow->xyEquationCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyEquationCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYDataReductionCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Data Reduction")); if (!m_mainWindow->xyDataReductionCurveDock) { m_mainWindow->xyDataReductionCurveDock = new XYDataReductionCurveDock(m_mainWindow->stackedWidget, m_mainWindow->statusBar()); m_mainWindow->xyDataReductionCurveDock->setupGeneral(); connect(m_mainWindow->xyDataReductionCurveDock, &XYDataReductionCurveDock::info, [&](const QString& text){ m_mainWindow->statusBar()->showMessage(text); }); m_mainWindow->stackedWidget->addWidget(m_mainWindow->xyDataReductionCurveDock); } m_mainWindow->xyDataReductionCurveDock->setCurves(castList(selectedAspects)); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyDataReductionCurveDock); break; case AspectType::XYDifferentiationCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Differentiation")); raiseDockSetupConnect(m_mainWindow->xyDifferentiationCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyDifferentiationCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYIntegrationCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Integration")); raiseDockSetupConnect(m_mainWindow->xyIntegrationCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyIntegrationCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYInterpolationCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Interpolation")); raiseDockSetupConnect(m_mainWindow->xyInterpolationCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyInterpolationCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYSmoothCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Smoothing")); raiseDockSetupConnect(m_mainWindow->xySmoothCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xySmoothCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYFitCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fit")); raiseDockSetupConnect(m_mainWindow->xyFitCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyFitCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYFourierTransformCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fourier Transform")); raiseDockSetupConnect(m_mainWindow->xyFourierTransformCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyFourierTransformCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYFourierFilterCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fourier Filter")); raiseDockSetupConnect(m_mainWindow->xyFourierFilterCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyFourierFilterCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYConvolution: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Convolution/Deconvolution")); raiseDockSetupConnect(m_mainWindow->xyConvolutionCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyConvolutionCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::XYCorrelationCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Auto-/Cross-Correlation")); raiseDockSetupConnect(m_mainWindow->xyCorrelationCurveDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->xyCorrelationCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::Histogram: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Histogram Properties")); raiseDockConnect(m_mainWindow->histogramDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->histogramDock->setCurves(castList(selectedAspects)); break; case AspectType::TextLabel: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Text Label")); raiseDock(m_mainWindow->textLabelDock, m_mainWindow->stackedWidget); m_mainWindow->textLabelDock->setLabels(castList(selectedAspects)); break; case AspectType::CustomPoint: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Custom Point")); raiseDock(m_mainWindow->customPointDock, m_mainWindow->stackedWidget); m_mainWindow->customPointDock->setPoints(castList(selectedAspects)); break; case AspectType::DatapickerCurve: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Datapicker Curve")); raiseDock(m_mainWindow->datapickerCurveDock, m_mainWindow->stackedWidget); m_mainWindow->datapickerCurveDock->setCurves(castList(selectedAspects)); break; case AspectType::Datapicker: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Datapicker")); raiseDock(m_mainWindow->datapickerImageDock, m_mainWindow->stackedWidget); { QList list; for (auto* aspect : selectedAspects) list << qobject_cast(aspect)->image(); m_mainWindow->datapickerImageDock->setImages(list); } break; case AspectType::Project: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Project")); raiseDock(m_mainWindow->projectDock, m_mainWindow->stackedWidget); m_mainWindow->projectDock->setProject(m_mainWindow->m_project); break; case AspectType::CantorWorksheet: #ifdef HAVE_CANTOR_LIBS raiseDockConnect(m_mainWindow->cantorWorksheetDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); { QList list = castList(selectedAspects); if (list.size() == 1) m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window %1 is a Cantor backend", "%1 Properties", list.first()->backendName())); else m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "CAS Properties")); m_mainWindow->cantorWorksheetDock->setCantorWorksheets(list); } #endif break; case AspectType::Note: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Notes")); raiseDock(m_mainWindow->notesDock, m_mainWindow->stackedWidget); m_mainWindow->notesDock->setNotesList(castList(selectedAspects)); break; case AspectType::MQTTClient: #ifdef HAVE_MQTT m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "MQTT Data Source")); raiseDock(m_mainWindow->m_liveDataDock, m_mainWindow->stackedWidget); m_mainWindow->m_liveDataDock->setMQTTClient(static_cast(selectedAspects.first())); #endif break; case AspectType::MQTTSubscription: #ifdef HAVE_MQTT m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "MQTT Data Source")); raiseDock(m_mainWindow->m_liveDataDock, m_mainWindow->stackedWidget); m_mainWindow->m_liveDataDock->setMQTTClient(static_cast(selectedAspects.first())->mqttClient()); #endif break; case AspectType::MQTTTopic: #ifdef HAVE_MQTT m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "MQTT Data Source")); raiseDock(m_mainWindow->m_liveDataDock, m_mainWindow->stackedWidget); m_mainWindow->m_liveDataDock->setMQTTClient(static_cast(selectedAspects.first())->mqttClient()); #endif break; case AspectType::LiveDataSource: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Live Data Source")); raiseDock(m_mainWindow->m_liveDataDock, m_mainWindow->stackedWidget); m_mainWindow->m_liveDataDock->setLiveDataSource(static_cast(selectedAspects.first())); break; case AspectType::AbstractAspect: case AspectType::AbstractColumn: case AspectType::AbstractDataSource: case AspectType::AbstractFilter: case AspectType::AbstractPart: case AspectType::AbstractPlot: case AspectType::ColumnStringIO: case AspectType::DatapickerImage: case AspectType::DatapickerPoint: case AspectType::Folder: case AspectType::PlotArea: case AspectType::SimpleFilterColumn: case AspectType::Workbook: case AspectType::WorksheetElement: case AspectType::WorksheetElementContainer: case AspectType::WorksheetElementGroup: case AspectType::XYAnalysisCurve: clearDock(); return; } m_mainWindow->stackedWidget->currentWidget()->show(); } /*! handles the selection of a hidden aspect \c aspect in the view (relevant for WorksheetView only at the moment). Currently, a hidden aspect can only be a plot title lable or an axis label. -> Activate the corresponding DockWidget and make the title tab current. */ void GuiObserver::hiddenAspectSelected(const AbstractAspect* aspect) const { const AbstractAspect* parent = aspect->parentAspect(); if (!parent) return; switch (static_cast(parent->type())) { // cast the enum to turn off warnings about unhandled cases case static_cast(AspectType::Axis): if (!m_mainWindow->axisDock) { m_mainWindow->axisDock = new AxisDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->axisDock); } m_mainWindow->axisDock->activateTitleTab(); break; case static_cast(AspectType::CartesianPlot): if (!m_mainWindow->cartesianPlotDock) { m_mainWindow->cartesianPlotDock = new CartesianPlotDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotDock); } m_mainWindow->cartesianPlotDock->activateTitleTab(); break; case static_cast(AspectType::CartesianPlotLegend): if (!m_mainWindow->cartesianPlotLegendDock) { m_mainWindow->cartesianPlotLegendDock = new CartesianPlotLegendDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotLegendDock); } m_mainWindow->cartesianPlotLegendDock->activateTitleTab(); break; default: break; } } diff --git a/src/kdefrontend/MainWin.cpp b/src/kdefrontend/MainWin.cpp index ead37f34f..5241c7d07 100644 --- a/src/kdefrontend/MainWin.cpp +++ b/src/kdefrontend/MainWin.cpp @@ -1,2308 +1,2325 @@ /*************************************************************************** File : MainWin.cc Project : LabPlot Description : Main window of the application -------------------------------------------------------------------- Copyright : (C) 2008-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "MainWin.h" #include "backend/core/Project.h" #include "backend/core/Folder.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Workbook.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/worksheet/Worksheet.h" #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/DatasetHandler.h" #ifdef HAVE_LIBORIGIN #include "backend/datasources/projects/OriginProjectParser.h" #endif #ifdef HAVE_CANTOR_LIBS #include "backend/cantorWorksheet/CantorWorksheet.h" #endif #include "backend/datapicker/Datapicker.h" #include "backend/note/Note.h" #include "backend/lib/macros.h" -#include "backend/worksheet/TreeModel.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include "commonfrontend/core/PartMdiView.h" #include "commonfrontend/ProjectExplorer.h" #include "commonfrontend/matrix/MatrixView.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "commonfrontend/worksheet/WorksheetView.h" #ifdef HAVE_CANTOR_LIBS #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" #endif #include "commonfrontend/datapicker/DatapickerView.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "commonfrontend/note/NoteView.h" #include "commonfrontend/widgets/MemoryWidget.h" #include "kdefrontend/datasources/ImportFileDialog.h" #include "kdefrontend/datasources/ImportDatasetDialog.h" #include "kdefrontend/datasources/ImportDatasetWidget.h" #include "kdefrontend/datasources/ImportProjectDialog.h" #include "kdefrontend/datasources/ImportSQLDatabaseDialog.h" #include #include "kdefrontend/dockwidgets/ProjectDock.h" #include "kdefrontend/HistoryDialog.h" #include "kdefrontend/SettingsDialog.h" #include "kdefrontend/GuiObserver.h" #include "kdefrontend/widgets/FITSHeaderEditDialog.h" #include "DatasetModel.h" #include "WelcomeScreenHelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CANTOR_LIBS #include #include #include #include #endif /*! \class MainWin \brief Main application window. \ingroup kdefrontend */ MainWin::MainWin(QWidget *parent, const QString& filename) : KXmlGuiWindow(parent) { initGUI(filename); setAcceptDrops(true); //restore the geometry KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); restoreGeometry(group.readEntry("geometry", QByteArray())); } MainWin::~MainWin() { //save the recent opened files m_recentProjectsAction->saveEntries( KSharedConfig::openConfig()->group("Recent Files") ); KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); group.writeEntry("geometry", saveGeometry()); KSharedConfig::openConfig()->sync(); qDebug() << "Mainwin Destructor "; if(dynamic_cast(centralWidget()) != nullptr) { qDebug() << "Destructor save welcome screen"; QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions"); } if (m_project != nullptr) { if(dynamic_cast(centralWidget()) == nullptr) m_mdiArea->closeAllSubWindows(); disconnect(m_project, nullptr, this, nullptr); delete m_project; } if (m_aspectTreeModel) delete m_aspectTreeModel; if (m_guiObserver) delete m_guiObserver; if(m_welcomeScreenHelper) delete m_welcomeScreenHelper; } void MainWin::showPresenter() { Worksheet* w = activeWorksheet(); if (w) { auto* view = dynamic_cast(w->view()); view->presenterMode(); } else { //currently active object is not a worksheet but we're asked to start in the presenter mode //determine the first available worksheet and show it in the presenter mode QVector worksheets = m_project->children(); if (worksheets.size() > 0) { auto* view = qobject_cast(worksheets.first()->view()); view->presenterMode(); } else { QMessageBox::information(this, i18n("Presenter Mode"), i18n("No worksheets are available in the project. The presenter mode will not be started.")); } } } AspectTreeModel* MainWin::model() const { return m_aspectTreeModel; } -/*! - * Show cursor dock and set the treeview model - * @param model - */ -void MainWin::showCursorDock(TreeModel* model, QVector plots) { - if (!cursorDock) { - cursorDock = new QDockWidget(i18n("Cursor"), this); - cursorWidget = new CursorDock(cursorDock); - cursorDock->setWidget(cursorWidget); - cursorDock->setFloating(true); - - // does not work. Don't understand why -// if (m_propertiesDock) -// tabifyDockWidget(cursorDock, m_propertiesDock); -// else - addDockWidget(Qt::DockWidgetArea::AllDockWidgetAreas, cursorDock); - } - cursorWidget->setCursorTreeViewModel(model); - cursorWidget->setPlots(plots); - cursorDock->show(); -} - Project* MainWin::project() const { return m_project; } void MainWin::initGUI(const QString& fileName) { m_mdiArea = new QMdiArea; setCentralWidget(m_mdiArea); connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &MainWin::handleCurrentSubWindowChanged); statusBar()->showMessage(i18nc("%1 is the LabPlot version", "Welcome to LabPlot %1", QLatin1String(LVERSION))); initActions(); #ifdef Q_OS_DARWIN setupGUI(Default, QLatin1String("/Applications/labplot2.app/Contents/Resources/labplot2ui.rc")); #else setupGUI(Default, KXMLGUIClient::xmlFile()); // should be "labplot2ui.rc" #endif DEBUG("component name: " << KXMLGUIClient::componentName().toStdString()); DEBUG("XML file: " << KXMLGUIClient::xmlFile().toStdString() << " (should be \"labplot2ui.rc\")"); //all toolbars created via the KXMLGUI framework are locked on default: // * on the very first program start, unlock all toolbars // * on later program starts, set stored lock status //Furthermore, we want to show icons only after the first program start. KConfigGroup groupMain = KSharedConfig::openConfig()->group("MainWindow"); if (groupMain.exists()) { //KXMLGUI framework automatically stores "Disabled" for the key "ToolBarsMovable" //in case the toolbars are locked -> load this value const QString& str = groupMain.readEntry(QLatin1String("ToolBarsMovable"), ""); bool locked = (str == QLatin1String("Disabled")); KToolBar::setToolBarsLocked(locked); } else { //first start KToolBar::setToolBarsLocked(false); //show icons only for (auto* container : factory()->containers(QLatin1String("ToolBar"))) { auto* toolbar = dynamic_cast(container); if (toolbar) toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); } } initMenus(); auto* mainToolBar = qobject_cast(factory()->container("main_toolbar", this)); if (!mainToolBar) { QMessageBox::critical(this, i18n("GUI configuration file not found"), i18n("%1 file was not found. Please check your installation.", KXMLGUIClient::xmlFile())); //TODO: the application is not really usable if the rc file was not found. We should quit the application. The following line crashes //the application because of the splash screen. We need to find another solution. // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); //call close as soon as we enter the eventloop return; } auto* tbImport = new QToolButton(mainToolBar); tbImport->setPopupMode(QToolButton::MenuButtonPopup); tbImport->setMenu(m_importMenu); tbImport->setDefaultAction(m_importFileAction); mainToolBar->addWidget(tbImport); qobject_cast(factory()->container("import", this))->setIcon(QIcon::fromTheme("document-import")); setWindowIcon(QIcon::fromTheme("LabPlot2", QGuiApplication::windowIcon())); setAttribute( Qt::WA_DeleteOnClose ); //make the status bar of a fixed size in order to avoid height changes when placing a ProgressBar there. QFont font; font.setFamily(font.defaultFamily()); QFontMetrics fm(font); statusBar()->setFixedHeight(fm.height() + 5); //load recently used projects m_recentProjectsAction->loadEntries( KSharedConfig::openConfig()->group("Recent Files") ); //set the view mode of the mdi area KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); int viewMode = group.readEntry("ViewMode", 0); if (viewMode == 1) { m_mdiArea->setViewMode(QMdiArea::TabbedView); int tabPosition = group.readEntry("TabPosition", 0); m_mdiArea->setTabPosition(QTabWidget::TabPosition(tabPosition)); m_mdiArea->setTabsClosable(true); m_mdiArea->setTabsMovable(true); m_tileWindows->setVisible(false); m_cascadeWindows->setVisible(false); } //auto-save m_autoSaveActive = group.readEntry("AutoSave", false); int interval = group.readEntry("AutoSaveInterval", 1); interval = interval*60*1000; m_autoSaveTimer.setInterval(interval); connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWin::autoSaveProject); if (!fileName.isEmpty()) { #ifdef HAVE_LIBORIGIN if (Project::isLabPlotProject(fileName) || OriginProjectParser::isOriginProject(fileName)) { #else if (Project::isLabPlotProject(fileName)) { #endif QTimer::singleShot(0, this, [=] () { openProject(fileName); }); } else { newProject(); QTimer::singleShot(0, this, [=] () { importFileDialog(fileName); }); } } else { //There is no file to open. Depending on the settings do nothing, //create a new project or open the last used project. int load = group.readEntry("LoadOnStart", 0); if (load == 1) //create new project newProject(); else if (load == 2) { //create new project with a worksheet newProject(); newWorksheet(); } else if (load == 3) { //open last used project if (!m_recentProjectsAction->urls().isEmpty()) { QDEBUG("TO OPEN m_recentProjectsAction->urls() =" << m_recentProjectsAction->urls().constFirst()); openRecentProject( m_recentProjectsAction->urls().constFirst() ); } } } //show memory info const bool showMemoryInfo = group.readEntry(QLatin1String("ShowMemoryInfo"), true); if (showMemoryInfo) { m_memoryInfoWidget = new MemoryWidget(statusBar()); statusBar()->addPermanentWidget(m_memoryInfoWidget); } updateGUIOnProjectChanges(); //load welcome screen m_showWelcomeScreen = group.readEntry(QLatin1String("ShowWelcomeScreen"), true); if(m_showWelcomeScreen) { m_welcomeWidget = createWelcomeScreen(); setCentralWidget(m_welcomeWidget); } } /** * @brief Creates a new welcome screen to be set as central widget. * @return */ QQuickWidget* MainWin::createWelcomeScreen() { QSize maxSize = qApp->primaryScreen()->availableSize(); resize(maxSize); setMinimumSize(700, 400); showMaximized(); qDebug() << "Main win size " << size(); KToolBar* toolbar = toolBar(); if(toolbar != nullptr) { toolbar->setVisible(false); } else { qDebug() << "There is no toolbar to hide"; } QList recentList; for (QUrl url : m_recentProjectsAction->urls()) recentList.append(QVariant(url)); QQuickWidget* quickWidget = new QQuickWidget(this); QUrl source("qrc:///main.qml"); QQmlContext *ctxt = quickWidget->rootContext(); QVariant variant(recentList); ctxt->setContextProperty("recentProjects", variant); if(m_welcomeScreenHelper != nullptr) delete m_welcomeScreenHelper; m_welcomeScreenHelper = new WelcomeScreenHelper(); connect(m_welcomeScreenHelper, SIGNAL(openExampleProject(QString)), this, SLOT(openProject(const QString& ))); ctxt->setContextProperty("datasetModel", m_welcomeScreenHelper->getDatasetModel()); ctxt->setContextProperty("helper", m_welcomeScreenHelper); qDebug() << "Categories: " << m_welcomeScreenHelper->getDatasetModel()->allCategories(); quickWidget->setSource(source); quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); QObject *item = quickWidget->rootObject(); qDebug() << "Start connecting welcome screen"; QObject::connect(item, SIGNAL(recentProjectClicked(QUrl)), this, SLOT(openRecentProject(QUrl))); QObject::connect(item, SIGNAL(datasetClicked(QString, QString, QString)), m_welcomeScreenHelper, SLOT(datasetClicked(const QString&, const QString&, const QString&))); QObject::connect(item, SIGNAL(openDataset()), this, SLOT(openDatasetExample())); QObject::connect(item, SIGNAL(openExampleProject(QString)), m_welcomeScreenHelper, SLOT(exampleProjectClicked(const QString&))); qDebug() << "Finished connecting welcome screen"; m_welcomeScreenHelper->showFirstDataset(); return quickWidget; } /** * @brief Initiates resetting the layout of the welcome screen */ void MainWin::resetWelcomeScreen() { if(dynamic_cast(centralWidget()) != nullptr) { QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "restoreOriginalLayout"); } } /** * @brief Creates a new MDI area, to replace the Welcome Screen as central widget */ void MainWin::createMdiArea() { //layout()->setSizeConstraint(QLayout::SetDefaultConstraint); setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); setMinimumSize(0,0); KToolBar* toolbar = toolBar(); if(toolbar != nullptr) { toolbar->setVisible(true); } else { qDebug() << "There is no toolbar to display"; } if(m_showWelcomeScreen) { qDebug() << "Call saving welcome screen widget dimensions"; QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions"); } m_mdiArea = new QMdiArea; setCentralWidget(m_mdiArea); connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &MainWin::handleCurrentSubWindowChanged); KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); int viewMode = group.readEntry("ViewMode", 0); if (viewMode == 1) { m_mdiArea->setViewMode(QMdiArea::TabbedView); int tabPosition = group.readEntry("TabPosition", 0); m_mdiArea->setTabPosition(QTabWidget::TabPosition(tabPosition)); m_mdiArea->setTabsClosable(true); m_mdiArea->setTabsMovable(true); m_tileWindows->setVisible(false); m_cascadeWindows->setVisible(false); } QAction* action = new QAction(i18n("&Close"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::Close); action->setStatusTip(i18n("Close the active window")); actionCollection()->addAction("close window", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::closeActiveSubWindow); action = new QAction(i18n("Close &All"), this); action->setStatusTip(i18n("Close all the windows")); actionCollection()->addAction("close all windows", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::closeAllSubWindows); m_tileWindows = new QAction(i18n("&Tile"), this); m_tileWindows->setStatusTip(i18n("Tile the windows")); actionCollection()->addAction("tile windows", m_tileWindows); connect(m_tileWindows, &QAction::triggered, m_mdiArea, &QMdiArea::tileSubWindows); m_cascadeWindows = new QAction(i18n("&Cascade"), this); m_cascadeWindows->setStatusTip(i18n("Cascade the windows")); actionCollection()->addAction("cascade windows", m_cascadeWindows); connect(m_cascadeWindows, &QAction::triggered, m_mdiArea, &QMdiArea::cascadeSubWindows); action = new QAction(QIcon::fromTheme("go-next-view"), i18n("Ne&xt"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::NextChild); action->setStatusTip(i18n("Move the focus to the next window")); actionCollection()->addAction("next window", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::activateNextSubWindow); action = new QAction(QIcon::fromTheme("go-previous-view"), i18n("Pre&vious"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::PreviousChild); action->setStatusTip(i18n("Move the focus to the previous window")); actionCollection()->addAction("previous window", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::activatePreviousSubWindow); } void MainWin::initActions() { // ******************** File-menu ******************************* //add some standard actions KStandardAction::openNew(this, SLOT(newProject()),actionCollection()); KStandardAction::open(this, SLOT(openProject()),actionCollection()); m_recentProjectsAction = KStandardAction::openRecent(this, SLOT(openRecentProject(QUrl)),actionCollection()); m_closeAction = KStandardAction::close(this, SLOT(closeProject()),actionCollection()); actionCollection()->setDefaultShortcut(m_closeAction, QKeySequence()); //remove the shortcut, QKeySequence::Close will be used for closing sub-windows m_saveAction = KStandardAction::save(this, SLOT(saveProject()),actionCollection()); m_saveAsAction = KStandardAction::saveAs(this, SLOT(saveProjectAs()),actionCollection()); m_printAction = KStandardAction::print(this, SLOT(print()),actionCollection()); m_printPreviewAction = KStandardAction::printPreview(this, SLOT(printPreview()),actionCollection()); - KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollection()); + + //TODO: on Mac OS when going full-screen we get a crash because of an stack-overflow +#ifndef Q_OS_MAC + KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollection()); +#endif //New Folder/Workbook/Spreadsheet/Matrix/Worksheet/Datasources m_newWorkbookAction = new QAction(QIcon::fromTheme("labplot-workbook-new"),i18n("Workbook"),this); actionCollection()->addAction("new_workbook", m_newWorkbookAction); m_newWorkbookAction->setWhatsThis(i18n("Creates a new workbook for collection spreadsheets, matrices and plots")); connect(m_newWorkbookAction, &QAction::triggered, this, &MainWin::newWorkbook); m_newDatapickerAction = new QAction(QIcon::fromTheme("color-picker-black"), i18n("Datapicker"), this); m_newDatapickerAction->setWhatsThis(i18n("Creates a data picker for getting data from a picture")); actionCollection()->addAction("new_datapicker", m_newDatapickerAction); connect(m_newDatapickerAction, &QAction::triggered, this, &MainWin::newDatapicker); m_newSpreadsheetAction = new QAction(QIcon::fromTheme("labplot-spreadsheet-new"),i18n("Spreadsheet"),this); // m_newSpreadsheetAction->setShortcut(Qt::CTRL+Qt::Key_Equal); m_newSpreadsheetAction->setWhatsThis(i18n("Creates a new spreadsheet for data editing")); actionCollection()->addAction("new_spreadsheet", m_newSpreadsheetAction); connect(m_newSpreadsheetAction, &QAction::triggered, this, &MainWin::newSpreadsheet); m_newMatrixAction = new QAction(QIcon::fromTheme("labplot-matrix-new"),i18n("Matrix"),this); // m_newMatrixAction->setShortcut(Qt::CTRL+Qt::Key_Equal); m_newMatrixAction->setWhatsThis(i18n("Creates a new matrix for data editing")); actionCollection()->addAction("new_matrix", m_newMatrixAction); connect(m_newMatrixAction, &QAction::triggered, this, &MainWin::newMatrix); m_newWorksheetAction = new QAction(QIcon::fromTheme("labplot-worksheet-new"),i18n("Worksheet"),this); // m_newWorksheetAction->setShortcut(Qt::ALT+Qt::Key_X); m_newWorksheetAction->setWhatsThis(i18n("Creates a new worksheet for data plotting")); actionCollection()->addAction("new_worksheet", m_newWorksheetAction); connect(m_newWorksheetAction, &QAction::triggered, this, &MainWin::newWorksheet); m_newNotesAction = new QAction(QIcon::fromTheme("document-new"),i18n("Note"),this); m_newNotesAction->setWhatsThis(i18n("Creates a new note for arbitrary text")); actionCollection()->addAction("new_notes", m_newNotesAction); connect(m_newNotesAction, &QAction::triggered, this, &MainWin::newNotes); // m_newScriptAction = new QAction(QIcon::fromTheme("insert-text"),i18n("Note/Script"),this); // actionCollection()->addAction("new_script", m_newScriptAction); // connect(m_newScriptAction, &QAction::triggered,SLOT(newScript())); m_newFolderAction = new QAction(QIcon::fromTheme("folder-new"),i18n("Folder"),this); m_newFolderAction->setWhatsThis(i18n("Creates a new folder to collect sheets and other elements")); actionCollection()->addAction("new_folder", m_newFolderAction); connect(m_newFolderAction, &QAction::triggered, this, &MainWin::newFolder); //"New file datasources" m_newLiveDataSourceAction = new QAction(QIcon::fromTheme("application-octet-stream"),i18n("Live Data Source"),this); m_newLiveDataSourceAction->setWhatsThis(i18n("Creates a live data source to read data from a real time device")); actionCollection()->addAction("new_live_datasource", m_newLiveDataSourceAction); connect(m_newLiveDataSourceAction, &QAction::triggered, this, &MainWin::newLiveDataSourceActionTriggered); m_newDatasetAction = new QAction(QIcon::fromTheme("application-octet-stream"), i18n("From Dataset Collection"), this); m_newDatasetAction->setWhatsThis(i18n("Imports data from an online dataset")); actionCollection()->addAction("import_dataset_datasource", m_newDatasetAction); connect(m_newDatasetAction, &QAction::triggered, this, &MainWin::newDatasetActionTriggered); //Import/Export m_importFileAction = new QAction(QIcon::fromTheme("document-import"), i18n("From File"), this); actionCollection()->setDefaultShortcut(m_importFileAction, Qt::CTRL+Qt::SHIFT+Qt::Key_I); m_importFileAction->setWhatsThis(i18n("Import data from a regular file")); actionCollection()->addAction("import_file", m_importFileAction); connect(m_importFileAction, &QAction::triggered, this, [=]() {importFileDialog();}); m_importSqlAction = new QAction(QIcon::fromTheme("document-import-database"), i18n("From SQL Database"), this); m_importSqlAction->setWhatsThis(i18n("Import data from a SQL database")); actionCollection()->addAction("import_sql", m_importSqlAction); connect(m_importSqlAction, &QAction::triggered, this, &MainWin::importSqlDialog); m_importLabPlotAction = new QAction(QIcon::fromTheme("document-import"), i18n("LabPlot Project"), this); m_importLabPlotAction->setWhatsThis(i18n("Import a project from a LabPlot project file (.lml)")); actionCollection()->addAction("import_labplot", m_importLabPlotAction); connect(m_importLabPlotAction, &QAction::triggered, this, &MainWin::importProjectDialog); #ifdef HAVE_LIBORIGIN m_importOpjAction = new QAction(QIcon::fromTheme("document-import-database"), i18n("Origin Project (OPJ)"), this); m_importOpjAction->setWhatsThis(i18n("Import a project from an OriginLab Origin project file (.opj)")); actionCollection()->addAction("import_opj", m_importOpjAction); connect(m_importOpjAction, &QAction::triggered, this, &MainWin::importProjectDialog); #endif m_exportAction = new QAction(QIcon::fromTheme("document-export"), i18n("Export"), this); m_exportAction->setWhatsThis(i18n("Export selected element")); actionCollection()->setDefaultShortcut(m_exportAction, Qt::CTRL+Qt::SHIFT+Qt::Key_E); actionCollection()->addAction("export", m_exportAction); connect(m_exportAction, &QAction::triggered, this, &MainWin::exportDialog); m_editFitsFileAction = new QAction(QIcon::fromTheme("editor"), i18n("FITS Metadata Editor"), this); m_editFitsFileAction->setWhatsThis(i18n("Open editor to edit FITS meta data")); actionCollection()->addAction("edit_fits", m_editFitsFileAction); connect(m_editFitsFileAction, &QAction::triggered, this, &MainWin::editFitsFileDialog); // Edit //Undo/Redo-stuff m_undoAction = KStandardAction::undo(this, SLOT(undo()), actionCollection()); m_redoAction = KStandardAction::redo(this, SLOT(redo()), actionCollection()); m_historyAction = new QAction(QIcon::fromTheme("view-history"), i18n("Undo/Redo History"),this); actionCollection()->addAction("history", m_historyAction); connect(m_historyAction, &QAction::triggered, this, &MainWin::historyDialog); // TODO: more menus // Appearance // Analysis: see WorksheetView.cpp // Drawing // Script //Windows QAction* action = new QAction(i18n("&Close"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::Close); action->setStatusTip(i18n("Close the active window")); actionCollection()->addAction("close window", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::closeActiveSubWindow); action = new QAction(i18n("Close &All"), this); action->setStatusTip(i18n("Close all the windows")); actionCollection()->addAction("close all windows", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::closeAllSubWindows); m_tileWindows = new QAction(i18n("&Tile"), this); m_tileWindows->setStatusTip(i18n("Tile the windows")); actionCollection()->addAction("tile windows", m_tileWindows); connect(m_tileWindows, &QAction::triggered, m_mdiArea, &QMdiArea::tileSubWindows); m_cascadeWindows = new QAction(i18n("&Cascade"), this); m_cascadeWindows->setStatusTip(i18n("Cascade the windows")); actionCollection()->addAction("cascade windows", m_cascadeWindows); connect(m_cascadeWindows, &QAction::triggered, m_mdiArea, &QMdiArea::cascadeSubWindows); action = new QAction(QIcon::fromTheme("go-next-view"), i18n("Ne&xt"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::NextChild); action->setStatusTip(i18n("Move the focus to the next window")); actionCollection()->addAction("next window", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::activateNextSubWindow); action = new QAction(QIcon::fromTheme("go-previous-view"), i18n("Pre&vious"), this); actionCollection()->setDefaultShortcut(action, QKeySequence::PreviousChild); action->setStatusTip(i18n("Move the focus to the previous window")); actionCollection()->addAction("previous window", action); connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::activatePreviousSubWindow); //"Standard actions" KStandardAction::preferences(this, SLOT(settingsDialog()), actionCollection()); KStandardAction::quit(this, SLOT(close()), actionCollection()); //Actions for window visibility auto* windowVisibilityActions = new QActionGroup(this); windowVisibilityActions->setExclusive(true); m_visibilityFolderAction = new QAction(QIcon::fromTheme("folder"), i18n("Current &Folder Only"), windowVisibilityActions); m_visibilityFolderAction->setCheckable(true); m_visibilityFolderAction->setData(Project::folderOnly); m_visibilitySubfolderAction = new QAction(QIcon::fromTheme("folder-documents"), i18n("Current Folder and &Subfolders"), windowVisibilityActions); m_visibilitySubfolderAction->setCheckable(true); m_visibilitySubfolderAction->setData(Project::folderAndSubfolders); m_visibilityAllAction = new QAction(i18n("&All"), windowVisibilityActions); m_visibilityAllAction->setCheckable(true); m_visibilityAllAction->setData(Project::allMdiWindows); connect(windowVisibilityActions, &QActionGroup::triggered, this, &MainWin::setMdiWindowVisibility); //Actions for hiding/showing the dock widgets auto* docksActions = new QActionGroup(this); docksActions->setExclusive(false); m_toggleProjectExplorerDockAction = new QAction(QIcon::fromTheme("view-list-tree"), i18n("Project Explorer"), docksActions); m_toggleProjectExplorerDockAction->setCheckable(true); m_toggleProjectExplorerDockAction->setChecked(true); actionCollection()->addAction("toggle_project_explorer_dock", m_toggleProjectExplorerDockAction); m_togglePropertiesDockAction = new QAction(QIcon::fromTheme("view-list-details"), i18n("Properties Explorer"), docksActions); m_togglePropertiesDockAction->setCheckable(true); m_togglePropertiesDockAction->setChecked(true); actionCollection()->addAction("toggle_properties_explorer_dock", m_togglePropertiesDockAction); connect(docksActions, &QActionGroup::triggered, this, &MainWin::toggleDockWidget); } void MainWin::initMenus() { //menu in the main toolbar for adding new aspects auto* menu = dynamic_cast(factory()->container("new", this)); menu->setIcon(QIcon::fromTheme("window-new")); - //menu in the project explorr and in the toolbar for adding new aspects + //menu in the project explorer and in the toolbar for adding new aspects m_newMenu = new QMenu(i18n("Add New"), this); m_newMenu->setIcon(QIcon::fromTheme("window-new")); m_newMenu->addAction(m_newFolderAction); m_newMenu->addAction(m_newWorkbookAction); m_newMenu->addAction(m_newSpreadsheetAction); m_newMenu->addAction(m_newMatrixAction); m_newMenu->addAction(m_newWorksheetAction); m_newMenu->addAction(m_newNotesAction); m_newMenu->addAction(m_newDatapickerAction); m_newMenu->addSeparator(); m_newMenu->addAction(m_newLiveDataSourceAction); //import menu m_importMenu = new QMenu(this); m_importMenu->setIcon(QIcon::fromTheme("document-import")); m_importMenu ->addAction(m_importFileAction); m_importMenu ->addAction(m_importSqlAction); m_newMenu->addAction(m_newDatasetAction); m_importMenu->addSeparator(); m_importMenu->addAction(m_importLabPlotAction); #ifdef HAVE_LIBORIGIN m_importMenu ->addAction(m_importOpjAction); #endif #ifdef HAVE_CANTOR_LIBS m_newMenu->addSeparator(); m_newCantorWorksheetMenu = new QMenu(i18n("CAS Worksheet"), this); m_newCantorWorksheetMenu->setIcon(QIcon::fromTheme("archive-insert")); //"Adding Cantor backends to menue and context menu" QStringList m_availableBackend = Cantor::Backend::listAvailableBackends(); if (m_availableBackend.count() > 0) { unplugActionList(QLatin1String("backends_list")); QList newBackendActions; for (Cantor::Backend* backend : Cantor::Backend::availableBackends()) { if (!backend->isEnabled()) continue; QAction* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(),this); action->setData(backend->name()); newBackendActions << action; m_newCantorWorksheetMenu->addAction(action); } connect(m_newCantorWorksheetMenu, &QMenu::triggered, this, &MainWin::newCantorWorksheet); plugActionList(QLatin1String("backends_list"), newBackendActions); } m_newMenu->addMenu(m_newCantorWorksheetMenu); #else delete this->guiFactory()->container("cas_worksheet", this); delete this->guiFactory()->container("new_cas_worksheet", this); delete this->guiFactory()->container("cas_worksheet_toolbar", this); #endif //menu subwindow visibility policy m_visibilityMenu = new QMenu(i18n("Window Visibility Policy"), this); m_visibilityMenu->setIcon(QIcon::fromTheme("window-duplicate")); m_visibilityMenu ->addAction(m_visibilityFolderAction); m_visibilityMenu ->addAction(m_visibilitySubfolderAction); m_visibilityMenu ->addAction(m_visibilityAllAction); //menu for editing files m_editMenu = new QMenu(i18n("Edit"), this); m_editMenu->addAction(m_editFitsFileAction); KColorSchemeManager schemeManager; KActionMenu* schemesMenu = schemeManager.createSchemeSelectionMenu(i18n("Color Theme"), this); schemesMenu->menu()->setTitle(i18n("Color Theme")); schemesMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color"))); QMenu* settingsMenu = dynamic_cast(factory()->container("settings", this)); if (settingsMenu) settingsMenu->insertMenu(settingsMenu->actions().constFirst(), schemesMenu->menu()); //set the action for the current color scheme checked KConfigGroup generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General"); QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze")); KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); QString schemeName = group.readEntry("ColorScheme", defaultSchemeName); for (auto* action : schemesMenu->menu()->actions()) { if (action->text() == schemeName) { action->setChecked(true); break; } } connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged); #ifdef HAVE_CANTOR_LIBS QAction* action = new QAction(QIcon::fromTheme(QLatin1String("cantor")), i18n("Configure CAS"), this); connect(action, &QAction::triggered, this, &MainWin::cantorSettingsDialog); if (settingsMenu) settingsMenu->addAction(action); #endif } void MainWin::colorSchemeChanged(QAction* action) { QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text()); //background of the mdi area is not updated on theme changes, do it here. KColorSchemeManager schemeManager; QModelIndex index = schemeManager.indexForScheme(schemeName); const QPalette& palette = KColorScheme::createApplicationPalette( KSharedConfig::openConfig(index.data(Qt::UserRole).toString()) ); const QBrush& brush = palette.brush(QPalette::Dark); m_mdiArea->setBackground(brush); //save the selected color scheme KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); group.writeEntry("ColorScheme", schemeName); group.sync(); } /*! Asks to save the project if it was modified. \return \c true if the project still needs to be saved ("cancel" clicked), \c false otherwise. */ bool MainWin::warnModified() { if (m_project->hasChanged()) { int want_save = KMessageBox::warningYesNoCancel( this, i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()), i18n("Save Project")); switch (want_save) { case KMessageBox::Yes: return !saveProject(); case KMessageBox::No: break; case KMessageBox::Cancel: return true; } } return false; } /*! * updates the state of actions, menus and toolbars (enabled or disabled) * on project changes (project closes and opens) */ void MainWin::updateGUIOnProjectChanges() { if (m_closing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } //disable all menus if there is no project bool b = (m_project == nullptr); m_saveAction->setEnabled(!b); m_saveAsAction->setEnabled(!b); m_printAction->setEnabled(!b); m_printPreviewAction->setEnabled(!b); m_importFileAction->setEnabled(!b); m_importSqlAction->setEnabled(!b); #ifdef HAVE_LIBORIGIN m_importOpjAction->setEnabled(!b); #endif m_exportAction->setEnabled(!b); m_newWorkbookAction->setEnabled(!b); m_newSpreadsheetAction->setEnabled(!b); m_newMatrixAction->setEnabled(!b); m_newWorksheetAction->setEnabled(!b); m_newDatapickerAction->setEnabled(!b); m_closeAction->setEnabled(!b); m_toggleProjectExplorerDockAction->setEnabled(!b); m_togglePropertiesDockAction->setEnabled(!b); if (!m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif } factory->container("new", this)->setEnabled(!b); factory->container("edit", this)->setEnabled(!b); factory->container("import", this)->setEnabled(!b); if (b) setCaption("LabPlot2"); else setCaption(m_project->name()); // undo/redo actions are disabled in both cases - when the project is closed or opened m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } /* * updates the state of actions, menus and toolbars (enabled or disabled) * depending on the currently active window (worksheet or spreadsheet). */ void MainWin::updateGUI() { if (m_project == nullptr || m_project->isLoading()) return; if (m_closing || m_projectClosing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } if (!m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif return; } //Handle the Worksheet-object Worksheet* w = this->activeWorksheet(); if (w != nullptr) { //enable worksheet related menus factory->container("worksheet", this)->setEnabled(true); factory->container("analysis", this)->setEnabled(true); //TODO factory->container("drawing", this)->setEnabled(true); //disable spreadsheet and matrix related menus factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); //populate worksheet menu auto* view = qobject_cast(w->view()); auto* menu = qobject_cast(factory->container("worksheet", this)); menu->clear(); view->createContextMenu(menu); //populate analysis menu menu = qobject_cast(factory->container("analysis", this)); menu->clear(); view->createAnalysisMenu(menu); //populate worksheet-toolbar auto* toolbar = qobject_cast(factory->container("worksheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the toolbar for cartesian plots toolbar = qobject_cast(factory->container("cartesian_plot_toolbar", this)); toolbar->clear(); view->fillCartesianPlotToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //hide the spreadsheet toolbar factory->container("spreadsheet_toolbar", this)->setVisible(false); } else { factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); // factory->container("drawing", this)->setEnabled(false); factory->container("worksheet_toolbar", this)->setEnabled(false); factory->container("cartesian_plot_toolbar", this)->setEnabled(false); } //Handle the Spreadsheet-object const auto* spreadsheet = this->activeSpreadsheet(); if (spreadsheet) { //enable spreadsheet related menus factory->container("spreadsheet", this)->setEnabled(true); //populate spreadsheet-menu auto* view = qobject_cast(spreadsheet->view()); auto* menu = qobject_cast(factory->container("spreadsheet", this)); menu->clear(); view->createContextMenu(menu); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("spreadsheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); } else { factory->container("spreadsheet", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->setEnabled(false); } //Handle the Matrix-object const Matrix* matrix = this->activeMatrix(); if (matrix) { factory->container("matrix", this)->setEnabled(true); //populate matrix-menu auto* view = qobject_cast(matrix->view()); auto* menu = qobject_cast(factory->container("matrix", this)); menu->clear(); view->createContextMenu(menu); } else factory->container("matrix", this)->setEnabled(false); #ifdef HAVE_CANTOR_LIBS CantorWorksheet* cantorworksheet = this->activeCantorWorksheet(); if (cantorworksheet) { // enable Cantor Worksheet related menus factory->container("cas_worksheet", this)->setEnabled(true); auto* view = qobject_cast(cantorworksheet->view()); auto* menu = qobject_cast(factory->container("cas_worksheet", this)); menu->clear(); view->createContextMenu(menu); auto* toolbar = qobject_cast(factory->container("cas_worksheet_toolbar", this)); toolbar->setVisible(true); toolbar->clear(); view->fillToolBar(toolbar); } else { //no Cantor worksheet selected -> deactivate Cantor worksheet related menu and toolbar factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->setVisible(false); } #endif const Datapicker* datapicker = this->activeDatapicker(); if (datapicker) { factory->container("datapicker", this)->setEnabled(true); //populate datapicker-menu auto* view = qobject_cast(datapicker->view()); auto* menu = qobject_cast(factory->container("datapicker", this)); menu->clear(); view->createContextMenu(menu); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("datapicker_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); } else { factory->container("datapicker", this)->setEnabled(false); factory->container("datapicker_toolbar", this)->setVisible(false); } } /*! creates a new empty project. Returns \c true, if a new project was created. */ bool MainWin::newProject() { //close the current project, if available if (!closeProject()) return false; if(dynamic_cast(centralWidget()) != nullptr) { createMdiArea(); setCentralWidget(m_mdiArea); } QApplication::processEvents(QEventLoop::AllEvents, 100); if (m_project) delete m_project; if (m_aspectTreeModel) delete m_aspectTreeModel; m_project = new Project(); m_currentAspect = m_project; m_currentFolder = m_project; KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); Project::MdiWindowVisibility vis = Project::MdiWindowVisibility(group.readEntry("MdiWindowVisibility", 0)); m_project->setMdiWindowVisibility( vis ); if (vis == Project::folderOnly) m_visibilityFolderAction->setChecked(true); else if (vis == Project::folderAndSubfolders) m_visibilitySubfolderAction->setChecked(true); else m_visibilityAllAction->setChecked(true); m_aspectTreeModel = new AspectTreeModel(m_project, this); connect(m_aspectTreeModel, &AspectTreeModel::statusInfo, [=](const QString& text){ statusBar()->showMessage(text); }); //newProject is called for the first time, there is no project explorer yet //-> initialize the project explorer, the GUI-observer and the dock widgets. if (m_projectExplorer == nullptr) { m_projectExplorerDock = new QDockWidget(this); m_projectExplorerDock->setObjectName("projectexplorer"); m_projectExplorerDock->setWindowTitle(i18nc("@title:window", "Project Explorer")); addDockWidget(Qt::LeftDockWidgetArea, m_projectExplorerDock); m_projectExplorer = new ProjectExplorer(m_projectExplorerDock); m_projectExplorerDock->setWidget(m_projectExplorer); connect(m_projectExplorer, &ProjectExplorer::currentAspectChanged, this, &MainWin::handleCurrentAspectChanged); connect(m_projectExplorerDock, &QDockWidget::visibilityChanged, this, &MainWin::projectExplorerDockVisibilityChanged); //Properties dock m_propertiesDock = new QDockWidget(this); m_propertiesDock->setObjectName("aspect_properties_dock"); m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); addDockWidget(Qt::RightDockWidgetArea, m_propertiesDock); auto* sa = new QScrollArea(m_propertiesDock); stackedWidget = new QStackedWidget(sa); sa->setWidget(stackedWidget); sa->setWidgetResizable(true); m_propertiesDock->setWidget(sa); connect(m_propertiesDock, &QDockWidget::visibilityChanged, this, &MainWin::propertiesDockVisibilityChanged); //GUI-observer; m_guiObserver = new GuiObserver(this); } m_projectExplorer->setModel(m_aspectTreeModel); m_projectExplorer->setProject(m_project); m_projectExplorer->setCurrentAspect(m_project); m_projectExplorerDock->show(); m_propertiesDock->show(); updateGUIOnProjectChanges(); connect(m_project, &Project::aspectAdded, this, &MainWin::handleAspectAdded); connect(m_project, &Project::aspectRemoved, this, &MainWin::handleAspectRemoved); connect(m_project, &Project::aspectAboutToBeRemoved, this, &MainWin::handleAspectAboutToBeRemoved); connect(m_project, SIGNAL(statusInfo(QString)), statusBar(), SLOT(showMessage(QString))); connect(m_project, &Project::changed, this, &MainWin::projectChanged); connect(m_project, &Project::requestProjectContextMenu, this, &MainWin::createContextMenu); connect(m_project, &Project::requestFolderContextMenu, this, &MainWin::createFolderContextMenu); connect(m_project, &Project::mdiWindowVisibilityChanged, this, &MainWin::updateMdiWindowVisibility); connect(m_project, &Project::closeRequested, this, &MainWin::closeProject); m_undoViewEmptyLabel = i18n("%1: created", m_project->name()); setCaption(m_project->name()); return true; } void MainWin::openProject() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); const QString& path = QFileDialog::getOpenFileName(this,i18n("Open Project"), dir, #ifdef HAVE_LIBORIGIN i18n("LabPlot Projects (%1);;Origin Projects (%2)", Project::supportedExtensions(), OriginProjectParser::supportedExtensions()) ); #else i18n("LabPlot Projects (%1)", Project::supportedExtensions()) ); #endif if (path.isEmpty())// "Cancel" was clicked return; this->openProject(path); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } } void MainWin::openProject(const QString& filename) { if (filename == m_currentFileName) { KMessageBox::information(this, i18n("The project file %1 is already opened.", filename), i18n("Open Project")); return; } if(dynamic_cast(centralWidget()) != nullptr) { createMdiArea(); setCentralWidget(m_mdiArea); } if (!newProject()) return; WAIT_CURSOR; QElapsedTimer timer; timer.start(); bool rc = false; - if (Project::isLabPlotProject(filename)) + if (Project::isLabPlotProject(filename)) { + qDebug()<<"openning project " << filename; + m_project->setFileName(filename); rc = m_project->load(filename); #ifdef HAVE_LIBORIGIN - else if (OriginProjectParser::isOriginProject(filename)) { + } else if (OriginProjectParser::isOriginProject(filename)) { OriginProjectParser parser; parser.setProjectFileName(filename); parser.importTo(m_project, QStringList()); //TODO: add return code rc = true; } #endif if (!rc) { closeProject(); RESET_CURSOR; return; } m_currentFileName = filename; - m_project->setFileName(filename); m_project->undoStack()->clear(); m_undoViewEmptyLabel = i18n("%1: opened", m_project->name()); m_recentProjectsAction->addUrl( QUrl(filename) ); setCaption(m_project->name()); updateGUIOnProjectChanges(); updateGUI(); //there are most probably worksheets or spreadsheets in the open project -> update the GUI m_saveAction->setEnabled(false); statusBar()->showMessage( i18n("Project successfully opened (in %1 seconds).", (float)timer.elapsed()/1000) ); if (m_autoSaveActive) m_autoSaveTimer.start(); RESET_CURSOR; } void MainWin::openRecentProject(const QUrl& url) { if(dynamic_cast(centralWidget()) != nullptr) { createMdiArea(); setCentralWidget(m_mdiArea); } if (url.isLocalFile()) // fix for Windows this->openProject(url.toLocalFile()); else this->openProject(url.path()); } /*! Closes the current project, if available. Return \c true, if the project was closed. */ bool MainWin::closeProject() { if (m_project == nullptr) return true; //nothing to close if (warnModified()) return false; if(!m_closing) { if(dynamic_cast(centralWidget()) == nullptr && m_showWelcomeScreen) { m_welcomeWidget = createWelcomeScreen(); setCentralWidget(m_welcomeWidget); } } m_projectClosing = true; delete m_aspectTreeModel; m_aspectTreeModel = nullptr; delete m_project; m_project = nullptr; m_currentFileName.clear(); m_projectClosing = false; //update the UI if we're just closing a project //and not closing(quitting) the application if (!m_closing) { m_projectExplorerDock->hide(); m_propertiesDock->hide(); m_currentAspect = nullptr; m_currentFolder = nullptr; updateGUIOnProjectChanges(); if (m_autoSaveActive) m_autoSaveTimer.stop(); } removeDockWidget(cursorDock); delete cursorDock; cursorDock = nullptr; cursorWidget = nullptr; // is deleted, because it's the cild of cursorDock return true; } bool MainWin::saveProject() { const QString& fileName = m_project->fileName(); if (fileName.isEmpty()) return saveProjectAs(); else return save(fileName); } bool MainWin::saveProjectAs() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); QString path = QFileDialog::getSaveFileName(this, i18n("Save Project As"), dir, i18n("LabPlot Projects (*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ)")); if (path.isEmpty())// "Cancel" was clicked return false; if (path.contains(QLatin1String(".lml"), Qt::CaseInsensitive) == false) path.append(QLatin1String(".lml")); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } return save(path); } /*! * auxiliary function that does the actual saving of the project */ bool MainWin::save(const QString& fileName) { WAIT_CURSOR; // use file ending to find out how to compress file QIODevice* file; // if ending is .lml, do gzip compression anyway if (fileName.endsWith(QLatin1String(".lml"))) file = new KCompressionDevice(fileName, KCompressionDevice::GZip); else file = new KFilterDev(fileName); if (file == nullptr) file = new QFile(fileName); bool ok; if (file->open(QIODevice::WriteOnly)) { m_project->setFileName(fileName); QPixmap thumbnail = centralWidget()->grab(); QXmlStreamWriter writer(file); + m_project->setFileName(fileName); m_project->save(thumbnail, &writer); m_project->undoStack()->clear(); m_project->setChanged(false); file->close(); setCaption(m_project->name()); statusBar()->showMessage(i18n("Project saved")); m_saveAction->setEnabled(false); m_recentProjectsAction->addUrl( QUrl(fileName) ); ok = true; //if the project dock is visible, refresh the shown content //(version and modification time might have been changed) if (stackedWidget->currentWidget() == projectDock) projectDock->setProject(m_project); //we have a file name now // -> auto save can be activated now if not happened yet if (m_autoSaveActive && !m_autoSaveTimer.isActive()) m_autoSaveTimer.start(); } else { KMessageBox::error(this, i18n("Sorry. Could not open file for writing.")); ok = false; } delete file; RESET_CURSOR; return ok; } /*! * automatically saves the project in the specified time interval. */ void MainWin::autoSaveProject() { //don't auto save when there are no changes or the file name //was not provided yet (the project was never explicitly saved yet). if ( !m_project->hasChanged() || m_project->fileName().isEmpty()) return; this->saveProject(); } /*! prints the current sheet (worksheet, spreadsheet or matrix) */ void MainWin::print() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printView()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->showMessage(QString()); } void MainWin::printPreview() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printPreview()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->showMessage(QString()); } /**************************************************************************************/ /*! adds a new Folder to the project. */ void MainWin::newFolder() { Folder* folder = new Folder(i18n("Folder")); this->addAspectToProject(folder); } /*! adds a new Workbook to the project. */ void MainWin::newWorkbook() { Workbook* workbook = new Workbook(i18n("Workbook")); this->addAspectToProject(workbook); } /*! adds a new Datapicker to the project. */ void MainWin::newDatapicker() { Datapicker* datapicker = new Datapicker(i18n("Datapicker")); this->addAspectToProject(datapicker); } /*! adds a new Spreadsheet to the project. */ void MainWin::newSpreadsheet() { Spreadsheet* spreadsheet = new Spreadsheet(i18n("Spreadsheet")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new spreadsheet to the workbook Workbook* workbook = activeWorkbook(); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits(AspectType::Folder)) { workbook->addChild(spreadsheet); return; } } this->addAspectToProject(spreadsheet); } /*! adds a new Matrix to the project. */ void MainWin::newMatrix() { Matrix* matrix = new Matrix(i18n("Matrix")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new matrix to the workbook Workbook* workbook = activeWorkbook(); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits(AspectType::Folder)) { workbook->addChild(matrix); return; } } this->addAspectToProject(matrix); } /*! adds a new Worksheet to the project. */ void MainWin::newWorksheet() { Worksheet* worksheet = new Worksheet(i18n("Worksheet")); this->addAspectToProject(worksheet); } /*! adds a new Note to the project. */ void MainWin::newNotes() { Note* notes = new Note(i18n("Note")); this->addAspectToProject(notes); } /*! returns a pointer to a Workbook-object, if the currently active Mdi-Subwindow is \a WorkbookView. Otherwise returns \a 0. */ Workbook* MainWin::activeWorkbook() const { if(dynamic_cast(centralWidget()) != nullptr) { return nullptr; } QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } /*! returns a pointer to a Datapicker-object, if the currently active Mdi-Subwindow is \a DatapickerView. Otherwise returns \a 0. */ Datapicker* MainWin::activeDatapicker() const { if(dynamic_cast(centralWidget()) != nullptr) { return nullptr; } QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } /*! returns a pointer to a \c Spreadsheet object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c SpreadsheetView Otherwise returns \c 0. */ Spreadsheet* MainWin::activeSpreadsheet() const { if(dynamic_cast(centralWidget()) != nullptr) { return nullptr; } QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); Spreadsheet* spreadsheet = nullptr; const auto* workbook = dynamic_cast(part); if (workbook) { spreadsheet = workbook->currentSpreadsheet(); if (!spreadsheet) { //potentially, the spreadsheet was not selected in workbook yet since the selection in project explorer //arrives in workbook's slot later than in this function //->check whether we have a spreadsheet or one of its columns currently selected in the project explorer spreadsheet = dynamic_cast(m_currentAspect); if (!spreadsheet) { if (m_currentAspect->parentAspect()) spreadsheet = dynamic_cast(m_currentAspect->parentAspect()); } } } else spreadsheet = dynamic_cast(part); return spreadsheet; } /*! returns a pointer to a \c Matrix object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c MatrixView Otherwise returns \c 0. */ Matrix* MainWin::activeMatrix() const { if(dynamic_cast(centralWidget()) != nullptr) { return nullptr; } QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); Matrix* matrix = nullptr; const auto* workbook = dynamic_cast(part); if (workbook) { matrix = workbook->currentMatrix(); if (!matrix) { //potentially, the matrix was not selected in workbook yet since the selection in project explorer //arrives in workbook's slot later than in this function //->check whether we have a matrix currently selected in the project explorer matrix = dynamic_cast(m_currentAspect); } } else matrix = dynamic_cast(part); return matrix; } /*! returns a pointer to a Worksheet-object, if the currently active Mdi-Subwindow is \a WorksheetView Otherwise returns \a 0. */ Worksheet* MainWin::activeWorksheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; if(dynamic_cast(centralWidget()) != nullptr) { return nullptr; } AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } #ifdef HAVE_CANTOR_LIBS /* adds a new Cantor Spreadsheet to the project. */ void MainWin::newCantorWorksheet(QAction* action) { CantorWorksheet* cantorworksheet = new CantorWorksheet(action->data().toString()); this->addAspectToProject(cantorworksheet); } /********************************************************************************/ /*! returns a pointer to a CantorWorksheet-object, if the currently active Mdi-Subwindow is \a CantorWorksheetView Otherwise returns \a 0. */ CantorWorksheet* MainWin::activeCantorWorksheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return nullptr; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } #endif /*! called if there were changes in the project. Adds "changed" to the window caption and activates the save-Action. */ void MainWin::projectChanged() { setCaption(i18n("%1 [Changed]", m_project->name())); m_saveAction->setEnabled(true); m_undoAction->setEnabled(true); return; } void MainWin::handleCurrentSubWindowChanged(QMdiSubWindow* win) { if (!win) return; auto* view = qobject_cast(win); if (!view) { updateGUI(); return; } if (view == m_currentSubWindow) { //do nothing, if the current sub-window gets selected again. //This event happens, when labplot loses the focus (modal window is opened or the user switches to another application) //and gets it back (modal window is closed or the user switches back to labplot). return; } else m_currentSubWindow = view; updateGUI(); if (!m_suppressCurrentSubWindowChangedEvent) m_projectExplorer->setCurrentAspect(view->part()); } void MainWin::handleAspectAdded(const AbstractAspect* aspect) { const auto* part = dynamic_cast(aspect); if (part) { // connect(part, &AbstractPart::importFromFileRequested, this, &MainWin::importFileDialog); connect(part, &AbstractPart::importFromFileRequested, this, [=]() {importFileDialog();}); connect(part, &AbstractPart::importFromSQLDatabaseRequested, this, &MainWin::importSqlDialog); //TODO: export, print and print preview should be handled in the views and not in MainWin. connect(part, &AbstractPart::exportRequested, this, &MainWin::exportDialog); connect(part, &AbstractPart::printRequested, this, &MainWin::print); connect(part, &AbstractPart::printPreviewRequested, this, &MainWin::printPreview); connect(part, &AbstractPart::showRequested, this, &MainWin::handleShowSubWindowRequested); - } - const auto* worksheet = dynamic_cast(aspect); - if (worksheet) - connect(worksheet, &Worksheet::showCursorDock, this, &MainWin::showCursorDock); + const auto* worksheet = dynamic_cast(aspect); + if (worksheet) + connect(worksheet, &Worksheet::cartesianPlotMouseModeChanged, this, &MainWin::cartesianPlotMouseModeChanged); + } } +// void MainWin::cartesianPlotMouseModeChanged(CartesianPlot::MouseMode void MainWin::handleAspectRemoved(const AbstractAspect* parent,const AbstractAspect* before,const AbstractAspect* aspect) { Q_UNUSED(before); Q_UNUSED(aspect); //no need to react on AbstractSimpleFilter if (!dynamic_cast(aspect)) m_projectExplorer->setCurrentAspect(parent); } void MainWin::handleAspectAboutToBeRemoved(const AbstractAspect *aspect) { const auto* part = qobject_cast(aspect); if (!part) return; const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (!workbook && !datapicker) { PartMdiView* win = part->mdiSubWindow(); if (win) m_mdiArea->removeSubWindow(win); } } /*! called when the current aspect in the tree of the project explorer was changed. Selects the new aspect. */ void MainWin::handleCurrentAspectChanged(AbstractAspect *aspect) { if (!aspect) aspect = m_project; // should never happen, just in case m_suppressCurrentSubWindowChangedEvent = true; if (aspect->folder() != m_currentFolder) { m_currentFolder = aspect->folder(); updateMdiWindowVisibility(); } m_currentAspect = aspect; //activate the corresponding MDI sub window for the current aspect activateSubWindowForAspect(aspect); m_suppressCurrentSubWindowChangedEvent = false; updateGUI(); } void MainWin::activateSubWindowForAspect(const AbstractAspect* aspect) const { const auto* part = dynamic_cast(aspect); if (part) { //for LiveDataSource we currently don't show any view /*if (dynamic_cast(part)) return;*/ PartMdiView* win; //for aspects being children of a Workbook, we show workbook's window, otherwise the window of the selected part const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (workbook) win = workbook->mdiSubWindow(); else if (datapicker) win = datapicker->mdiSubWindow(); else win = part->mdiSubWindow(); if (m_mdiArea->subWindowList().indexOf(win) == -1) { if (dynamic_cast(part)) m_mdiArea->addSubWindow(win, Qt::Tool); else m_mdiArea->addSubWindow(win); win->show(); //Qt provides its own "system menu" for every sub-window. The shortcut for the close-action //in this menu collides with our global m_closeAction. //remove the shortcuts in the system menu to avoid this collision. QMenu* menu = win->systemMenu(); if (menu) { for (QAction* action : menu->actions()) action->setShortcut(QKeySequence()); } } m_mdiArea->setActiveSubWindow(win); } else { //activate the mdiView of the parent, if a child was selected const AbstractAspect* parent = aspect->parentAspect(); if (parent) { activateSubWindowForAspect(parent); //if the parent's parent is a Workbook (a column of a spreadsheet in workbook was selected), //we need to select the corresponding tab in WorkbookView too if (parent->parentAspect()) { auto* workbook = dynamic_cast(parent->parentAspect()); auto* datapicker = dynamic_cast(parent->parentAspect()); if (!datapicker) datapicker = dynamic_cast(parent->parentAspect()->parentAspect()); if (workbook) workbook->childSelected(parent); else if (datapicker) datapicker->childSelected(parent); } } } return; } void MainWin::setMdiWindowVisibility(QAction* action) { m_project->setMdiWindowVisibility((Project::MdiWindowVisibility)(action->data().toInt())); } /*! shows the sub window of a worksheet, matrix or a spreadsheet. Used if the window was closed before and the user asks to show the window again via the context menu in the project explorer. */ void MainWin::handleShowSubWindowRequested() { activateSubWindowForAspect(m_currentAspect); } /*! this is called on a right click on the root folder in the project explorer */ void MainWin::createContextMenu(QMenu* menu) const { QAction* firstAction = nullptr; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size()>1) firstAction = menu->actions().at(1); menu->insertMenu(firstAction, m_newMenu); //The tabbed view collides with the visibility policy for the subwindows. //Hide the menus for the visibility policy if the tabbed view is used. if (m_mdiArea->viewMode() != QMdiArea::TabbedView) { menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_visibilityMenu); menu->insertSeparator(firstAction); } } /*! this is called on a right click on a non-root folder in the project explorer */ void MainWin::createFolderContextMenu(const Folder* folder, QMenu* menu) const { Q_UNUSED(folder); //Folder provides it's own context menu. Add a separator before adding additional actions. menu->addSeparator(); this->createContextMenu(menu); } void MainWin::undo() { WAIT_CURSOR; m_project->undoStack()->undo(); if (m_project->undoStack()->index() == 0) { setCaption(m_project->name()); m_saveAction->setEnabled(false); m_undoAction->setEnabled(false); m_project->setChanged(false); } m_redoAction->setEnabled(true); RESET_CURSOR; } void MainWin::redo() { WAIT_CURSOR; m_project->undoStack()->redo(); projectChanged(); if (m_project->undoStack()->index() == m_project->undoStack()->count()) m_redoAction->setEnabled(false); RESET_CURSOR; } /*! Shows/hides mdi sub-windows depending on the current visibility policy. */ void MainWin::updateMdiWindowVisibility() const { QList windows = m_mdiArea->subWindowList(); PartMdiView* part_view; switch (m_project->mdiWindowVisibility()) { case Project::allMdiWindows: for (auto* window : windows) window->show(); break; case Project::folderOnly: for (auto* window : windows) { part_view = qobject_cast(window); Q_ASSERT(part_view); if (part_view->part()->folder() == m_currentFolder) part_view->show(); else part_view->hide(); } break; case Project::folderAndSubfolders: for (auto* window : windows) { part_view = qobject_cast(window); if (part_view->part()->isDescendantOf(m_currentFolder)) part_view->show(); else part_view->hide(); } break; } } void MainWin::toggleDockWidget(QAction* action) { if (action->objectName() == "toggle_project_explorer_dock") { if (m_projectExplorerDock->isVisible()) m_projectExplorerDock->hide(); // toggleHideWidget(m_projectExplorerDock, true); else m_projectExplorerDock->show(); // toggleShowWidget(m_projectExplorerDock, true); } else if (action->objectName() == "toggle_properties_explorer_dock") { if (m_propertiesDock->isVisible()) m_propertiesDock->hide(); // toggleHideWidget(m_propertiesDock, false); else m_propertiesDock->show(); // toggleShowWidget(m_propertiesDock, false); } } /* void MainWin::toggleHideWidget(QWidget* widget, bool hideToLeft) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=] { const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (hideToLeft) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Backward); timeline->start(); connect(timeline, &QTimeLine::finished, [widget] {widget->hide();}); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } void MainWin::toggleShowWidget(QWidget* widget, bool showToRight) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=]() { if (widget->isHidden()) { widget->show(); } const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (showToRight) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Forward); timeline->start(); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } */ void MainWin::projectExplorerDockVisibilityChanged(bool visible) { m_toggleProjectExplorerDockAction->setChecked(visible); } void MainWin::propertiesDockVisibilityChanged(bool visible) { m_togglePropertiesDockAction->setChecked(visible); } +void MainWin::cursorDockVisibilityChanged(bool visible) { + //if the cursor dock was closed, switch to the "Select and Edit" mouse mode + if (!visible) { +// auto* worksheet = activeWorksheet(); + //TODO: + } +} + +void MainWin::cartesianPlotMouseModeChanged(CartesianPlot::MouseMode mode) { + if (mode != CartesianPlot::Cursor) { + if (cursorDock) + cursorDock->hide(); + } else { + if (!cursorDock) { + cursorDock = new QDockWidget(i18n("Cursor"), this); + cursorWidget = new CursorDock(cursorDock); + cursorDock->setWidget(cursorWidget); + connect(cursorDock, &QDockWidget::visibilityChanged, this, &MainWin::cursorDockVisibilityChanged); + // cursorDock->setFloating(true); + + // does not work. Don't understand why + // if (m_propertiesDock) + // tabifyDockWidget(cursorDock, m_propertiesDock); + // else + addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, cursorDock); + } + + auto* worksheet = static_cast(QObject::sender()); + cursorWidget->setWorksheet(worksheet); + cursorDock->show(); + } +} + void MainWin::toggleFullScreen() { if (this->windowState() == Qt::WindowFullScreen) this->setWindowState(m_lastWindowState); else { m_lastWindowState = this->windowState(); this->showFullScreen(); } } void MainWin::closeEvent(QCloseEvent* event) { m_closing = true; if (!this->closeProject()) { m_closing = false; event->ignore(); } } void MainWin::dragEnterEvent(QDragEnterEvent* event) { event->accept(); } void MainWin::dropEvent(QDropEvent* event) { if (event->mimeData() && !event->mimeData()->urls().isEmpty()) { QUrl url = event->mimeData()->urls().at(0); const QString& f = url.toLocalFile(); #ifdef HAVE_LIBORIGIN if (Project::isLabPlotProject(f) || OriginProjectParser::isOriginProject(f)) #else if (Project::isLabPlotProject(f)) #endif openProject(f); else { if (!m_project) newProject(); importFileDialog(f); } event->accept(); } else event->ignore(); } void MainWin::handleSettingsChanges() { const KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); if(dynamic_cast(centralWidget()) == nullptr) { QMdiArea::ViewMode viewMode = QMdiArea::ViewMode(group.readEntry("ViewMode", 0)); if (m_mdiArea->viewMode() != viewMode) { m_mdiArea->setViewMode(viewMode); if (viewMode == QMdiArea::SubWindowView) this->updateMdiWindowVisibility(); } if (m_mdiArea->viewMode() == QMdiArea::TabbedView) { m_tileWindows->setVisible(false); m_cascadeWindows->setVisible(false); QTabWidget::TabPosition tabPosition = QTabWidget::TabPosition(group.readEntry("TabPosition", 0)); if (m_mdiArea->tabPosition() != tabPosition) m_mdiArea->setTabPosition(tabPosition); } else { m_tileWindows->setVisible(true); m_cascadeWindows->setVisible(true); } } //autosave bool autoSave = group.readEntry("AutoSave", 0); if (m_autoSaveActive != autoSave) { m_autoSaveActive = autoSave; if (autoSave) m_autoSaveTimer.start(); else m_autoSaveTimer.stop(); } int interval = group.readEntry("AutoSaveInterval", 1); interval *= 60*1000; if (interval != m_autoSaveTimer.interval()) m_autoSaveTimer.setInterval(interval); //show memory info bool showMemoryInfo = group.readEntry(QLatin1String("ShowMemoryInfo"), true); if (m_showMemoryInfo != showMemoryInfo) { m_showMemoryInfo = showMemoryInfo; if (showMemoryInfo) { m_memoryInfoWidget = new MemoryWidget(statusBar()); statusBar()->addPermanentWidget(m_memoryInfoWidget); } else { if (m_memoryInfoWidget) { statusBar()->removeWidget(m_memoryInfoWidget); delete m_memoryInfoWidget; m_memoryInfoWidget = nullptr; } } } bool showWelcomeScreen = group.readEntry(QLatin1String("ShowWelcomeScreen"), true); if(m_showWelcomeScreen != showWelcomeScreen) { m_showWelcomeScreen = showWelcomeScreen; } } void MainWin::openDatasetExample() { newProject(); addAspectToProject(m_welcomeScreenHelper->releaseConfiguredSpreadsheet()); } /***************************************************************************************/ /************************************** dialogs ***************************************/ /***************************************************************************************/ /*! shows the dialog with the Undo-history. */ void MainWin::historyDialog() { if (!m_project->undoStack()) return; auto* dialog = new HistoryDialog(this, m_project->undoStack(), m_undoViewEmptyLabel); int index = m_project->undoStack()->index(); if (dialog->exec() != QDialog::Accepted) { if (m_project->undoStack()->count() != 0) m_project->undoStack()->setIndex(index); } //disable undo/redo-actions if the history was cleared //(in both cases, when accepted or rejected in the dialog) if (m_project->undoStack()->count() == 0) { m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } } /*! Opens the dialog to import data to the selected workbook, spreadsheet or matrix */ void MainWin::importFileDialog(const QString& fileName) { DEBUG("MainWin::importFileDialog()"); auto* dlg = new ImportFileDialog(this, false, fileName); // select existing container if (m_currentAspect->inherits(AspectType::Spreadsheet) || m_currentAspect->inherits(AspectType::Matrix) || m_currentAspect->inherits(AspectType::Workbook)) dlg->setCurrentIndex(m_projectExplorer->currentIndex()); else if (m_currentAspect->inherits(AspectType::Column) && m_currentAspect->parentAspect()->inherits(AspectType::Spreadsheet)) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importFileDialog() DONE"); } void MainWin::importSqlDialog() { DEBUG("MainWin::importSqlDialog()"); auto* dlg = new ImportSQLDatabaseDialog(this); // select existing container if (m_currentAspect->inherits(AspectType::Spreadsheet) || m_currentAspect->inherits(AspectType::Matrix) || m_currentAspect->inherits(AspectType::Workbook)) dlg->setCurrentIndex(m_projectExplorer->currentIndex()); else if (m_currentAspect->inherits(AspectType::Column) && m_currentAspect->parentAspect()->inherits(AspectType::Spreadsheet)) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importSqlDialog() DONE"); } void MainWin::importProjectDialog() { DEBUG("MainWin::importProjectDialog()"); ImportProjectDialog::ProjectType type; if (QObject::sender() == m_importOpjAction) type = ImportProjectDialog::ProjectOrigin; else type = ImportProjectDialog::ProjectLabPlot; auto* dlg = new ImportProjectDialog(this, type); // set current folder dlg->setCurrentFolder(m_currentFolder); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importProjectDialog() DONE"); } /*! opens the dialog for the export of the currently active worksheet, spreadsheet or matrix. */ void MainWin::exportDialog() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); if (part->exportView()) statusBar()->showMessage(i18n("%1 exported", part->name())); } void MainWin::editFitsFileDialog() { auto* editDialog = new FITSHeaderEditDialog(this); if (editDialog->exec() == QDialog::Accepted) { if (editDialog->saved()) statusBar()->showMessage(i18n("FITS files saved")); } } /*! adds a new file data source to the current project. */ void MainWin::newLiveDataSourceActionTriggered() { ImportFileDialog* dlg = new ImportFileDialog(this, true); if (dlg->exec() == QDialog::Accepted) { if (static_cast(dlg->sourceType()) == LiveDataSource::MQTT) { #ifdef HAVE_MQTT MQTTClient* mqttClient = new MQTTClient(i18n("MQTT Client%1", 1)); dlg->importToMQTT(mqttClient); mqttClient->setName(mqttClient->clientHostName()); QVector existingClients = m_project->children(AbstractAspect::Recursive); //doesn't make sense to have more MQTTClients connected to the same broker bool found = false; for (const auto* client : existingClients) { if (client->clientHostName() == mqttClient->clientHostName() && client->clientPort() == mqttClient->clientPort()) { found = true; break; } } if (!found) addAspectToProject(mqttClient); else { delete mqttClient; QMessageBox::warning(this, "Warning", "There already is a MQTTClient with this host!"); } #endif } else { LiveDataSource* dataSource = new LiveDataSource(i18n("Live data source%1", 1), false); dlg->importToLiveDataSource(dataSource, statusBar()); addAspectToProject(dataSource); } } delete dlg; } /*! * \brief adds a new dataset to the current project */ void MainWin::newDatasetActionTriggered() { ImportDatasetDialog* dlg = new ImportDatasetDialog(this); if (dlg->exec() == QDialog::Accepted) { Spreadsheet* spreadsheet = new Spreadsheet(i18n("Dataset%1", 1)); DatasetHandler* dataset = new DatasetHandler(spreadsheet); dlg->importToDataset(dataset, statusBar()); QTimer timer; timer.setSingleShot(true); QEventLoop loop; connect(dataset, &DatasetHandler::downloadCompleted, &loop, &QEventLoop::quit); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); timer.start(1500); loop.exec(); if(timer.isActive()){ timer.stop(); addAspectToProject(spreadsheet); delete dataset; } else delete dataset; } delete dlg; } void MainWin::addAspectToProject(AbstractAspect* aspect) { const QModelIndex& index = m_projectExplorer->currentIndex(); if (index.isValid()) { auto* parent = static_cast(index.internalPointer()); #ifdef HAVE_MQTT //doesn't make sense to add a new MQTTClient to an existing MQTTClient or to any of its successors QString className = parent->metaObject()->className(); MQTTClient* clientAncestor = parent->ancestor(); if (className == "MQTTClient") parent = parent->parentAspect(); else if (clientAncestor != nullptr) parent = clientAncestor->parentAspect(); #endif parent->folder()->addChild(aspect); } else m_project->addChild(aspect); } void MainWin::settingsDialog() { auto* dlg = new SettingsDialog(this); connect (dlg, &SettingsDialog::settingsChanged, this, &MainWin::handleSettingsChanges); connect (dlg, &SettingsDialog::resetWelcomeScreen, this, &MainWin::resetWelcomeScreen); dlg->exec(); } #ifdef HAVE_CANTOR_LIBS void MainWin::cantorSettingsDialog() { static KCoreConfigSkeleton* emptyConfig = new KCoreConfigSkeleton(); KConfigDialog *cantorDialog = new KConfigDialog(this, QLatin1String("Cantor Settings"), emptyConfig); for (auto* backend : Cantor::Backend::availableBackends()) if (backend->config()) //It has something to configure, so add it to the dialog cantorDialog->addPage(backend->settingsWidget(cantorDialog), backend->config(), backend->name(), backend->icon()); cantorDialog->show(); } #endif diff --git a/src/kdefrontend/MainWin.h b/src/kdefrontend/MainWin.h index 027ea33ee..c075d5a91 100644 --- a/src/kdefrontend/MainWin.h +++ b/src/kdefrontend/MainWin.h @@ -1,331 +1,335 @@ /*************************************************************************** File : MainWin.h Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2011-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Description : Main window of the application ***************************************************************************/ /*************************************************************************** * * * 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 MAINWIN_H #define MAINWIN_H +#include "backend/worksheet/plots/cartesian/CartesianPlot.h" + #include #include class AbstractAspect; class AspectTreeModel; class Folder; class ProjectExplorer; class Project; class Worksheet; class Note; class Workbook; class Datapicker; class Image; class Spreadsheet; class Matrix; class GuiObserver; class AxisDock; class CursorDock; class NoteDock; class CartesianPlotDock; class HistogramDock; class BarChartPlotDock; class CartesianPlotLegendDock; class CustomPointDock; class ColumnDock; class LiveDataDock; class MatrixDock; class ProjectDock; class SpreadsheetDock; class XYCurveDock; class XYEquationCurveDock; class XYDataReductionCurveDock; class XYDifferentiationCurveDock; class XYIntegrationCurveDock; class XYInterpolationCurveDock; class XYSmoothCurveDock; class XYFitCurveDock; class XYFourierFilterCurveDock; class XYFourierTransformCurveDock; class XYConvolutionCurveDock; class XYCorrelationCurveDock; class WorksheetDock; class LabelWidget; class DatapickerImageWidget; class DatapickerCurveWidget; class MemoryWidget; class CartesianPlot; #ifdef HAVE_CANTOR_LIBS class CantorWorksheet; class CantorWorksheetDock; #endif class QDockWidget; class QStackedWidget; class QDragEnterEvent; class QDropEvent; class QMdiArea; class QMdiSubWindow; class QToolButton; class KRecentFilesAction; class QQuickWidget; class WelcomeScreenHelper; class ImportDatasetWidget; class TreeModel; class MainWin : public KXmlGuiWindow { Q_OBJECT public: explicit MainWin(QWidget* parent = nullptr, const QString& filename = nullptr); ~MainWin() override; void showPresenter(); AspectTreeModel* model() const; Project* project() const; void addAspectToProject(AbstractAspect*); private: QMdiArea* m_mdiArea; QMdiSubWindow* m_currentSubWindow{nullptr}; Project* m_project{nullptr}; AspectTreeModel* m_aspectTreeModel{nullptr}; ProjectExplorer* m_projectExplorer{nullptr}; QDockWidget* m_projectExplorerDock{nullptr}; QDockWidget* m_propertiesDock{nullptr}; AbstractAspect* m_currentAspect{nullptr}; Folder* m_currentFolder{nullptr}; QString m_currentFileName; QString m_undoViewEmptyLabel; bool m_suppressCurrentSubWindowChangedEvent{false}; bool m_closing{false}; bool m_projectClosing{false}; bool m_autoSaveActive{false}; QTimer m_autoSaveTimer; bool m_showMemoryInfo{true}; bool m_showWelcomeScreen{true}; bool m_saveWelcomeScreen{true}; MemoryWidget* m_memoryInfoWidget{nullptr}; Qt::WindowStates m_lastWindowState; //< last window state before switching to full screen mode QMdiSubWindow* m_welcomeWindow{nullptr}; QQuickWidget* m_welcomeWidget{nullptr}; WelcomeScreenHelper* m_welcomeScreenHelper{nullptr}; ImportDatasetWidget* m_importDatasetWidget{nullptr}; KRecentFilesAction* m_recentProjectsAction; QAction* m_saveAction; QAction* m_saveAsAction; QAction* m_printAction; QAction* m_printPreviewAction; QAction* m_importFileAction; QAction* m_importSqlAction; QAction* m_importLabPlotAction; QAction* m_importOpjAction; QAction* m_exportAction; QAction* m_closeAction; QAction* m_newFolderAction; QAction* m_newWorkbookAction; QAction* m_newSpreadsheetAction; QAction* m_newMatrixAction; QAction* m_newWorksheetAction; QAction* m_newNotesAction; QAction* m_newDatasetAction; QAction* m_newLiveDataSourceAction; QAction* m_newSqlDataSourceAction; QAction* m_newScriptAction; QAction* m_newProjectAction; QAction* m_historyAction; QAction* m_undoAction; QAction* m_redoAction; QAction* m_tileWindows; QAction* m_cascadeWindows; QAction* m_newDatapickerAction; QAction* m_editFitsFileAction; //toggling doch widgets QAction* m_toggleProjectExplorerDocQAction; QAction* m_togglePropertiesDocQAction; //worksheet actions QAction* worksheetZoomInAction; QAction* worksheetZoomOutAction; QAction* worksheetZoomOriginAction; QAction* worksheetZoomFitPageHeightAction; QAction* worksheetZoomFitPageWidthAction; QAction* worksheetZoomFitSelectionAction; QAction* worksheetNavigationModeAction; QAction* worksheetZoomModeAction; QAction* worksheetSelectionModeAction; QAction* worksheetVerticalLayoutAction; QAction* worksheetHorizontalLayoutAction; QAction* worksheetGridLayoutAction; QAction* worksheetBreakLayoutAction; QAction* m_visibilityFolderAction; QAction* m_visibilitySubfolderAction; QAction* m_visibilityAllAction; QAction* m_toggleProjectExplorerDockAction; QAction* m_togglePropertiesDockAction; //Menus QMenu* m_visibilityMenu{nullptr}; QMenu* m_newMenu{nullptr}; QMenu* m_importMenu; QMenu* m_editMenu{nullptr}; //Docks QStackedWidget* stackedWidget; AxisDock* axisDock{nullptr}; QDockWidget* cursorDock{nullptr}; CursorDock* cursorWidget{nullptr}; NoteDock* notesDock{nullptr}; CartesianPlotDock* cartesianPlotDock{nullptr}; CartesianPlotLegendDock* cartesianPlotLegendDock{nullptr}; ColumnDock* columnDock{nullptr}; LiveDataDock* m_liveDataDock{nullptr}; MatrixDock* matrixDock{nullptr}; SpreadsheetDock* spreadsheetDock{nullptr}; ProjectDock* projectDock{nullptr}; XYCurveDock* xyCurveDock{nullptr}; XYEquationCurveDock* xyEquationCurveDock{nullptr}; XYDataReductionCurveDock* xyDataReductionCurveDock{nullptr}; XYDifferentiationCurveDock* xyDifferentiationCurveDock{nullptr}; XYIntegrationCurveDock* xyIntegrationCurveDock{nullptr}; XYInterpolationCurveDock* xyInterpolationCurveDock{nullptr}; XYSmoothCurveDock* xySmoothCurveDock{nullptr}; XYFitCurveDock* xyFitCurveDock{nullptr}; XYFourierFilterCurveDock* xyFourierFilterCurveDock{nullptr}; XYFourierTransformCurveDock* xyFourierTransformCurveDock{nullptr}; XYConvolutionCurveDock* xyConvolutionCurveDock{nullptr}; XYCorrelationCurveDock* xyCorrelationCurveDock{nullptr}; HistogramDock* histogramDock{nullptr}; WorksheetDock* worksheetDock{nullptr}; LabelWidget* textLabelDock{nullptr}; CustomPointDock* customPointDock{nullptr}; DatapickerImageWidget* datapickerImageDock{nullptr}; DatapickerCurveWidget* datapickerCurveDock{nullptr}; void initActions(); void initMenus(); bool warnModified(); void activateSubWindowForAspect(const AbstractAspect*) const; bool save(const QString&); // void toggleShowWidget(QWidget* widget, bool showToRight); // void toggleHideWidget(QWidget* widget, bool hideToLeft); Workbook* activeWorkbook() const; Spreadsheet* activeSpreadsheet() const; Matrix* activeMatrix() const; Worksheet* activeWorksheet() const; Datapicker* activeDatapicker() const; //Cantor #ifdef HAVE_CANTOR_LIBS QMenu* m_newCantorWorksheetMenu; CantorWorksheetDock* cantorWorksheetDock{nullptr}; CantorWorksheet* activeCantorWorksheet() const; #endif friend class GuiObserver; GuiObserver* m_guiObserver{nullptr}; protected: void closeEvent(QCloseEvent*) override; void dragEnterEvent(QDragEnterEvent*) override; void dropEvent(QDropEvent*) override; -public slots: - void showCursorDock(TreeModel* model, QVector plots); + private slots: void initGUI(const QString&); QQuickWidget* createWelcomeScreen(); void resetWelcomeScreen(); void createMdiArea(); void updateGUI(); void updateGUIOnProjectChanges(); void undo(); void redo(); bool newProject(); void openProject(); void openProject(const QString&); void openRecentProject(const QUrl&); bool closeProject(); bool saveProject(); bool saveProjectAs(); void autoSaveProject(); void print(); void printPreview(); void historyDialog(); void importFileDialog(const QString& fileName = QString()); void importSqlDialog(); void importProjectDialog(); void exportDialog(); void editFitsFileDialog(); void settingsDialog(); void projectChanged(); void colorSchemeChanged(QAction*); void openDatasetExample(); //Cantor #ifdef HAVE_CANTOR_LIBS void newCantorWorksheet(QAction* action); void cantorSettingsDialog(); #endif void newFolder(); void newWorkbook(); void newSpreadsheet(); void newMatrix(); void newWorksheet(); void newNotes(); void newDatapicker(); //TODO: void newScript(); void newLiveDataSourceActionTriggered(); void newDatasetActionTriggered(); void createContextMenu(QMenu*) const; void createFolderContextMenu(const Folder*, QMenu*) const; void handleAspectAdded(const AbstractAspect*); void handleAspectAboutToBeRemoved(const AbstractAspect*); void handleAspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*); void handleCurrentAspectChanged(AbstractAspect* ); void handleCurrentSubWindowChanged(QMdiSubWindow*); void handleShowSubWindowRequested(); void handleSettingsChanges(); void setMdiWindowVisibility(QAction*); void updateMdiWindowVisibility() const; void toggleDockWidget(QAction*); void toggleFullScreen(); void projectExplorerDockVisibilityChanged(bool); void propertiesDockVisibilityChanged(bool); + void cursorDockVisibilityChanged(bool); + + void cartesianPlotMouseModeChanged(CartesianPlot::MouseMode); }; #endif diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp index f94e01466..916aba3ea 100644 --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -1,491 +1,490 @@ /*************************************************************************** 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); m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); 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 connect(m_optionsButton, &QPushButton::clicked, this, &ImportFileDialog::toggleOptions); KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(0, 0).expandedTo(minimumSize())); 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); - source->ready(); } #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); 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 for (int i = start; i < nrNames; ++i) { Spreadsheet *spreadsheet = new Spreadsheet(i18n("Spreadsheet")); if (mode == AbstractFileFilter::Prepend) workbook->insertChildBefore(spreadsheet, sheets[0]); else workbook->addChild(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])); } } else { // single import file types // use active spreadsheet/matrix if present, else new spreadsheet Spreadsheet* spreadsheet = workbook->currentSpreadsheet(); Matrix* matrix = workbook->currentMatrix(); if (spreadsheet) filter->readDataFromFile(fileName, spreadsheet, mode); else if (matrix) filter->readDataFromFile(fileName, matrix, mode); else { spreadsheet = new Spreadsheet(i18n("Spreadsheet")); workbook->addChild(spreadsheet); filter->readDataFromFile(fileName, spreadsheet, mode); } } } statusBar->showMessage(i18n("File %1 imported in %2 seconds.", fileName, (float)timer.elapsed()/1000)); RESET_CURSOR; statusBar->removeWidget(progressBar); } 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/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index 196941210..1e3ff6528 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2111 +1,2128 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/filters.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include "JsonOptionsWidget.h" #include "ROOTOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include "MQTTConnectionManagerDialog.h" #include "MQTTSubscriptionWidget.h" #include #include #include #include #include #include #include #endif /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, bool liveDataSource, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_liveDataSource(liveDataSource) #ifdef HAVE_MQTT , m_connectTimeoutTimer(new QTimer(this)), m_subscriptionWidget(new MQTTSubscriptionWidget(this)) #endif { ui.setupUi(this); //add supported file types if (!liveDataSource) { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); ui.cbFileType->addItem(i18n("Image"), AbstractFileFilter::Image); #ifdef HAVE_HDF5 ui.cbFileType->addItem(i18n("Hierarchical Data Format 5 (HDF5)"), AbstractFileFilter::HDF5); #endif #ifdef HAVE_NETCDF ui.cbFileType->addItem(i18n("Network Common Data Format (NetCDF)"), AbstractFileFilter::NETCDF); #endif #ifdef HAVE_FITS ui.cbFileType->addItem(i18n("Flexible Image Transport System Data Format (FITS)"), AbstractFileFilter::FITS); #endif ui.cbFileType->addItem(i18n("JSON data"), AbstractFileFilter::JSON); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); //hide widgets relevant for live data reading only ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.gbUpdateOptions->hide(); } else { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); + ui.chbLinkFile->setToolTip(i18n("If this option is checked, only the link to the file is stored in the project file but not its content.")); + ui.chbRelativePath->setToolTip(i18n("If this option is checked, the relative path of the file (relative to project's folder) will be saved.")); + #ifdef HAVE_MQTT m_connectTimeoutTimer->setInterval(6000); #endif } QStringList filterItems {i18n("Automatic"), i18n("Custom")}; ui.cbFilter->addItems(filterItems); //hide options that will be activated on demand ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); setMQTTVisible(false); ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); ui.bOpen->setIcon( QIcon::fromTheme(QLatin1String("document-open")) ); ui.bFileInfo->setIcon( QIcon::fromTheme(QLatin1String("help-about")) ); ui.bManageFilters->setIcon( QIcon::fromTheme(QLatin1String("configure")) ); ui.bSaveFilter->setIcon( QIcon::fromTheme(QLatin1String("document-save")) ); ui.bRefreshPreview->setIcon( QIcon::fromTheme(QLatin1String("view-refresh")) ); ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.tvJson->setAlternatingRowColors(true); showJsonModel(false); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); auto* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // the combobox for the import path m_cbFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, ui.tePreview); m_cbFileName->setMaxItems(7); auto* gridLayout = dynamic_cast(ui.gbDataSource->layout()); if (gridLayout) gridLayout->addWidget(m_cbFileName, 1, 2, 1, 3); #ifdef HAVE_MQTT ui.cbSourceType->addItem(QLatin1String("MQTT")); m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QLatin1String("MQTT_connections"); //add subscriptions widget layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_subscriptionWidget); ui.frameSubscriptions->setLayout(layout); ui.bManageConnections->setIcon(QIcon::fromTheme(QLatin1String("network-server"))); ui.bManageConnections->setToolTip(i18n("Manage MQTT connections")); QString info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings AbstractFileFilter::FileType fileType = static_cast(conf.readEntry("Type", 0)); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } if (m_fileName.isEmpty()) { ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); m_cbFileName->setUrl(conf.readEntry("LastImportedFile", "")); QStringList urls = m_cbFileName->urls(); urls.append(conf.readXdgListEntry("LastImportedFiles")); m_cbFileName->setUrls(urls); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed } else m_cbFileName->setUrl(QUrl(m_fileName)); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate").toInt()); ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType").toInt()); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType").toInt()); updateTypeChanged(ui.cbUpdateType->currentIndex()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues").toInt()); ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleSize->setValue(conf.readEntry("SampleSize").toInt()); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval").toInt()); + ui.chbLinkFile->setCheckState((Qt::CheckState)conf.readEntry("LinkFile").toInt()); + ui.chbRelativePath->setCheckState((Qt::CheckState)conf.readEntry("RelativePath").toInt()); #ifdef HAVE_MQTT //read available MQTT connections m_initialisingMQTT = true; readMQTTConnections(); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conf.readEntry("Connection", ""))); m_initialisingMQTT = false; m_willSettings.enabled = conf.readEntry("mqttWillEnabled", m_willSettings.enabled); m_willSettings.willRetain = conf.readEntry("mqttWillRetain", m_willSettings.willRetain); m_willSettings.willUpdateType = static_cast(conf.readEntry("mqttWillUpdateType", (int)m_willSettings.willUpdateType)); m_willSettings.willMessageType = static_cast(conf.readEntry("mqttWillMessageType", (int)m_willSettings.willMessageType)); m_willSettings.willQoS = conf.readEntry("mqttWillQoS", (int)m_willSettings.willQoS); m_willSettings.willOwnMessage = conf.readEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); m_willSettings.willTimeInterval = conf.readEntry("mqttWillUpdateInterval", m_willSettings.willTimeInterval); const QString& willStatistics = conf.readEntry("mqttWillStatistics",""); const QStringList& statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts); for (auto value : statisticsList) m_willSettings.willStatistics[value.toInt()] = true; #endif //initialize the slots after all settings were set in order to avoid unneeded refreshes initSlots(); //update the status of the widgets fileTypeChanged(fileType); sourceTypeChanged(currentSourceType()); readingTypeChanged(ui.cbReadingType->currentIndex()); //all set now, refresh the preview m_suppressRefresh = false; QTimer::singleShot(0, this, [=] () { refreshPreview(); }); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", (int)currentFileType()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", m_cbFileName->currentText()); conf.writeXdgListEntry("LastImportedFiles", m_cbFileName->urls()); //live data related settings conf.writeEntry("SourceType", (int)currentSourceType()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex()); conf.writeEntry("SampleSize", ui.sbSampleSize->value()); conf.writeEntry("KeepNValues", ui.sbKeepNValues->value()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); + conf.writeEntry("LinkFile", (int)ui.chbLinkFile->checkState()); + conf.writeEntry("RelativePath", (int)ui.chbRelativePath->checkState()); #ifdef HAVE_MQTT delete m_connectTimeoutTimer; delete m_subscriptionWidget; //MQTT related settings conf.writeEntry("Connection", ui.cbConnection->currentText()); conf.writeEntry("mqttWillMessageType", static_cast(m_willSettings.willMessageType)); conf.writeEntry("mqttWillUpdateType", static_cast(m_willSettings.willUpdateType)); conf.writeEntry("mqttWillQoS", QString::number(m_willSettings.willQoS)); conf.writeEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); conf.writeEntry("mqttWillUpdateInterval", QString::number(m_willSettings.willTimeInterval)); QString willStatistics; for (int i = 0; i < m_willSettings.willStatistics.size(); ++i) { if (m_willSettings.willStatistics[i]) willStatistics += QString::number(i)+ QLatin1Char('|'); } conf.writeEntry("mqttWillStatistics", willStatistics); conf.writeEntry("mqttWillRetain", static_cast(m_willSettings.willRetain)); conf.writeEntry("mqttWillUse", static_cast(m_willSettings.enabled)); #endif // data type specific settings if (m_asciiOptionsWidget) m_asciiOptionsWidget->saveSettings(); if (m_binaryOptionsWidget) m_binaryOptionsWidget->saveSettings(); if (m_imageOptionsWidget) m_imageOptionsWidget->saveSettings(); if (m_jsonOptionsWidget) m_jsonOptionsWidget->saveSettings(); } void ImportFileWidget::initSlots() { //SLOTs for the general part of the data source configuration connect(ui.cbSourceType, static_cast(&QComboBox::currentIndexChanged), this, static_cast(&ImportFileWidget::sourceTypeChanged)); connect(m_cbFileName, &KUrlComboBox::urlActivated, this, [=](const QUrl &url){fileNameChanged(url.path());}); connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::hostChanged); connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::portChanged); connect(ui.tvJson, &QTreeView::clicked, this, &ImportFileWidget::refreshPreview); connect(ui.bOpen, &QPushButton::clicked, this, &ImportFileWidget::selectFile); connect(ui.bFileInfo, &QPushButton::clicked, this, &ImportFileWidget::fileInfoDialog); connect(ui.bSaveFilter, &QPushButton::clicked, this, &ImportFileWidget::saveFilter); connect(ui.bManageFilters, &QPushButton::clicked, this, &ImportFileWidget::manageFilters); connect(ui.cbFileType, static_cast(&KComboBox::currentIndexChanged), this, &ImportFileWidget::fileTypeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::readingTypeChanged); connect(ui.cbFilter, static_cast(&KComboBox::activated), this, &ImportFileWidget::filterChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, this, &ImportFileWidget::refreshPreview); #ifdef HAVE_MQTT connect(ui.cbConnection, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::mqttConnectionChanged); connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() { emit checkFileType(); }); connect(ui.bManageConnections, &QPushButton::clicked, this, &ImportFileWidget::showMQTTConnectionManager); connect(ui.bLWT, &QPushButton::clicked, this, &ImportFileWidget::showWillSettings); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, this, &ImportFileWidget::mqttSubscribe); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::MQTTUnsubscribeFromTopic, this, &ImportFileWidget::unsubscribeFromTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &ImportFileWidget::enableWill); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::subscriptionChanged, this, &ImportFileWidget::refreshPreview); #endif } void ImportFileWidget::showAsciiHeaderOptions(bool b) { if (m_asciiOptionsWidget) m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showJsonModel(bool b) { ui.tvJson->setVisible(b); ui.lField->setVisible(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { return m_cbFileName->currentText(); } QString ImportFileWidget::selectedObject() const { const QString& path = fileName(); //determine the file name only QString name = path.right(path.length() - path.lastIndexOf(QDir::separator()) - 1); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); //for multi-dimensional formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedNames(); if (hdf5Names.size()) name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/' } else if (format == AbstractFileFilter::NETCDF) { const QStringList& names = m_netcdfOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } else if (format == AbstractFileFilter::FITS) { const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; } else if (format == AbstractFileFilter::ROOT) { const QStringList& names = m_rootOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } return name; } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { AbstractFileFilter::FileType fileType = currentFileType(); auto updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = currentSourceType(); auto readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( fileName() ); source->setFileType(fileType); currentFileFilter(); source->setFilter(m_currentFilter.release()); // pass ownership of the filter to the LiveDataSource source->setSourceType(sourceType); - source->setReadingType(readingType); - - if (updateType == LiveDataSource::UpdateType::TimeInterval) - source->setUpdateInterval(ui.sbUpdateInterval->value()); - else - source->setFileWatched(true); - - source->setKeepNValues(ui.sbKeepNValues->value()); - source->setUpdateType(updateType); - - if (readingType != LiveDataSource::ReadingType::TillEnd) - source->setSampleSize(ui.sbSampleSize->value()); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName(fileName()); source->setFileLinked(ui.chbLinkFile->isChecked()); + source->setUseRelativePath(ui.chbRelativePath->isChecked()); break; case LiveDataSource::SourceType::LocalSocket: source->setFileName(fileName()); source->setLocalSocketName(fileName()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; case LiveDataSource::SourceType::MQTT: break; default: break; } + + //reading options + source->setReadingType(readingType); + source->setKeepNValues(ui.sbKeepNValues->value()); + source->setUpdateType(updateType); + if (updateType == LiveDataSource::UpdateType::TimeInterval) + source->setUpdateInterval(ui.sbUpdateInterval->value()); + + if (readingType != LiveDataSource::ReadingType::TillEnd) + source->setSampleSize(ui.sbSampleSize->value()); } #ifdef HAVE_MQTT /*! saves the settings to the MQTTClient \c client. */ void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const { DEBUG("ImportFileWidget::saveMQTTSettings"); MQTTClient::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); MQTTClient::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); client->setComment(fileName()); currentFileFilter(); client->setFilter(static_cast(m_currentFilter.release())); // pass ownership of the filter to MQTTClient client->setReadingType(readingType); if (updateType == MQTTClient::UpdateType::TimeInterval) client->setUpdateInterval(ui.sbUpdateInterval->value()); client->setKeepNValues(ui.sbKeepNValues->value()); client->setUpdateType(updateType); if (readingType != MQTTClient::ReadingType::TillEnd) client->setSampleSize(ui.sbSampleSize->value()); client->setMQTTClientHostPort(m_client->hostname(), m_client->port()); KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); bool useID = group.readEntry("UseID").toUInt(); bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); client->setMQTTUseAuthentication(useAuthentication); if (useAuthentication) client->setMQTTClientAuthentication(m_client->username(), m_client->password()); client->setMQTTUseID(useID); if (useID) client->setMQTTClientId(m_client->clientId()); for (int i = 0; i < m_mqttSubscriptions.count(); ++i) client->addInitialMQTTSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); const bool retain = group.readEntry("Retain").toUInt(); client->setMQTTRetain(retain); if (m_willSettings.enabled) client->setWillSettings(m_willSettings); } #endif /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentData().toInt()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = currentFileType(); if (m_currentFilter && m_currentFilter->type() != fileType) m_currentFilter.reset(); switch (fileType) { case AbstractFileFilter::Ascii: { DEBUG(" ASCII"); if (!m_currentFilter) m_currentFilter.reset(new AsciiFilter); auto filter = static_cast(m_currentFilter.get()); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_asciiOptionsWidget) m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings(ui.cbFilter->currentText()); //save the data portion to import filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::Binary: { DEBUG(" Binary"); if (!m_currentFilter) m_currentFilter.reset(new BinaryFilter); auto filter = static_cast(m_currentFilter.get()); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_binaryOptionsWidget) m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::Image: { DEBUG(" Image"); if (!m_currentFilter) m_currentFilter.reset(new ImageFilter); auto filter = static_cast(m_currentFilter.get()); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::currentFileFilter(): HDF5"); if (!m_currentFilter) m_currentFilter.reset(new HDF5Filter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedHDF5Names(); QDEBUG("ImportFileWidget::currentFileFilter(): selected HDF5 names =" << names); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); DEBUG("ImportFileWidget::currentFileFilter(): OK"); break; } case AbstractFileFilter::NETCDF: { DEBUG(" NETCDF"); if (!m_currentFilter) m_currentFilter.reset(new NetCDFFilter); auto filter = static_cast(m_currentFilter.get()); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::FITS: { DEBUG(" FITS"); if (!m_currentFilter) m_currentFilter.reset(new FITSFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::JSON: { DEBUG(" JSON"); if (!m_currentFilter) m_currentFilter.reset(new JsonFilter); auto filter = static_cast(m_currentFilter.get()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::ROOT: { DEBUG(" ROOT"); if (!m_currentFilter) m_currentFilter.reset(new ROOTFilter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedROOTNames(); if (!names.isEmpty()) filter->setCurrentObject(names.first()); filter->setStartRow(m_rootOptionsWidget->startRow()); filter->setEndRow(m_rootOptionsWidget->endRow()); filter->setColumns(m_rootOptionsWidget->columns()); break; } case AbstractFileFilter::NgspiceRawAscii: { DEBUG(" NgspiceRawAscii"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawAsciiFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::NgspiceRawBinary: { DEBUG(" NgspiceRawBinary"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawBinaryFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } } return m_currentFilter.get(); } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("ImportFileWidget")); const QString& dir = conf.readEntry(QLatin1String("LastDir"), ""); const QString& path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); if (path.isEmpty()) //cancel was clicked in the file-dialog return; int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry(QLatin1String("LastDir"), newDir); } //process all events after the FileDialog was closed to repaint the widget //before we start calculating the preview QApplication::processEvents(QEventLoop::AllEvents, 0); QStringList urls = m_cbFileName->urls(); urls.insert(0, "file://"+path); // add type of path m_cbFileName->setUrls(urls); m_cbFileName->setCurrentText(urls.first()); fileNameChanged(path); // why do I have to call this function separately } /*! hides the MQTT related items of the widget */ void ImportFileWidget::setMQTTVisible(bool visible) { ui.lConnections->setVisible(visible); ui.cbConnection->setVisible(visible); ui.bManageConnections->setVisible(visible); //topics if (ui.cbConnection->currentIndex() != -1 && visible) { ui.lTopics->setVisible(true); ui.frameSubscriptions->setVisible(true); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); #endif } else { ui.lTopics->setVisible(false); ui.frameSubscriptions->setVisible(false); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(false); m_subscriptionWidget->makeVisible(false); #endif } //will message ui.lLWT->setVisible(visible); ui.bLWT->setVisible(visible); } #ifdef HAVE_MQTT /*! * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic, * returns \c false otherwise. */ bool ImportFileWidget::isMqttValid() { if (!m_client) return false; bool connected = (m_client->state() == QMqttClient::ClientState::Connected); bool subscribed = (m_subscriptionWidget->subscriptionCount() > 0); bool fileTypeOk = false; if (this->currentFileType() == AbstractFileFilter::FileType::Ascii) fileTypeOk = true; return connected && subscribed && fileTypeOk; } /*! *\brief Unsubscribes from the given topic, and removes any data connected to it * * \param topicName the name of a topic we want to unsubscribe from */ void ImportFileWidget::unsubscribeFromTopic(const QString& topicName, QVector children) { if (topicName.isEmpty()) return; QMqttTopicFilter filter{topicName}; m_client->unsubscribe(filter); for (int i = 0; i< m_mqttSubscriptions.count(); ++i) if (m_mqttSubscriptions[i]->topic().filter() == topicName) { m_mqttSubscriptions.remove(i); break; } QMapIterator j(m_lastMessage); while (j.hasNext()) { j.next(); if (MQTTSubscriptionWidget::checkTopicContains(topicName, j.key().name())) m_lastMessage.remove(j.key()); } for (int i = 0; i < m_subscribedTopicNames.size(); ++i) { if (MQTTSubscriptionWidget::checkTopicContains(topicName, m_subscribedTopicNames[i])) { m_subscribedTopicNames.remove(i); i--; } } if (m_willSettings.willTopic == topicName) { if (m_subscriptionWidget->subscriptionCount() > 0) m_willSettings.willTopic = children[0]->text(0); else m_willSettings.willTopic.clear(); } //signals that there was a change among the subscribed topics emit subscriptionsChanged(); refreshPreview(); } #endif /************** SLOTS **************************************************************/ QString absolutePath(const QString& fileName) { #ifndef HAVE_WINDOWS // make absolute path // FIXME if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) return QDir::homePath() + QDir::separator() + fileName; #endif return fileName; } /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { DEBUG("ImportFileWidget::fileNameChanged()"); const QString fileName = absolutePath(name); bool fileExists = QFile::exists(fileName); if (fileExists) m_cbFileName->setStyleSheet(QString()); else m_cbFileName->setStyleSheet("QComboBox{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.gbUpdateOptions->setEnabled(fileExists); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tePreview->clear(); m_twPreview->clear(); initOptionsWidget(); emit fileNameChanged(); return; } if (currentSourceType() == LiveDataSource::FileOrPipe) { const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { // automatically select a new file type if (ui.cbFileType->currentIndex() != i) { ui.cbFileType->setCurrentIndex(i); // will call the slot fileTypeChanged which updates content and preview emit fileNameChanged(); return; } else { initOptionsWidget(); updateContent(fileName); break; } } } } emit fileNameChanged(); refreshPreview(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined fllter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int index) { Q_UNUSED(index); AbstractFileFilter::FileType fileType = currentFileType(); DEBUG("ImportFileWidget::fileTypeChanged " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); initOptionsWidget(); //default ui.lFilter->show(); ui.cbFilter->show(); //different file types show different number of tabs in ui.tabWidget. //when switching from the previous file type we re-set the tab widget to its original state //and remove/add the required tabs further below for (int i = 0; icount(); ++i) ui.tabWidget->removeTab(0); ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); if (!m_liveDataSource) ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); ui.lPreviewLines->show(); ui.sbPreviewLines->show(); ui.lStartColumn->show(); ui.sbStartColumn->show(); ui.lEndColumn->show(); ui.sbEndColumn->show(); showJsonModel(false); switch (fileType) { case AbstractFileFilter::Ascii: break; case AbstractFileFilter::Binary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; case AbstractFileFilter::ROOT: ui.tabWidget->removeTab(1); // falls through case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: ui.lFilter->hide(); ui.cbFilter->hide(); // hide global preview tab. we have our own ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Image: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lPreviewLines->hide(); ui.sbPreviewLines->hide(); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); ui.tabWidget->removeTab(0); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::JSON: ui.lFilter->hide(); ui.cbFilter->hide(); showJsonModel(true); break; default: DEBUG("unknown file type"); } int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); if (currentSourceType() == LiveDataSource::FileOrPipe) { QString tempFileName = fileName(); const QString& fileName = absolutePath(tempFileName); if (QFile::exists(fileName)) updateContent(fileName); } //for file types other than ASCII and binary we support re-reading the whole file only //select "read whole file" and deactivate the combobox if (m_liveDataSource && (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary)) { ui.cbReadingType->setCurrentIndex(LiveDataSource::ReadingType::WholeFile); ui.cbReadingType->setEnabled(false); } else ui.cbReadingType->setEnabled(true); refreshPreview(); } // file type specific option widgets void ImportFileWidget::initOptionsWidget() { DEBUG("ImportFileWidget::initOptionsWidget for " << ENUM_TO_STRING(AbstractFileFilter, FileType, currentFileType())); switch (currentFileType()) { case AbstractFileFilter::Ascii: { if (!m_asciiOptionsWidget) { QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); m_asciiOptionsWidget->loadSettings(); ui.swOptions->addWidget(asciiw); } //for MQTT topics we don't allow to set the vector names since the different topics //can have different number of columns bool isMQTT = (currentSourceType() == LiveDataSource::MQTT); m_asciiOptionsWidget->showAsciiHeaderOptions(!isMQTT); m_asciiOptionsWidget->showTimestampOptions(isMQTT); ui.swOptions->setCurrentWidget(m_asciiOptionsWidget->parentWidget()); break; } case AbstractFileFilter::Binary: if (!m_binaryOptionsWidget) { QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->addWidget(binaryw); m_binaryOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_binaryOptionsWidget->parentWidget()); break; case AbstractFileFilter::Image: if (!m_imageOptionsWidget) { QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->addWidget(imagew); m_imageOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_imageOptionsWidget->parentWidget()); break; case AbstractFileFilter::HDF5: if (!m_hdf5OptionsWidget) { QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->addWidget(hdf5w); } else m_hdf5OptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_hdf5OptionsWidget->parentWidget()); break; case AbstractFileFilter::NETCDF: if (!m_netcdfOptionsWidget) { QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); } else m_netcdfOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_netcdfOptionsWidget->parentWidget()); break; case AbstractFileFilter::FITS: if (!m_fitsOptionsWidget) { QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->addWidget(fitsw); } else m_fitsOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_fitsOptionsWidget->parentWidget()); break; case AbstractFileFilter::JSON: if (!m_jsonOptionsWidget) { QWidget* jsonw = new QWidget(); m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); ui.tvJson->setModel(m_jsonOptionsWidget->model()); ui.swOptions->addWidget(jsonw); m_jsonOptionsWidget->loadSettings(); } else m_jsonOptionsWidget->clearModel(); ui.swOptions->setCurrentWidget(m_jsonOptionsWidget->parentWidget()); showJsonModel(true); break; case AbstractFileFilter::ROOT: if (!m_rootOptionsWidget) { QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->addWidget(rootw); } else m_rootOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_rootOptionsWidget->parentWidget()); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedExtensions(); } const QStringList ImportFileWidget::selectedROOTNames() const { return m_rootOptionsWidget->selectedNames(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = fileName().split(';'); auto* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats AbstractFileFilter::FileType fileType = currentFileType(); if (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary) { ui.swOptions->setEnabled(true); return; } if (index == 0) { // "automatic" ui.swOptions->setEnabled(false); ui.bSaveFilter->setEnabled(false); } else if (index == 1) { //custom ui.swOptions->setEnabled(true); ui.bSaveFilter->setEnabled(true); } else { // predefined filter settings were selected. //load and show them in the GUI. //TODO } } void ImportFileWidget::refreshPreview() { if (m_suppressRefresh) return; DEBUG("ImportFileWidget::refreshPreview()"); WAIT_CURSOR; QString tempFileName = fileName(); QString fileName = absolutePath(tempFileName); AbstractFileFilter::FileType fileType = currentFileType(); LiveDataSource::SourceType sourceType = currentSourceType(); int lines = ui.sbPreviewLines->value(); if (sourceType == LiveDataSource::SourceType::FileOrPipe) DEBUG("refreshPreview(): file name = " << fileName.toStdString()); // generic table widget if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary || fileType == AbstractFileFilter::JSON || fileType == AbstractFileFilter::NgspiceRawAscii || fileType == AbstractFileFilter::NgspiceRawBinary) m_twPreview->show(); else m_twPreview->hide(); bool ok = true; QTableWidget* tmpTableWidget = m_twPreview; QVector importedStrings; QStringList vectorNameList; QVector columnModes; DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); switch (fileType) { case AbstractFileFilter::Ascii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, sourceType)); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: { importedStrings = filter->preview(fileName, lines); break; } case LiveDataSource::SourceType::LocalSocket: { QLocalSocket lsocket{this}; DEBUG("Local socket: CONNECT PREVIEW"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { DEBUG("connected to local socket " << fileName.toStdString()); if (lsocket.waitForReadyRead()) importedStrings = filter->preview(lsocket); DEBUG("Local socket: DISCONNECT PREVIEW"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); break; } case LiveDataSource::SourceType::NetworkTcpSocket: { QTcpSocket tcpSocket{this}; tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); if (tcpSocket.waitForConnected()) { DEBUG("connected to TCP socket"); if ( tcpSocket.waitForReadyRead() ) importedStrings = filter->preview(tcpSocket); tcpSocket.disconnectFromHost(); } else DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); break; } case LiveDataSource::SourceType::NetworkUdpSocket: { QUdpSocket udpSocket{this}; DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.bind(QHostAddress(host()), port().toInt()); udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); if (udpSocket.waitForConnected()) { DEBUG(" connected to UDP socket " << host().toStdString() << ':' << port().toInt()); if (!udpSocket.waitForReadyRead(2000) ) DEBUG(" ERROR: not ready for read after 2 sec"); if (udpSocket.hasPendingDatagrams()) { DEBUG(" has pending data"); } else { DEBUG(" has no pending data"); } importedStrings = filter->preview(udpSocket); DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.disconnectFromHost(); } else DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; DEBUG(" Port: " << serialPort().toStdString() << ", Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() << ',' << sPort.stopBits()); sPort.setPortName(serialPort()); sPort.setBaudRate(baudRate()); if (sPort.open(QIODevice::ReadOnly)) { if (sPort.waitForReadyRead(2000)) importedStrings = filter->preview(sPort); else DEBUG(" ERROR: not ready for read after 2 sec"); sPort.close(); } else DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT //show the preview for the currently selected topic auto* item = m_subscriptionWidget->currentItem(); if (item && item->childCount() == 0) { //only preview if the lowest level (i.e. a topic) is selected const QString& topicName = item->text(0); auto i = m_lastMessage.find(topicName); if (i != m_lastMessage.end()) importedStrings = filter->preview(i.value().payload().data()); else importedStrings << QStringList{i18n("No data arrived yet for the selected topic")}; } #endif break; } } vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::Binary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); break; } case AbstractFileFilter::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::refreshPreview: HDF5"); auto filter = static_cast(currentFileFilter()); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, nullptr, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case AbstractFileFilter::NETCDF: { auto filter = static_cast(currentFileFilter()); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, nullptr, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case AbstractFileFilter::FITS: { auto filter = static_cast(currentFileFilter()); lines = m_fitsOptionsWidget->lines(); QString extensionName = m_fitsOptionsWidget->extensionName(&ok); if (!extensionName.isEmpty()) { DEBUG(" extension name = " << extensionName.toStdString()); fileName = extensionName; } DEBUG(" file name = " << fileName.toStdString()); bool readFitsTableToMatrix; importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); emit checkedFitsTableToMatrix(readFitsTableToMatrix); tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } case AbstractFileFilter::JSON: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); importedStrings = filter->preview(fileName); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::ROOT: { auto filter = static_cast(currentFileFilter()); lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(fileName)); importedStrings = filter->previewCurrentObject( fileName, m_rootOptionsWidget->startRow(), qMin(m_rootOptionsWidget->startRow() + m_rootOptionsWidget->lines() - 1, m_rootOptionsWidget->endRow()) ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names vectorNameList = importedStrings.last(); importedStrings.removeLast(); columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); break; } case AbstractFileFilter::NgspiceRawAscii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::NgspiceRawBinary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if ( !importedStrings.isEmpty() ) { if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); auto* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { const int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { auto* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else m_fileEmpty = true; emit previewRefreshed(); RESET_CURSOR; } void ImportFileWidget::updateContent(const QString& fileName) { QDEBUG("ImportFileWidget::updateContent(): file name = " << fileName); if (auto filter = currentFileFilter()) { switch (filter->type()) { case AbstractFileFilter::HDF5: m_hdf5OptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::NETCDF: m_netcdfOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::FITS: #ifdef HAVE_FITS m_fitsOptionsWidget->updateContent(static_cast(filter), fileName); #endif break; case AbstractFileFilter::ROOT: m_rootOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::JSON: m_jsonOptionsWidget->loadDocument(fileName); ui.tvJson->setExpanded( m_jsonOptionsWidget->model()->index(0, 0), true); //expand the root node break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } } void ImportFileWidget::updateTypeChanged(int idx) { const auto UpdateType = static_cast(idx); switch (UpdateType) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } } void ImportFileWidget::readingTypeChanged(int idx) { const auto readingType = static_cast(idx); const LiveDataSource::SourceType sourceType = currentSourceType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } if (readingType == LiveDataSource::ReadingType::WholeFile) { ui.lKeepLastValues->hide(); ui.sbKeepNValues->hide(); } else { ui.lKeepLastValues->show(); ui.sbKeepNValues->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { const auto sourceType = static_cast(idx); // enable/disable "on new data"-option const auto* model = qobject_cast(ui.cbUpdateType->model()); QStandardItem* item = model->item(LiveDataSource::UpdateType::NewData); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); m_cbFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); + ui.lRelativePath->show(); + ui.chbRelativePath->show(); ui.chbLinkFile->show(); //option for sample size are available for "continuously fixed" and "from end" reading options if (ui.cbReadingType->currentIndex() < 2) { ui.lSampleSize->show(); ui.sbSampleSize->show(); } else { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); fileNameChanged(fileName()); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); + ui.lRelativePath->hide(); + ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); m_cbFileName->show(); + ui.bFileInfo->hide(); ui.bOpen->show(); + ui.lRelativePath->hide(); + ui.chbRelativePath->hide(); + ui.lSampleSize->hide(); ui.sbSampleSize->hide(); - - ui.bFileInfo->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); + ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); + ui.lRelativePath->hide(); + ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::MQTT: #ifdef HAVE_MQTT item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //for MQTT we read ascii data only, hide the file type options for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == AbstractFileFilter::Ascii) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } ui.cbFileType->hide(); ui.lFileType->hide(); ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); + ui.lRelativePath->hide(); + ui.chbRelativePath()->hide(); ui.chbLinkFile->hide(); setMQTTVisible(true); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); //in case there are already connections defined, //show the available topics for the currently selected connection mqttConnectionChanged(); #endif break; } //deactivate/activate options that are specific to file of pipe sources only auto* typeModel = qobject_cast(ui.cbFileType->model()); if (sourceType != LiveDataSource::FileOrPipe) { //deactivate file types other than ascii and binary for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (ui.cbFileType->currentIndex() > 1) ui.cbFileType->setCurrentIndex(1); //"whole file" read option is available for file or pipe only, disable it typeModel = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = typeModel->item(LiveDataSource::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); //"update options" groupbox can be deactivated for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. ui.gbUpdateOptions->setEnabled(true); } else { for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //enable "whole file" item for file or pipe typeModel = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = typeModel->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } emit sourceTypeChanged(); refreshPreview(); } #ifdef HAVE_MQTT /*! *\brief called when a different MQTT connection is selected in the connection ComboBox. * connects to the MQTT broker according to the connection settings. */ void ImportFileWidget::mqttConnectionChanged() { if (m_initialisingMQTT || ui.cbConnection->currentIndex() == -1) return; WAIT_CURSOR; //disconnected from the broker that was selected before, if this is the case if (m_client && m_client->state() == QMqttClient::ClientState::Connected) { emit MQTTClearTopics(); disconnect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); QDEBUG("Disconnecting from " << m_client->hostname()); m_client->disconnectFromHost(); delete m_client; } //determine the connection settings for the new broker and initialize the mqtt client KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); m_client = new QMqttClient; connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect); connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived); connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged); m_client->setHostname(group.readEntry("Host")); m_client->setPort(group.readEntry("Port").toUInt()); const bool useID = group.readEntry("UseID").toUInt(); if (useID) m_client->setClientId(group.readEntry("ClientID")); const bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); if (useAuthentication) { m_client->setUsername(group.readEntry("UserName")); m_client->setPassword(group.readEntry("Password")); } //connect to the selected broker QDEBUG("Connect to " << m_client->hostname() << ":" << m_client->port()); m_connectTimeoutTimer->start(); m_client->connectToHost(); } /*! *\brief called when the client connects to the broker successfully. * subscribes to every topic (# wildcard) in order to later list every available topic */ void ImportFileWidget::onMqttConnect() { if (m_client->error() == QMqttClient::NoError) { m_connectTimeoutTimer->stop(); ui.frameSubscriptions->setVisible(true); m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); if (!m_client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); } emit subscriptionsChanged(); RESET_CURSOR; } /*! *\brief called when the client disconnects from the broker successfully * removes every information about the former connection */ void ImportFileWidget::onMqttDisconnect() { DEBUG("Disconected from " << m_client->hostname().toStdString()); m_connectTimeoutTimer->stop(); ui.lTopics->hide(); ui.frameSubscriptions->hide(); ui.lLWT->hide(); ui.bLWT->hide(); ui.cbConnection->setItemText(ui.cbConnection->currentIndex(), ui.cbConnection->currentText() + " " + i18n("(Disconnected)")); emit subscriptionsChanged(); RESET_CURSOR; QMessageBox::critical(this, i18n("Disconnected"), i18n("Disconnected from the broker '%1' before the connection was successful.", m_client->hostname())); } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics */ void ImportFileWidget::mqttSubscribe(const QString& name, uint QoS) { const QMqttTopicFilter filter {name}; QMqttSubscription* tempSubscription = m_client->subscribe(filter, static_cast(QoS) ); if (tempSubscription) { m_mqttSubscriptions.push_back(tempSubscription); connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void ImportFileWidget::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message); // qDebug()<<"received " << topic.name(); if (m_addedTopics.contains(topic.name())) return; m_addedTopics.push_back(topic.name()); m_subscriptionWidget->setTopicTreeText(i18n("Available (%1)", m_addedTopics.size())); QStringList name; QString rootName; const QChar sep = '/'; if (topic.name().contains(sep)) { const QStringList& list = topic.name().split(sep, QString::SkipEmptyParts); if (!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); int topItemIdx = -1; //check whether the first level of the topic can be found in twTopics for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if (topItemIdx < 0) { QTreeWidgetItem* currentItem = new QTreeWidgetItem(name); m_subscriptionWidget->addTopic(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { QTreeWidgetItem* currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for (int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if (childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if (!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for (; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topic.name(); name.append(topic.name()); m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); if (!subscriptionName.isEmpty()) { if (rootName == subscriptionName.first()) { QVector subscriptions; for(int i = 0; i < m_mqttSubscriptions.size(); ++i) subscriptions.push_back(m_mqttSubscriptions[i]->topic().filter()); emit updateSubscriptionTree(subscriptions); break; } } } //signals that a newTopic was added, in order to fill the completer of leTopics emit newTopic(rootName); } /*! *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) */ void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { QDEBUG("message received from: " << msg.topic().name()); if (!m_subscribedTopicNames.contains(msg.topic().name())) m_subscribedTopicNames.push_back(msg.topic().name()); //update the last message for the topic m_lastMessage[msg.topic()] = msg; } /*! *\brief called when the clientError of the MQTT client changes * * \param clientError the current error of the client */ void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { switch (clientError) { case QMqttClient::BadUsernameOrPassword: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("Wrong username or password")); break; case QMqttClient::IdRejected: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("The client ID wasn't accepted")); break; case QMqttClient::ServerUnavailable: QMessageBox::critical(this, i18n("Server unavailable"), i18n("The broker couldn't be reached.")); break; case QMqttClient::NotAuthorized: QMessageBox::critical(this, i18n("Not authorized"), i18n("The client is not authorized to connect.")); break; case QMqttClient::UnknownError: QMessageBox::critical(this, i18n("Unknown MQTT error"), i18n("An unknown error occurred.")); break; case QMqttClient::NoError: case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ProtocolViolation: case QMqttClient::Mqtt5SpecificError: break; default: break; } } /*! *\brief called when m_connectTimeoutTimer ticks, * meaning that the client couldn't connect to the broker in 5 seconds * disconnects the client, stops the timer, and warns the user */ void ImportFileWidget::mqttConnectTimeout() { m_connectionTimedOut = true; m_client->disconnectFromHost(); m_connectTimeoutTimer->stop(); RESET_CURSOR; QMessageBox::warning(this, i18n("Warning"), i18n("Connecting to the given broker timed out! Try changing the settings")); } /*! Shows the MQTT connection manager where the connections are created and edited. The selected connection is selected in the connection combo box in this widget. */ void ImportFileWidget::showMQTTConnectionManager() { bool previousConnectionChanged = false; MQTTConnectionManagerDialog* dlg = new MQTTConnectionManagerDialog(this, ui.cbConnection->currentText(), previousConnectionChanged); if (dlg->exec() == QDialog::Accepted) { //re-read the available connections to be in sync with the changes in MQTTConnectionManager m_initialisingMQTT = true; const QString& prevConn = ui.cbConnection->currentText(); ui.cbConnection->clear(); readMQTTConnections(); m_initialisingMQTT = false; //select the connection the user has selected in MQTTConnectionManager const QString& conn = dlg->connection(); int index = ui.cbConnection->findText(conn); if (conn != prevConn) {//Current connection isn't the previous one if (ui.cbConnection->currentIndex() != index) ui.cbConnection->setCurrentIndex(index); else mqttConnectionChanged(); } else if (dlg->initialConnectionChanged()) {//Current connection is the same with previous one but it changed if (ui.cbConnection->currentIndex() == index) mqttConnectionChanged(); else ui.cbConnection->setCurrentIndex(index); } else { //Previous connection wasn't changed m_initialisingMQTT = true; ui.cbConnection->setCurrentIndex(index); m_initialisingMQTT = false; } } delete dlg; } /*! loads all available saved MQTT nconnections */ void ImportFileWidget::readMQTTConnections() { DEBUG("ImportFileWidget: reading available MQTT connections"); KConfig config(m_configPath, KConfig::SimpleConfig); for (const auto& name : config.groupList()) ui.cbConnection->addItem(name); } /*! * \brief Shows the mqtt will settings widget, which allows the user to modify the will settings */ void ImportFileWidget::showWillSettings() { QMenu menu; QVector children; for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) MQTTSubscriptionWidget::findSubscriptionLeafChildren(children, m_subscriptionWidget->topLevelSubscription(i)); QVector topics; for (int i = 0; i < children.size(); ++i) topics.append(children[i]->text(0)); MQTTWillSettingsWidget willSettingsWidget(&menu, m_willSettings, topics); connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() { m_willSettings = willSettingsWidget.will(); menu.close(); }); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettingsWidget); menu.addAction(widgetAction); const QPoint pos(ui.bLWT->sizeHint().width(),ui.bLWT->sizeHint().height()); menu.exec(ui.bLWT->mapToGlobal(pos)); } void ImportFileWidget::enableWill(bool enable) { if(enable) { if(!ui.bLWT->isEnabled()) ui.bLWT->setEnabled(enable); } else ui.bLWT->setEnabled(enable); } #endif diff --git a/src/kdefrontend/datasources/ImportProjectDialog.cpp b/src/kdefrontend/datasources/ImportProjectDialog.cpp index 7967d5938..80ea381f7 100644 --- a/src/kdefrontend/datasources/ImportProjectDialog.cpp +++ b/src/kdefrontend/datasources/ImportProjectDialog.cpp @@ -1,425 +1,438 @@ /*************************************************************************** File : ImportProjectDialog.cpp Project : LabPlot Description : import project dialog -------------------------------------------------------------------- Copyright : (C) 2017-2019 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportProjectDialog.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/datasources/projects/LabPlotProjectParser.h" #ifdef HAVE_LIBORIGIN #include "backend/datasources/projects/OriginProjectParser.h" #endif #include "kdefrontend/MainWin.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportProjectDialog \brief Dialog for importing project files. \ingroup kdefrontend */ ImportProjectDialog::ImportProjectDialog(MainWin* parent, ProjectType type) : QDialog(parent), m_mainWin(parent), m_projectType(type), m_aspectTreeModel(new AspectTreeModel(parent->project())) { auto* vLayout = new QVBoxLayout(this); //main widget QWidget* mainWidget = new QWidget(this); ui.setupUi(mainWidget); ui.chbUnusedObjects->hide(); vLayout->addWidget(mainWidget); ui.tvPreview->setAnimated(true); ui.tvPreview->setAlternatingRowColors(true); ui.tvPreview->setSelectionBehavior(QAbstractItemView::SelectRows); ui.tvPreview->setSelectionMode(QAbstractItemView::ExtendedSelection); ui.tvPreview->setUniformRowHeights(true); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); m_cbAddTo = new TreeViewComboBox(ui.gbImportTo); m_cbAddTo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); ui.gbImportTo->layout()->addWidget(m_cbAddTo); QList list{AspectType::Folder}; m_cbAddTo->setTopLevelClasses(list); m_aspectTreeModel->setSelectableAspects(list); m_cbAddTo->setModel(m_aspectTreeModel); m_bNewFolder = new QPushButton(ui.gbImportTo); m_bNewFolder->setIcon(QIcon::fromTheme("list-add")); m_bNewFolder->setToolTip(i18n("Add new folder")); ui.gbImportTo->layout()->addWidget(m_bNewFolder); //dialog buttons m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); vLayout->addWidget(m_buttonBox); //ok-button is only enabled if some project objects were selected (s.a. ImportProjectDialog::selectionChanged()) m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); //Signals/Slots connect(ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString))); connect(ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile())); connect(m_bNewFolder, SIGNAL(clicked()), this, SLOT(newFolder())); connect(ui.chbUnusedObjects, &QCheckBox::stateChanged, this, &ImportProjectDialog::refreshPreview); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QString title; switch (m_projectType) { case (ProjectLabPlot): m_projectParser = new LabPlotProjectParser(); title = i18nc("@title:window", "Import LabPlot Project"); break; case (ProjectOrigin): #ifdef HAVE_LIBORIGIN m_projectParser = new OriginProjectParser(); title = i18nc("@title:window", "Import Origin Project"); #endif break; } //dialog title and icon setWindowTitle(title); setWindowIcon(QIcon::fromTheme("document-import")); + //"What's this?" texts + QString info = i18n("Specify the file where the project content has to be imported from."); + ui.leFileName->setWhatsThis(info); + + info = i18n("Select one or several objects to be imported into the current project.\n" + "Note, all children of the selected objects as well as all the dependent objects will be automatically selected.\n" + "To import the whole project, select the top-level project node." + ); + ui.tvPreview->setWhatsThis(info); + + info = i18n("Specify the target folder in the current project where the selected objects have to be imported into."); + m_cbAddTo->setWhatsThis(info); + //restore saved settings if available create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(300, 0).expandedTo(minimumSize())); QString lastImportedFile; switch (m_projectType) { case (ProjectLabPlot): lastImportedFile = QLatin1String("LastImportedLabPlotProject"); break; case (ProjectOrigin): lastImportedFile = QLatin1String("LastImportedOriginProject"); break; } QApplication::processEvents(QEventLoop::AllEvents, 100); ui.leFileName->setText(conf.readEntry(lastImportedFile, "")); } ImportProjectDialog::~ImportProjectDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); QString lastImportedFile; switch (m_projectType) { case (ProjectLabPlot): lastImportedFile = QLatin1String("LastImportedLabPlotProject"); break; case (ProjectOrigin): lastImportedFile = QLatin1String("LastImportedOriginProject"); break; } conf.writeEntry(lastImportedFile, ui.leFileName->text()); } void ImportProjectDialog::setCurrentFolder(const Folder* folder) { m_cbAddTo->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(folder)); } void ImportProjectDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportProjectDialog::importTo()"); //determine the selected objects, convert the model indexes to string pathes const QModelIndexList& indexes = ui.tvPreview->selectionModel()->selectedIndexes(); QStringList selectedPathes; for (int i = 0; i < indexes.size()/4; ++i) { QModelIndex index = indexes.at(i*4); const auto* aspect = static_cast(index.internalPointer()); //path of the current aspect and the pathes of all aspects it depends on selectedPathes << aspect->path(); QDEBUG(" aspect path: " << aspect->path()); for (const auto* depAspect : aspect->dependsOn()) selectedPathes << depAspect->path(); } selectedPathes.removeDuplicates(); Folder* targetFolder = static_cast(m_cbAddTo->currentModelIndex().internalPointer()); //check whether the selected pathes already exist in the target folder and warn the user const QString& targetFolderPath = targetFolder->path(); const Project* targetProject = targetFolder->project(); QStringList targetAllPathes; for (const auto* aspect : targetProject->children(AbstractAspect::Recursive)) { if (!dynamic_cast(aspect)) targetAllPathes << aspect->path(); } QStringList existingPathes; for (const auto& path : selectedPathes) { const QString& newPath = targetFolderPath + path.right(path.length() - path.indexOf('/')); if (targetAllPathes.indexOf(newPath) != -1) existingPathes << path; } QDEBUG("project objects to be imported: " << selectedPathes); QDEBUG("all already available project objects: " << targetAllPathes); QDEBUG("already available project objects to be overwritten: " << existingPathes); if (!existingPathes.isEmpty()) { QString msg = i18np("The object listed below already exists in target folder and will be overwritten:", - "The objects listed below already exist in target folder and will be overwritten:", - existingPathes.size()); + "The objects listed below already exist in target folder and will be overwritten:", + existingPathes.size()); msg += '\n'; for (const auto& path : existingPathes) msg += '\n' + path.right(path.length() - path.indexOf('/') - 1); //strip away the name of the root folder "Project" msg += "\n\n" + i18n("Do you want to proceed?"); const int rc = KMessageBox::warningYesNo(nullptr, msg, i18n("Override existing objects?")); if (rc == KMessageBox::No) return; } //show a progress bar in the status bar auto* progressBar = new QProgressBar(); progressBar->setMinimum(0); progressBar->setMaximum(100); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QApplication::processEvents(QEventLoop::AllEvents, 100); //import the selected project objects into the specified folder QTime timer; timer.start(); connect(m_projectParser, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); #ifdef HAVE_LIBORIGIN if (m_projectType == ProjectOrigin && ui.chbUnusedObjects->isVisible() && ui.chbUnusedObjects->isChecked()) reinterpret_cast(m_projectParser)->setImportUnusedObjects(true); #endif m_projectParser->importTo(targetFolder, selectedPathes); statusBar->showMessage( i18n("Project data imported in %1 seconds.", (float)timer.elapsed()/1000) ); QApplication::restoreOverrideCursor(); statusBar->removeWidget(progressBar); } /*! * show the content of the project in the tree view */ void ImportProjectDialog::refreshPreview() { QString project = ui.leFileName->text(); m_projectParser->setProjectFileName(project); #ifdef HAVE_LIBORIGIN if (m_projectType == ProjectOrigin) { auto* originParser = reinterpret_cast(m_projectParser); if (originParser->hasUnusedObjects()) ui.chbUnusedObjects->show(); else ui.chbUnusedObjects->hide(); originParser->setImportUnusedObjects(ui.chbUnusedObjects->isVisible() && ui.chbUnusedObjects->isChecked()); } #endif ui.tvPreview->setModel(m_projectParser->model()); connect(ui.tvPreview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), - this, SLOT(selectionChanged(QItemSelection,QItemSelection)) ); + this, SLOT(selectionChanged(QItemSelection,QItemSelection)) ); //show top-level containers only if (ui.tvPreview->model()) { QModelIndex root = ui.tvPreview->model()->index(0,0); showTopLevelOnly(root); } //select the first top-level node and //expand the tree to show all available top-level objects and adjust the header sizes ui.tvPreview->setCurrentIndex(ui.tvPreview->model()->index(0,0)); ui.tvPreview->expandAll(); ui.tvPreview->header()->resizeSections(QHeaderView::ResizeToContents); } /*! Hides the non-toplevel items of the model used in the tree view. */ void ImportProjectDialog::showTopLevelOnly(const QModelIndex& index) { int rows = index.model()->rowCount(index); for (int i = 0; i < rows; ++i) { QModelIndex child = index.model()->index(i, 0, index); showTopLevelOnly(child); const auto* aspect = static_cast(child.internalPointer()); ui.tvPreview->setRowHidden(i, index, !isTopLevel(aspect)); } } /*! checks whether \c aspect is one of the allowed top level types */ bool ImportProjectDialog::isTopLevel(const AbstractAspect* aspect) const { foreach (AspectType type, m_projectParser->topLevelClasses()) { if (aspect->inherits(type)) return true; } return false; } //############################################################################## //################################# SLOTS #################################### //############################################################################## void ImportProjectDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { Q_UNUSED(deselected); //determine the dependent objects and select/deselect them too const QModelIndexList& indexes = selected.indexes(); if (indexes.isEmpty()) return; //for the just selected aspect, determine all the objects it depends on and select them, too //TODO: we need a better "selection", maybe with tri-state check boxes in the tree view const auto* aspect = static_cast(indexes.at(0).internalPointer()); const QVector aspects = aspect->dependsOn(); const auto* model = reinterpret_cast(ui.tvPreview->model()); for (const auto* aspect : aspects) { QModelIndex index = model->modelIndexOfAspect(aspect, 0); ui.tvPreview->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); } //Ok-button is only enabled if some project objects were selected bool enable = (selected.indexes().size() != 0); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); if (enable) m_buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18n("Close the dialog and import the selected objects.")); else m_buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18n("Select object(s) to be imported.")); } /*! opens a file dialog and lets the user select the project file. */ void ImportProjectDialog::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); QString title; QString lastDir; QString supportedFormats; QString lastDirConfEntryName; switch (m_projectType) { case (ProjectLabPlot): title = i18nc("@title:window", "Open LabPlot Project"); lastDirConfEntryName = QLatin1String("LastImportLabPlotProjectDir"); supportedFormats = i18n("LabPlot Projects (%1)", Project::supportedExtensions()); break; case (ProjectOrigin): #ifdef HAVE_LIBORIGIN title = i18nc("@title:window", "Open Origin Project"); lastDirConfEntryName = QLatin1String("LastImportOriginProjecttDir"); supportedFormats = i18n("Origin Projects (%1)", OriginProjectParser::supportedExtensions()); #endif break; } lastDir = conf.readEntry(lastDirConfEntryName, ""); QString path = QFileDialog::getOpenFileName(this, title, lastDir, supportedFormats); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != lastDir) conf.writeEntry(lastDirConfEntryName, newDir); } ui.leFileName->setText(path); refreshPreview(); } void ImportProjectDialog::fileNameChanged(const QString& name) { QString fileName = name; #ifndef HAVE_WINDOWS // make relative path if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif bool fileExists = QFile::exists(fileName); if (fileExists) ui.leFileName->setStyleSheet(QString()); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tvPreview->setModel(nullptr); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } refreshPreview(); } void ImportProjectDialog::newFolder() { QString path = ui.leFileName->text(); QString name = path.right( path.length()-path.lastIndexOf(QDir::separator())-1 ); bool ok; QInputDialog* dlg = new QInputDialog(this); name = dlg->getText(this, i18n("Add new folder"), i18n("Folder name:"), QLineEdit::Normal, name, &ok); if (ok) { auto* folder = new Folder(name); m_mainWin->addAspectToProject(folder); m_cbAddTo->setCurrentModelIndex(m_mainWin->model()->modelIndexOfAspect(folder)); } delete dlg; } diff --git a/src/kdefrontend/dockwidgets/AxisDock.cpp b/src/kdefrontend/dockwidgets/AxisDock.cpp index 9f8a3f1af..3205fbaff 100644 --- a/src/kdefrontend/dockwidgets/AxisDock.cpp +++ b/src/kdefrontend/dockwidgets/AxisDock.cpp @@ -1,2199 +1,2198 @@ /*************************************************************************** File : AxisDock.cpp Project : LabPlot Description : axes widget class -------------------------------------------------------------------- Copyright : (C) 2011-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2013 Stefan Gerlach (stefan.gerlach@uni-konstanz.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 "AxisDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/widgets/LabelWidget.h" #include "commonfrontend/widgets/DateTimeSpinBox.h" #include #include #include #include #include /*! \class AxisDock \brief Provides a widget for editing the properties of the axes currently selected in the project explorer. \ingroup kdefrontend */ AxisDock::AxisDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; //"Title"-tab auto* hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget = new LabelWidget(ui.tabTitle); labelWidget->setFixedLabelMode(true); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //"Ticks"-tab auto* layout = static_cast(ui.tabTicks->layout()); cbMajorTicksColumn = new TreeViewComboBox(ui.tabTicks); layout->addWidget(cbMajorTicksColumn, 7, 2); cbMinorTicksColumn = new TreeViewComboBox(ui.tabTicks); layout->addWidget(cbMinorTicksColumn, 21, 2); dtsbMajorTicksIncrement = new DateTimeSpinBox(ui.tabTicks); layout->addWidget(dtsbMajorTicksIncrement, 6, 2); dtsbMinorTicksIncrement = new DateTimeSpinBox(ui.tabTicks); layout->addWidget(dtsbMinorTicksIncrement, 20, 2); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //********************************** Slots ********************************************** //"General"-tab connect(ui.leName, &QLineEdit::textChanged, this, &AxisDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &AxisDock::commentChanged); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( ui.cbOrientation, SIGNAL(currentIndexChanged(int)), this, SLOT(orientationChanged(int)) ); connect( ui.cbPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(positionChanged(int)) ); connect( ui.lePosition, SIGNAL(textChanged(QString)), this, SLOT(positionChanged()) ); connect( ui.cbScale, SIGNAL(currentIndexChanged(int)), this, SLOT(scaleChanged(int)) ); connect( ui.chkAutoScale, SIGNAL(stateChanged(int)), this, SLOT(autoScaleChanged(int)) ); connect( ui.leStart, SIGNAL(textChanged(QString)), this, SLOT(startChanged()) ); connect( ui.leEnd, SIGNAL(textChanged(QString)), this, SLOT(endChanged()) ); connect(ui.dateTimeEditStart, &QDateTimeEdit::dateTimeChanged, this, &AxisDock::startDateTimeChanged); connect(ui.dateTimeEditEnd, &QDateTimeEdit::dateTimeChanged, this, &AxisDock::endDateTimeChanged); connect( ui.leZeroOffset, SIGNAL(textChanged(QString)), this, SLOT(zeroOffsetChanged()) ); connect( ui.leScalingFactor, SIGNAL(textChanged(QString)), this, SLOT(scalingFactorChanged()) ); //"Line"-tab connect( ui.cbLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(lineStyleChanged(int)) ); connect( ui.kcbLineColor, SIGNAL(changed(QColor)), this, SLOT(lineColorChanged(QColor)) ); connect( ui.sbLineWidth, SIGNAL(valueChanged(double)), this, SLOT(lineWidthChanged(double)) ); connect( ui.sbLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(lineOpacityChanged(int)) ); connect( ui.cbArrowPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(arrowPositionChanged(int)) ); connect( ui.cbArrowType, SIGNAL(currentIndexChanged(int)), this, SLOT(arrowTypeChanged(int)) ); connect( ui.sbArrowSize, SIGNAL(valueChanged(int)), this, SLOT(arrowSizeChanged(int)) ); //"Major ticks"-tab connect( ui.cbMajorTicksDirection, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksDirectionChanged(int)) ); connect( ui.cbMajorTicksType, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksTypeChanged(int)) ); connect( ui.sbMajorTicksNumber, SIGNAL(valueChanged(int)), this, SLOT(majorTicksNumberChanged(int)) ); connect( ui.sbMajorTicksIncrementNumeric, SIGNAL(valueChanged(double)), this, SLOT(majorTicksIncrementChanged()) ); connect( dtsbMajorTicksIncrement, SIGNAL(valueChanged()), this, SLOT(majorTicksIncrementChanged()) ); //connect( ui.sbMajorTicksIncrementNumeric, &QDoubleSpinBox::valueChanged, this, &AxisDock::majorTicksIncrementChanged); connect( cbMajorTicksColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(majorTicksColumnChanged(QModelIndex)) ); connect( ui.cbMajorTicksLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksLineStyleChanged(int)) ); connect( ui.kcbMajorTicksColor, SIGNAL(changed(QColor)), this, SLOT(majorTicksColorChanged(QColor)) ); connect( ui.sbMajorTicksWidth, SIGNAL(valueChanged(double)), this, SLOT(majorTicksWidthChanged(double)) ); connect( ui.sbMajorTicksLength, SIGNAL(valueChanged(double)), this, SLOT(majorTicksLengthChanged(double)) ); connect( ui.sbMajorTicksOpacity, SIGNAL(valueChanged(int)), this, SLOT(majorTicksOpacityChanged(int)) ); //"Minor ticks"-tab connect( ui.cbMinorTicksDirection, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksDirectionChanged(int)) ); connect( ui.cbMinorTicksType, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksTypeChanged(int)) ); connect( ui.sbMinorTicksNumber, SIGNAL(valueChanged(int)), this, SLOT(minorTicksNumberChanged(int)) ); connect( ui.sbMajorTicksIncrementNumeric, SIGNAL(valueChanged(double)), this, SLOT(minorTicksIncrementChanged()) ); connect( dtsbMinorTicksIncrement, SIGNAL(valueChanged()), this, SLOT(minorTicksIncrementChanged()) ); connect( cbMinorTicksColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(minorTicksColumnChanged(QModelIndex)) ); connect( ui.cbMinorTicksLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksLineStyleChanged(int)) ); connect( ui.kcbMinorTicksColor, SIGNAL(changed(QColor)), this, SLOT(minorTicksColorChanged(QColor)) ); connect( ui.sbMinorTicksWidth, SIGNAL(valueChanged(double)), this, SLOT(minorTicksWidthChanged(double)) ); connect( ui.sbMinorTicksLength, SIGNAL(valueChanged(double)), this, SLOT(minorTicksLengthChanged(double)) ); connect( ui.sbMinorTicksOpacity, SIGNAL(valueChanged(int)), this, SLOT(minorTicksOpacityChanged(int)) ); //"Extra ticks"-tab //"Tick labels"-tab connect( ui.cbLabelsFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(labelsFormatChanged(int)) ); connect( ui.sbLabelsPrecision, SIGNAL(valueChanged(int)), this, SLOT(labelsPrecisionChanged(int)) ); connect( ui.chkLabelsAutoPrecision, SIGNAL(stateChanged(int)), this, SLOT(labelsAutoPrecisionChanged(int)) ); connect(ui.cbLabelsDateTimeFormat, static_cast(&QComboBox::currentIndexChanged), this, &AxisDock::labelsDateTimeFormatChanged); connect( ui.cbLabelsPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(labelsPositionChanged(int)) ); connect( ui.sbLabelsOffset, SIGNAL(valueChanged(double)), this, SLOT(labelsOffsetChanged(double)) ); connect( ui.sbLabelsRotation, SIGNAL(valueChanged(int)), this, SLOT(labelsRotationChanged(int)) ); connect( ui.kfrLabelsFont, SIGNAL(fontSelected(QFont)), this, SLOT(labelsFontChanged(QFont)) ); connect( ui.kcbLabelsFontColor, SIGNAL(changed(QColor)), this, SLOT(labelsFontColorChanged(QColor)) ); connect( ui.leLabelsPrefix, SIGNAL(textChanged(QString)), this, SLOT(labelsPrefixChanged()) ); connect( ui.leLabelsSuffix, SIGNAL(textChanged(QString)), this, SLOT(labelsSuffixChanged()) ); connect( ui.sbLabelsOpacity, SIGNAL(valueChanged(int)), this, SLOT(labelsOpacityChanged(int)) ); //"Grid"-tab connect( ui.cbMajorGridStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(majorGridStyleChanged(int)) ); connect( ui.kcbMajorGridColor, SIGNAL(changed(QColor)), this, SLOT(majorGridColorChanged(QColor)) ); connect( ui.sbMajorGridWidth, SIGNAL(valueChanged(double)), this, SLOT(majorGridWidthChanged(double)) ); connect( ui.sbMajorGridOpacity, SIGNAL(valueChanged(int)), this, SLOT(majorGridOpacityChanged(int)) ); connect( ui.cbMinorGridStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(minorGridStyleChanged(int)) ); connect( ui.kcbMinorGridColor, SIGNAL(changed(QColor)), this, SLOT(minorGridColorChanged(QColor)) ); connect( ui.sbMinorGridWidth, SIGNAL(valueChanged(double)), this, SLOT(minorGridWidthChanged(double)) ); connect( ui.sbMinorGridOpacity, SIGNAL(valueChanged(int)), this, SLOT(minorGridOpacityChanged(int)) ); //template handler auto* frame = new QFrame(this); auto* hlayout = new QHBoxLayout(frame); hlayout->setContentsMargins(0, 11, 0, 11); auto* templateHandler = new TemplateHandler(this, TemplateHandler::Axis); hlayout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); ui.verticalLayout->addWidget(frame); - init(); } AxisDock::~AxisDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } void AxisDock::init() { m_initializing = true; //Validators ui.lePosition->setValidator( new QDoubleValidator(ui.lePosition) ); ui.leStart->setValidator( new QDoubleValidator(ui.leStart) ); ui.leEnd->setValidator( new QDoubleValidator(ui.leEnd) ); ui.leZeroOffset->setValidator( new QDoubleValidator(ui.leZeroOffset) ); ui.leScalingFactor->setValidator( new QDoubleValidator(ui.leScalingFactor) ); //TODO move this stuff to retranslateUI() ui.cbPosition->addItem(i18n("Top")); ui.cbPosition->addItem(i18n("Bottom")); ui.cbPosition->addItem(i18n("Centered")); ui.cbPosition->addItem(i18n("Custom")); ui.cbScale->addItem( i18n("Linear") ); ui.cbScale->addItem( QLatin1String("log(x)") ); ui.cbScale->addItem( QLatin1String("log2(x)") ); ui.cbScale->addItem( QLatin1String("ln(x)") ); ui.cbScale->addItem( QLatin1String("sqrt(x)") ); ui.cbScale->addItem( QLatin1String("x^2") ); ui.cbOrientation->addItem( i18n("Horizontal") ); ui.cbOrientation->addItem( i18n("Vertical") ); //Arrows ui.cbArrowType->addItem( i18n("No arrow") ); ui.cbArrowType->addItem( i18n("Simple, Small") ); ui.cbArrowType->addItem( i18n("Simple, Big") ); ui.cbArrowType->addItem( i18n("Filled, Small") ); ui.cbArrowType->addItem( i18n("Filled, Big") ); ui.cbArrowType->addItem( i18n("Semi-filled, Small") ); ui.cbArrowType->addItem( i18n("Semi-filled, Big") ); QPainter pa; pa.setPen( QPen(Qt::SolidPattern, 0) ); QPixmap pm(20, 20); ui.cbArrowType->setIconSize( QSize(20,20) ); //no arrow pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); pa.end(); ui.cbArrowType->setItemIcon(0, pm); //simple, small float cos_phi = cos(3.14159/6); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10); pa.drawLine(17,10, 10, 10-5*cos_phi); pa.drawLine(17,10, 10, 10+5*cos_phi); pa.end(); ui.cbArrowType->setItemIcon(1, pm); //simple, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10); pa.drawLine(17,10, 10, 10-10*cos_phi); pa.drawLine(17,10, 10, 10+10*cos_phi); pa.end(); ui.cbArrowType->setItemIcon(2, pm); //filled, small pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points3[3] = {QPointF(17, 10), QPointF(10, 10-4*cos_phi), QPointF(10, 10+4*cos_phi) }; pa.drawPolygon(points3, 3); pa.end(); ui.cbArrowType->setItemIcon(3, pm); //filled, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points4[3] = {QPointF(17, 10), QPointF(10, 10-10*cos_phi), QPointF(10, 10+10*cos_phi) }; pa.drawPolygon(points4, 3); pa.end(); ui.cbArrowType->setItemIcon(4, pm); //semi-filled, small pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points5[4] = {QPointF(17, 10), QPointF(10, 10-4*cos_phi), QPointF(13, 10), QPointF(10, 10+4*cos_phi) }; pa.drawPolygon(points5, 4); pa.end(); ui.cbArrowType->setItemIcon(5, pm); //semi-filled, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points6[4] = {QPointF(17, 10), QPointF(10, 10-10*cos_phi), QPointF(13, 10), QPointF(10, 10+10*cos_phi) }; pa.drawPolygon(points6, 4); pa.end(); ui.cbArrowType->setItemIcon(6, pm); ui.cbArrowPosition->addItem( i18n("Left") ); ui.cbArrowPosition->addItem( i18n("Right") ); ui.cbArrowPosition->addItem( i18n("Both") ); ui.cbMajorTicksDirection->addItem( i18n("None") ); ui.cbMajorTicksDirection->addItem( i18n("In") ); ui.cbMajorTicksDirection->addItem( i18n("Out") ); ui.cbMajorTicksDirection->addItem( i18n("In and Out") ); ui.cbMajorTicksType->addItem( i18n("Number") ); ui.cbMajorTicksType->addItem( i18n("Increment") ); ui.cbMajorTicksType->addItem( i18n("Custom column") ); ui.cbMinorTicksDirection->addItem( i18n("None") ); ui.cbMinorTicksDirection->addItem( i18n("In") ); ui.cbMinorTicksDirection->addItem( i18n("Out") ); ui.cbMinorTicksDirection->addItem( i18n("In and Out") ); ui.cbMinorTicksType->addItem( i18n("Number") ); ui.cbMinorTicksType->addItem( i18n("Increment") ); ui.cbMinorTicksType->addItem( i18n("Custom column") ); GuiTools::updatePenStyles(ui.cbLineStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, QColor(Qt::black)); + GuiTools::updatePenStyles(ui.cbMajorGridStyle, QColor(Qt::black)); + GuiTools::updatePenStyles(ui.cbMinorGridStyle, QColor(Qt::black)); //labels ui.cbLabelsPosition->addItem(i18n("No labels")); ui.cbLabelsPosition->addItem(i18n("Top")); ui.cbLabelsPosition->addItem(i18n("Bottom")); ui.cbLabelsFormat->addItem( i18n("Decimal notation") ); ui.cbLabelsFormat->addItem( i18n("Scientific notation") ); ui.cbLabelsFormat->addItem( i18n("Powers of 10") ); ui.cbLabelsFormat->addItem( i18n("Powers of 2") ); ui.cbLabelsFormat->addItem( i18n("Powers of e") ); ui.cbLabelsFormat->addItem( i18n("Multiples of π") ); ui.cbLabelsDateTimeFormat->addItems(AbstractColumn::dateTimeFormats()); m_initializing = false; } void AxisDock::setModel() { QList list{AspectType::Folder, AspectType::Spreadsheet, AspectType::Column}; cbMajorTicksColumn->setTopLevelClasses(list); cbMinorTicksColumn->setTopLevelClasses(list); list = {AspectType::Column}; m_aspectTreeModel->setSelectableAspects(list); cbMajorTicksColumn->setModel(m_aspectTreeModel); cbMinorTicksColumn->setModel(m_aspectTreeModel); } /*! sets the axes. The properties of the axes in the list \c list can be edited in this widget. */ void AxisDock::setAxes(QList list) { m_initializing = true; m_axesList = list; m_axis = list.first(); m_aspect = list.first(); Q_ASSERT(m_axis != nullptr); m_aspectTreeModel = new AspectTreeModel(m_axis->project()); this->setModel(); labelWidget->setAxes(list); //if there are more then one axis in the list, disable the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_axis->name()); ui.leComment->setText(m_axis->comment()); this->setModelIndexFromColumn(cbMajorTicksColumn, m_axis->majorTicksColumn()); this->setModelIndexFromColumn(cbMinorTicksColumn, m_axis->minorTicksColumn()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); cbMajorTicksColumn->setCurrentModelIndex(QModelIndex()); cbMinorTicksColumn->setCurrentModelIndex(QModelIndex()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first axis this->load(); // general connect(m_axis, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(axisDescriptionChanged(const AbstractAspect*))); connect(m_axis, SIGNAL(orientationChanged(Axis::AxisOrientation)), this, SLOT(axisOrientationChanged(Axis::AxisOrientation))); connect(m_axis, SIGNAL(positionChanged(Axis::AxisPosition)), this, SLOT(axisPositionChanged(Axis::AxisPosition))); connect(m_axis, SIGNAL(scaleChanged(Axis::AxisScale)), this, SLOT(axisScaleChanged(Axis::AxisScale))); connect(m_axis, SIGNAL(autoScaleChanged(bool)), this, SLOT(axisAutoScaleChanged(bool))); connect(m_axis, SIGNAL(startChanged(double)), this, SLOT(axisStartChanged(double))); connect(m_axis, SIGNAL(endChanged(double)), this, SLOT(axisEndChanged(double))); connect(m_axis, SIGNAL(zeroOffsetChanged(qreal)), this, SLOT(axisZeroOffsetChanged(qreal))); connect(m_axis, SIGNAL(scalingFactorChanged(qreal)), this, SLOT(axisScalingFactorChanged(qreal))); // line connect(m_axis, SIGNAL(linePenChanged(QPen)), this, SLOT(axisLinePenChanged(QPen))); connect(m_axis, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(axisLineOpacityChanged(qreal))); connect(m_axis, SIGNAL(arrowTypeChanged(Axis::ArrowType)), this, SLOT(axisArrowTypeChanged(Axis::ArrowType))); connect(m_axis, SIGNAL(arrowPositionChanged(Axis::ArrowPosition)), this, SLOT(axisArrowPositionChanged(Axis::ArrowPosition))); connect(m_axis, SIGNAL(arrowSizeChanged(qreal)), this, SLOT(axisArrowSizeChanged(qreal))); // ticks connect(m_axis, SIGNAL(majorTicksDirectionChanged(Axis::TicksDirection)), this, SLOT(axisMajorTicksDirectionChanged(Axis::TicksDirection))); connect(m_axis, SIGNAL(majorTicksTypeChanged(Axis::TicksType)), this, SLOT(axisMajorTicksTypeChanged(Axis::TicksType))); connect(m_axis, SIGNAL(majorTicksNumberChanged(int)), this, SLOT(axisMajorTicksNumberChanged(int))); connect(m_axis, SIGNAL(majorTicksIncrementChanged(qreal)), this, SLOT(axisMajorTicksIncrementChanged(qreal))); connect(m_axis, SIGNAL(majorTicksPenChanged(QPen)), this, SLOT(axisMajorTicksPenChanged(QPen))); connect(m_axis, SIGNAL(majorTicksLengthChanged(qreal)), this, SLOT(axisMajorTicksLengthChanged(qreal))); connect(m_axis, SIGNAL(majorTicksOpacityChanged(qreal)), this, SLOT(axisMajorTicksOpacityChanged(qreal))); connect(m_axis, SIGNAL(minorTicksDirectionChanged(Axis::TicksDirection)), this, SLOT(axisMinorTicksDirectionChanged(Axis::TicksDirection))); connect(m_axis, SIGNAL(minorTicksTypeChanged(Axis::TicksType)), this, SLOT(axisMinorTicksTypeChanged(Axis::TicksType))); connect(m_axis, SIGNAL(minorTicksNumberChanged(int)), this, SLOT(axisMinorTicksNumberChanged(int))); connect(m_axis, SIGNAL(minorTicksIncrementChanged(qreal)), this, SLOT(axisMinorTicksIncrementChanged(qreal))); connect(m_axis, SIGNAL(minorTicksPenChanged(QPen)), this, SLOT(axisMinorTicksPenChanged(QPen))); connect(m_axis, SIGNAL(minorTicksLengthChanged(qreal)), this, SLOT(axisMinorTicksLengthChanged(qreal))); connect(m_axis, SIGNAL(minorTicksOpacityChanged(qreal)), this, SLOT(axisMinorTicksOpacityChanged(qreal))); // labels connect(m_axis, SIGNAL(labelsFormatChanged(Axis::LabelsFormat)), this, SLOT(axisLabelsFormatChanged(Axis::LabelsFormat))); connect(m_axis, SIGNAL(labelsAutoPrecisionChanged(bool)), this, SLOT(axisLabelsAutoPrecisionChanged(bool))); connect(m_axis, SIGNAL(labelsPrecisionChanged(int)), this, SLOT(axisLabelsPrecisionChanged(int))); connect(m_axis, &Axis::labelsDateTimeFormatChanged, this, &AxisDock::axisLabelsDateTimeFormatChanged); connect(m_axis, SIGNAL(labelsPositionChanged(Axis::LabelsPosition)), this, SLOT(axisLabelsPositionChanged(Axis::LabelsPosition))); connect(m_axis, SIGNAL(labelsOffsetChanged(double)), this, SLOT(axisLabelsOffsetChanged(double))); connect(m_axis, SIGNAL(labelsRotationAngleChanged(qreal)), this, SLOT(axisLabelsRotationAngleChanged(qreal))); connect(m_axis, SIGNAL(labelsFontChanged(QFont)), this, SLOT(axisLabelsFontChanged(QFont))); connect(m_axis, SIGNAL(labelsColorChanged(QColor)), this, SLOT(axisLabelsFontColorChanged(QColor))); connect(m_axis, SIGNAL(labelsPrefixChanged(QString)), this, SLOT(axisLabelsPrefixChanged(QString))); connect(m_axis, SIGNAL(labelsSuffixChanged(QString)), this, SLOT(axisLabelsSuffixChanged(QString))); connect(m_axis, SIGNAL(labelsOpacityChanged(qreal)), this, SLOT(axisLabelsOpacityChanged(qreal))); // grids connect(m_axis, SIGNAL(majorGridPenChanged(QPen)), this, SLOT(axisMajorGridPenChanged(QPen))); connect(m_axis, SIGNAL(majorGridOpacityChanged(qreal)), this, SLOT(axisMajorGridOpacityChanged(qreal))); connect(m_axis, SIGNAL(minorGridPenChanged(QPen)), this, SLOT(axisMinorGridPenChanged(QPen))); connect(m_axis, SIGNAL(minorGridOpacityChanged(qreal)), this, SLOT(axisMinorGridOpacityChanged(qreal))); connect(m_axis, SIGNAL(visibilityChanged(bool)), this, SLOT(axisVisibilityChanged(bool))); m_initializing = false; } void AxisDock::activateTitleTab() { ui.tabWidget->setCurrentWidget(ui.tabTitle); } void AxisDock::setModelIndexFromColumn(TreeViewComboBox* cb, const AbstractColumn* column) { if (column) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else cb->setCurrentModelIndex(QModelIndex()); } //************************************************************* //********** SLOTs for changes triggered in AxisDock ********** //************************************************************* //"General"-tab void AxisDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setVisible(state); } /*! called if the orientation (horizontal or vertical) of the current axis is changed. */ void AxisDock::orientationChanged(int index) { auto orientation = (Axis::AxisOrientation)index; if (orientation == Axis::AxisHorizontal) { ui.cbPosition->setItemText(0, i18n("Top") ); ui.cbPosition->setItemText(1, i18n("Bottom") ); ui.cbLabelsPosition->setItemText(1, i18n("Top") ); ui.cbLabelsPosition->setItemText(2, i18n("Bottom") ); ui.cbScale->setItemText(1, QLatin1String("log(x)") ); ui.cbScale->setItemText(2, QLatin1String("log2(x)") ); ui.cbScale->setItemText(3, QLatin1String("ln(x)") ); ui.cbScale->setItemText(4, QLatin1String("sqrt(x)") ); ui.cbScale->setItemText(5, QLatin1String("x^2") ); } else { //vertical ui.cbPosition->setItemText(0, i18n("Left") ); ui.cbPosition->setItemText(1, i18n("Right") ); ui.cbLabelsPosition->setItemText(1, i18n("Right") ); ui.cbLabelsPosition->setItemText(2, i18n("Left") ); ui.cbScale->setItemText(1, QLatin1String("log(y)") ); ui.cbScale->setItemText(2, QLatin1String("log2(y)") ); ui.cbScale->setItemText(3, QLatin1String("ln(y)") ); ui.cbScale->setItemText(4, QLatin1String("sqrt(y)") ); ui.cbScale->setItemText(5, QLatin1String("y^2") ); } if (m_initializing) return; //depending on the current orientation we need to update axis possition and labels position //axis position, map from the current index in the combobox to the enum value in Axis::AxisPosition Axis::AxisPosition axisPosition; int posIndex = ui.cbPosition->currentIndex(); if (orientation == Axis::AxisHorizontal) { if (posIndex > 1) posIndex += 2; axisPosition = Axis::AxisPosition(posIndex); - } else { + } else axisPosition = Axis::AxisPosition(posIndex+2); - } //labels position posIndex = ui.cbLabelsPosition->currentIndex(); auto labelsPosition = Axis::LabelsPosition(posIndex); for (auto* axis : m_axesList) { axis->beginMacro(i18n("%1: set axis orientation", axis->name())); axis->setOrientation(orientation); axis->setPosition(axisPosition); axis->setLabelsPosition(labelsPosition); axis->endMacro(); } } /*! called if one of the predefined axis positions (top, bottom, left, right, center or custom) was changed. */ void AxisDock::positionChanged(int index) { if (index == -1) return; //we occasionally get -1 here, nothing to do in this case if (index == 3) ui.lePosition->setVisible(true); else ui.lePosition->setVisible(false); if (m_initializing) return; //map from the current index in the combo box to the enum value in Axis::AxisPosition, //depends on the current orientation Axis::AxisPosition position; if ( ui.cbOrientation->currentIndex() == 0 ) { if (index>1) index += 2; position = Axis::AxisPosition(index); - } else { + } else position = Axis::AxisPosition(index+2); - } for (auto* axis : m_axesList) axis->setPosition(position); } /*! called when the custom position of the axis in the corresponding LineEdit is changed. */ void AxisDock::positionChanged() { if (m_initializing) return; double offset = ui.lePosition->text().toDouble(); for (auto* axis : m_axesList) axis->setOffset(offset); } void AxisDock::scaleChanged(int index) { if (m_initializing) return; auto scale = (Axis::AxisScale)index; for (auto* axis : m_axesList) axis->setScale(scale); } void AxisDock::autoScaleChanged(int index) { bool autoScale = index == Qt::Checked; ui.leStart->setEnabled(!autoScale); ui.leEnd->setEnabled(!autoScale); ui.dateTimeEditStart->setEnabled(!autoScale); ui.dateTimeEditEnd->setEnabled(!autoScale); if (m_initializing) return; for (auto* axis : m_axesList) axis->setAutoScale(autoScale); } void AxisDock::startChanged() { if (m_initializing) return; double value = ui.leStart->text().toDouble(); //check first, whether the value for the lower limit is valid for the log- and square root scaling. If not, set the default values. auto scale = Axis::AxisScale(ui.cbScale->currentIndex()); if (scale == Axis::ScaleLog10 || scale == Axis::ScaleLog2 || scale == Axis::ScaleLn) { if (value <= 0) { KMessageBox::sorry(this, i18n("The axes lower limit has a non-positive value. Default minimal value will be used."), i18n("Wrong lower limit value") ); ui.leStart->setText( "0.01" ); value = 0.01; } } else if (scale == Axis::ScaleSqrt) { if (value < 0) { KMessageBox::sorry(this, i18n("The axes lower limit has a negative value. Default minimal value will be used."), i18n("Wrong lower limit value") ); ui.leStart->setText( "0" ); value = 0; } } for (auto* axis : m_axesList) axis->setStart(value); } void AxisDock::endChanged() { if (m_initializing) return; double value = ui.leEnd->text().toDouble(); for (auto* axis : m_axesList) axis->setEnd(value); } void AxisDock::startDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* axis : m_axesList) axis->setStart(value); } void AxisDock::endDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* axis : m_axesList) axis->setEnd(value); } void AxisDock::zeroOffsetChanged() { if (m_initializing) return; double offset = ui.leZeroOffset->text().toDouble(); for (auto* axis : m_axesList) axis->setZeroOffset(offset); } void AxisDock::scalingFactorChanged() { if (m_initializing) return; double scalingFactor = ui.leScalingFactor->text().toDouble(); if (scalingFactor != 0.0) for (auto* axis : m_axesList) axis->setScalingFactor(scalingFactor); } // "Line"-tab void AxisDock::lineStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lLineColor->setEnabled(b); ui.kcbLineColor->setEnabled(b); ui.lLineWidth->setEnabled(b); ui.sbLineWidth->setEnabled(b); ui.lLineOpacity->setEnabled(b); ui.sbLineOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->linePen(); pen.setStyle(penStyle); axis->setLinePen(pen); } } void AxisDock::lineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->linePen(); pen.setColor(color); axis->setLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing = false; } void AxisDock::lineWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->linePen(); pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); axis->setLinePen(pen); } } void AxisDock::lineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setLineOpacity(opacity); } void AxisDock::arrowTypeChanged(int index) { auto type = (Axis::ArrowType)index; if (type == Axis::NoArrow) { ui.cbArrowPosition->setEnabled(false); ui.sbArrowSize->setEnabled(false); } else { ui.cbArrowPosition->setEnabled(true); ui.sbArrowSize->setEnabled(true); } if (m_initializing) return; for (auto* axis : m_axesList) axis->setArrowType(type); } void AxisDock::arrowPositionChanged(int index) { if (m_initializing) return; auto position = (Axis::ArrowPosition)index; for (auto* axis : m_axesList) axis->setArrowPosition(position); } void AxisDock::arrowSizeChanged(int value) { if (m_initializing) return; float v = Worksheet::convertToSceneUnits(value, Worksheet::Point); for (auto* axis : m_axesList) axis->setArrowSize(v); } //"Major ticks" tab void AxisDock::majorTicksDirectionChanged(int index) { Axis::TicksDirection direction = Axis::TicksDirection(index); bool b = (direction != Axis::noTicks); ui.lMajorTicksType->setEnabled(b); ui.cbMajorTicksType->setEnabled(b); ui.lMajorTicksType->setEnabled(b); ui.cbMajorTicksType->setEnabled(b); ui.lMajorTicksNumber->setEnabled(b); ui.sbMajorTicksNumber->setEnabled(b); ui.lMajorTicksIncrementNumeric->setEnabled(b); ui.sbMajorTicksIncrementNumeric->setEnabled(b); ui.lMajorTicksIncrementDateTime->setEnabled(b); dtsbMajorTicksIncrement->setEnabled(b); ui.lMajorTicksLineStyle->setEnabled(b); ui.cbMajorTicksLineStyle->setEnabled(b); dtsbMinorTicksIncrement->setEnabled(b); if (b) { auto penStyle = Qt::PenStyle(ui.cbMajorTicksLineStyle->currentIndex()); b = (penStyle != Qt::NoPen); } ui.lMajorTicksColor->setEnabled(b); ui.kcbMajorTicksColor->setEnabled(b); ui.lMajorTicksWidth->setEnabled(b); ui.sbMajorTicksWidth->setEnabled(b); ui.lMajorTicksLength->setEnabled(b); ui.sbMajorTicksLength->setEnabled(b); ui.lMajorTicksOpacity->setEnabled(b); ui.sbMajorTicksOpacity->setEnabled(b); if (m_initializing) return; for (auto* axis : m_axesList) axis->setMajorTicksDirection(direction); } /*! called if the current style of the ticks (Number or Increment) is changed. Shows/hides the corresponding widgets. */ void AxisDock::majorTicksTypeChanged(int index) { if (!m_axis) // If elements are added to the combobox 'cbMajorTicksType' (at init of this class), then this function is called, which is a problem if no axis are available return; auto type = Axis::TicksType(index); if (type == Axis::TicksTotalNumber) { ui.lMajorTicksNumber->show(); ui.sbMajorTicksNumber->show(); ui.lMajorTicksIncrementNumeric->hide(); ui.sbMajorTicksIncrementNumeric->hide(); ui.lMajorTicksIncrementDateTime->hide(); dtsbMajorTicksIncrement->hide(); ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); } else if (type == Axis::TicksIncrement) { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); ui.lMajorTicksIncrementNumeric->show(); const auto* plot = dynamic_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) { ui.lMajorTicksIncrementDateTime->hide(); dtsbMajorTicksIncrement->hide(); ui.lMajorTicksIncrementNumeric->show(); ui.sbMajorTicksIncrementNumeric->show(); } else { ui.lMajorTicksIncrementDateTime->show(); dtsbMajorTicksIncrement->show(); ui.lMajorTicksIncrementNumeric->hide(); ui.sbMajorTicksIncrementNumeric->hide(); } ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); // Check if Increment is not to small majorTicksIncrementChanged(); } else { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); ui.lMajorTicksIncrementNumeric->hide(); ui.sbMajorTicksIncrementNumeric->hide(); dtsbMajorTicksIncrement->hide(); dtsbMajorTicksIncrement->hide(); ui.lMajorTicksColumn->show(); cbMajorTicksColumn->show(); } if (m_initializing) return; for (auto* axis : m_axesList) axis->setMajorTicksType(type); } void AxisDock::majorTicksNumberChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setMajorTicksNumber(value); } void AxisDock::majorTicksIncrementChanged() { if (m_initializing) return; const auto* plot = dynamic_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); double value = numeric ? ui.sbMajorTicksIncrementNumeric->value() : dtsbMajorTicksIncrement->value(); double diff = m_axis->end() - m_axis->start(); if (value == 0 || diff / value > 100 || value < 0) { // maximum of 100 ticks if (value == 0) value = diff / ui.sbMajorTicksNumber->value(); if (diff / value > 100) value = diff / 100; // determine stepsize and number of decimals m_initializing = true; if (numeric) { int decimal = determineDecimals(value * 10); ui.sbMajorTicksIncrementNumeric->setDecimals(decimal); ui.sbMajorTicksIncrementNumeric->setSingleStep(determineStep(diff, decimal)); ui.sbMajorTicksIncrementNumeric->setValue(value); } else dtsbMajorTicksIncrement->setValue(value); m_initializing = false; } for (auto* axis : m_axesList) axis->setMajorTicksIncrement(value); } void AxisDock::majorTicksLineStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMajorTicksColor->setEnabled(b); ui.kcbMajorTicksColor->setEnabled(b); ui.lMajorTicksWidth->setEnabled(b); ui.sbMajorTicksWidth->setEnabled(b); ui.lMajorTicksLength->setEnabled(b); ui.sbMajorTicksLength->setEnabled(b); ui.lMajorTicksOpacity->setEnabled(b); ui.sbMajorTicksOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorTicksPen(); pen.setStyle(penStyle); axis->setMajorTicksPen(pen); } } void AxisDock::majorTicksColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); AbstractColumn* column = nullptr; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column != nullptr); } for (auto* axis : m_axesList) axis->setMajorTicksColumn(column); } void AxisDock::majorTicksColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorTicksPen(); pen.setColor(color); axis->setMajorTicksPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, color); m_initializing = false; } void AxisDock::majorTicksWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorTicksPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); axis->setMajorTicksPen(pen); } } void AxisDock::majorTicksLengthChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setMajorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void AxisDock::majorTicksOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMajorTicksOpacity(opacity); } //"Minor ticks" tab void AxisDock::minorTicksDirectionChanged(int index) { Axis::TicksDirection direction = Axis::TicksDirection(index); bool b = (direction != Axis::noTicks); ui.lMinorTicksType->setEnabled(b); ui.cbMinorTicksType->setEnabled(b); ui.lMinorTicksType->setEnabled(b); ui.cbMinorTicksType->setEnabled(b); ui.lMinorTicksNumber->setEnabled(b); ui.sbMinorTicksNumber->setEnabled(b); ui.lMinorTicksIncrementNumeric->setEnabled(b); ui.sbMinorTicksIncrementNumeric->setEnabled(b); ui.lMinorTicksIncrementDateTime->setEnabled(b); dtsbMinorTicksIncrement->setEnabled(b); ui.lMinorTicksLineStyle->setEnabled(b); ui.cbMinorTicksLineStyle->setEnabled(b); if (b) { auto penStyle = Qt::PenStyle(ui.cbMinorTicksLineStyle->currentIndex()); b = (penStyle != Qt::NoPen); } ui.lMinorTicksColor->setEnabled(b); ui.kcbMinorTicksColor->setEnabled(b); ui.lMinorTicksWidth->setEnabled(b); ui.sbMinorTicksWidth->setEnabled(b); ui.lMinorTicksLength->setEnabled(b); ui.sbMinorTicksLength->setEnabled(b); ui.lMinorTicksOpacity->setEnabled(b); ui.sbMinorTicksOpacity->setEnabled(b); if (m_initializing) return; for (auto* axis : m_axesList) axis->setMinorTicksDirection(direction); } void AxisDock::minorTicksTypeChanged(int index) { if (!m_axis) // If elements are added to the combobox 'cbMajorTicksType' (at init of this class), then this function is called, which is a problem if no axis are available return; auto type = Axis::TicksType(index); if (type == Axis::TicksTotalNumber) { ui.lMinorTicksNumber->show(); ui.sbMinorTicksNumber->show(); ui.lMinorTicksIncrementNumeric->hide(); ui.sbMinorTicksIncrementNumeric->hide(); ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); ui.lMinorTicksIncrementDateTime->hide(); dtsbMinorTicksIncrement->hide(); } else if ( type == Axis::TicksIncrement) { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); const auto* plot = dynamic_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) { ui.lMinorTicksIncrementNumeric->show(); ui.sbMinorTicksIncrementNumeric->show(); ui.lMinorTicksIncrementDateTime->hide(); dtsbMinorTicksIncrement->hide(); } else { ui.lMinorTicksIncrementNumeric->hide(); ui.sbMinorTicksIncrementNumeric->hide(); ui.lMinorTicksIncrementDateTime->show(); dtsbMinorTicksIncrement->show(); } ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); // Check if Increment is not to small minorTicksIncrementChanged(); } else { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); ui.lMinorTicksIncrementNumeric->hide(); ui.sbMinorTicksIncrementNumeric->hide(); ui.lMinorTicksIncrementDateTime->hide(); dtsbMinorTicksIncrement->hide(); ui.lMinorTicksColumn->show(); cbMinorTicksColumn->show(); } if (m_initializing) return; for (auto* axis : m_axesList) axis->setMinorTicksType(type); } void AxisDock::minorTicksNumberChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setMinorTicksNumber(value); } void AxisDock::minorTicksIncrementChanged() { if (m_initializing) return; const auto* plot = dynamic_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); double value = numeric ? ui.sbMinorTicksIncrementNumeric->value() : dtsbMinorTicksIncrement->value(); double numberTicks = 0.0; if (value > 0) numberTicks = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / value -1; // recal if (value == 0 || numberTicks > 100 || value < 0) { if (value == 0) value = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / (ui.sbMinorTicksNumber->value() + 1); numberTicks = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / value -1; // recalculate number of ticks if (numberTicks > 100) // maximum 100 minor ticks value = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / (100 + 1); // determine stepsize and number of decimals m_initializing = true; if (numeric) { int decimal = determineDecimals(value * 10); ui.sbMinorTicksIncrementNumeric->setDecimals(decimal); ui.sbMinorTicksIncrementNumeric->setSingleStep(determineStep((m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1), decimal)); ui.sbMinorTicksIncrementNumeric->setValue(value); } else dtsbMinorTicksIncrement->setValue(value); m_initializing = false; } for (auto* axis : m_axesList) axis->setMinorTicksIncrement(value); } void AxisDock::minorTicksColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column != nullptr); for (auto* axis : m_axesList) axis->setMinorTicksColumn(column); } void AxisDock::minorTicksLineStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMinorTicksColor->setEnabled(b); ui.kcbMinorTicksColor->setEnabled(b); ui.lMinorTicksWidth->setEnabled(b); ui.sbMinorTicksWidth->setEnabled(b); ui.lMinorTicksLength->setEnabled(b); ui.sbMinorTicksLength->setEnabled(b); ui.lMinorTicksOpacity->setEnabled(b); ui.sbMinorTicksOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorTicksPen(); pen.setStyle(penStyle); axis->setMinorTicksPen(pen); } } void AxisDock::minorTicksColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorTicksPen(); pen.setColor(color); axis->setMinorTicksPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, color); m_initializing = false; } void AxisDock::minorTicksWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorTicksPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); axis->setMinorTicksPen(pen); } } void AxisDock::minorTicksLengthChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setMinorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void AxisDock::minorTicksOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMinorTicksOpacity(opacity); } //"Tick labels"-tab void AxisDock::labelsFormatChanged(int index) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsFormat(Axis::LabelsFormat(index)); } void AxisDock::labelsPrecisionChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsPrecision(value); } void AxisDock::labelsAutoPrecisionChanged(int state) { bool checked = (state == Qt::Checked); ui.sbLabelsPrecision->setEnabled(!checked); if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsAutoPrecision(checked); } void AxisDock::labelsDateTimeFormatChanged(int) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsDateTimeFormat(ui.cbLabelsDateTimeFormat->currentText()); } void AxisDock::labelsPositionChanged(int index) { auto position = Axis::LabelsPosition(index); bool b = (position != Axis::NoLabels); ui.lLabelsOffset->setEnabled(b); ui.sbLabelsOffset->setEnabled(b); ui.lLabelsRotation->setEnabled(b); ui.sbLabelsRotation->setEnabled(b); ui.lLabelsFont->setEnabled(b); ui.kfrLabelsFont->setEnabled(b); ui.lLabelsColor->setEnabled(b); ui.kcbLabelsFontColor->setEnabled(b); ui.lLabelsPrefix->setEnabled(b); ui.leLabelsPrefix->setEnabled(b); ui.lLabelsSuffix->setEnabled(b); ui.leLabelsSuffix->setEnabled(b); ui.lLabelsOpacity->setEnabled(b); ui.sbLabelsOpacity->setEnabled(b); if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsPosition(position); } void AxisDock::labelsOffsetChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsOffset( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void AxisDock::labelsRotationChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsRotationAngle(value); } void AxisDock::labelsPrefixChanged() { if (m_initializing) return; QString prefix = ui.leLabelsPrefix->text(); for (auto* axis : m_axesList) axis->setLabelsPrefix(prefix); } void AxisDock::labelsSuffixChanged() { if (m_initializing) return; QString suffix = ui.leLabelsSuffix->text(); for (auto* axis : m_axesList) axis->setLabelsSuffix(suffix); } void AxisDock::labelsFontChanged(const QFont& font) { if (m_initializing) return; QFont labelsFont = font; labelsFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); for (auto* axis : m_axesList) axis->setLabelsFont( labelsFont ); } void AxisDock::labelsFontColorChanged(const QColor& color) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsColor(color); } void AxisDock::labelsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setLabelsOpacity(opacity); } // "Grid"-tab //major grid void AxisDock::majorGridStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMajorGridColor->setEnabled(b); ui.kcbMajorGridColor->setEnabled(b); ui.lMajorGridWidth->setEnabled(b); ui.sbMajorGridWidth->setEnabled(b); ui.lMajorGridOpacity->setEnabled(b); ui.sbMajorGridOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorGridPen(); pen.setStyle(penStyle); axis->setMajorGridPen(pen); } } void AxisDock::majorGridColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorGridPen(); pen.setColor(color); axis->setMajorGridPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMajorGridStyle, color); m_initializing = false; } void AxisDock::majorGridWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorGridPen(); pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); axis->setMajorGridPen(pen); } } void AxisDock::majorGridOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMajorGridOpacity(opacity); } //minor grid void AxisDock::minorGridStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMinorGridColor->setEnabled(b); ui.kcbMinorGridColor->setEnabled(b); ui.lMinorGridWidth->setEnabled(b); ui.sbMinorGridWidth->setEnabled(b); ui.lMinorGridOpacity->setEnabled(b); ui.sbMinorGridOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorGridPen(); pen.setStyle(penStyle); axis->setMinorGridPen(pen); } } void AxisDock::minorGridColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorGridPen(); pen.setColor(color); axis->setMinorGridPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMinorGridStyle, color); m_initializing = false; } void AxisDock::minorGridWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorGridPen(); pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); axis->setMinorGridPen(pen); } } void AxisDock::minorGridOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMinorGridOpacity(opacity); } //************************************************************* //************ SLOTs for changes triggered in Axis ************ //************************************************************* void AxisDock::axisDescriptionChanged(const AbstractAspect* aspect) { if (m_axis != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void AxisDock::axisOrientationChanged(Axis::AxisOrientation orientation) { m_initializing = true; ui.cbOrientation->setCurrentIndex( (int)orientation ); m_initializing = false; } void AxisDock::axisPositionChanged(Axis::AxisPosition position) { m_initializing = true; //map from the enum Axis::AxisOrientation to the index in the combo box int index(position); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); m_initializing = false; } void AxisDock::axisPositionChanged(float value) { m_initializing = true; ui.lePosition->setText( QString::number(value) ); m_initializing = false; } void AxisDock::axisScaleChanged(Axis::AxisScale scale) { m_initializing = true; ui.cbScale->setCurrentIndex( (int)scale ); m_initializing = false; } void AxisDock::axisAutoScaleChanged(bool on) { m_initializing = true; ui.chkAutoScale->setChecked(on); m_initializing = false; } void AxisDock::axisStartChanged(double value) { m_initializing = true; ui.leStart->setText( QString::number(value) ); ui.dateTimeEditStart->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); // determine stepsize and number of decimals double diff = m_axis->end() - m_axis->start(); int decimal = determineDecimals(diff); ui.sbMajorTicksIncrementNumeric->setDecimals(decimal); ui.sbMajorTicksIncrementNumeric->setSingleStep(determineStep(diff, decimal)); m_initializing = false; } void AxisDock::axisEndChanged(double value) { m_initializing = true; ui.leEnd->setText( QString::number(value) ); ui.dateTimeEditEnd->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); ui.sbMajorTicksIncrementNumeric->setSingleStep(floor(m_axis->end() - m_axis->start())/10); // determine stepsize and number of decimals double diff = m_axis->end() - m_axis->start(); int decimal = determineDecimals(diff); ui.sbMajorTicksIncrementNumeric->setDecimals(decimal); ui.sbMajorTicksIncrementNumeric->setSingleStep(determineStep(diff, decimal)); m_initializing = false; } void AxisDock::axisZeroOffsetChanged(qreal value) { m_initializing = true; ui.leZeroOffset->setText( QString::number(value) ); m_initializing = false; } void AxisDock::axisScalingFactorChanged(qreal value) { m_initializing = true; ui.leScalingFactor->setText( QString::number(value) ); m_initializing = false; } //line void AxisDock::axisLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( pen.style() ); ui.kcbLineColor->setColor( pen.color() ); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color() ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point) ); m_initializing = false; } void AxisDock::axisArrowTypeChanged(Axis::ArrowType type) { m_initializing = true; ui.cbArrowType->setCurrentIndex((int)type); m_initializing = false; } void AxisDock::axisLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisArrowPositionChanged(Axis::ArrowPosition position) { m_initializing = true; ui.cbArrowPosition->setCurrentIndex( (int)position ); m_initializing = false; } void AxisDock::axisArrowSizeChanged(qreal size) { m_initializing = true; ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } //major ticks void AxisDock::axisMajorTicksDirectionChanged(Axis::TicksDirection direction) { m_initializing = true; ui.cbMajorTicksDirection->setCurrentIndex(direction); m_initializing = false; } void AxisDock::axisMajorTicksTypeChanged(Axis::TicksType type) { m_initializing = true; ui.cbMajorTicksType->setCurrentIndex(type); m_initializing = false; } void AxisDock::axisMajorTicksNumberChanged(int number) { m_initializing = true; ui.sbMajorTicksNumber->setValue(number); m_initializing = false; } void AxisDock::axisMajorTicksIncrementChanged(qreal increment) { m_initializing = true; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) ui.sbMajorTicksIncrementNumeric->setValue(increment); else { dtsbMajorTicksIncrement->setValue(increment); } } m_initializing = false; } void AxisDock::axisMajorTicksPenChanged(const QPen& pen) { m_initializing = true; ui.cbMajorTicksLineStyle->setCurrentIndex(pen.style()); ui.kcbMajorTicksColor->setColor(pen.color()); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMajorTicksLengthChanged(qreal length) { m_initializing = true; ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(length,Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMajorTicksOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMajorTicksOpacity->setValue( round(opacity*100.0)); m_initializing = false; } //minor ticks void AxisDock::axisMinorTicksDirectionChanged(Axis::TicksDirection direction) { m_initializing = true; ui.cbMinorTicksDirection->setCurrentIndex(direction); m_initializing = false; } void AxisDock::axisMinorTicksTypeChanged(Axis::TicksType type) { m_initializing = true; ui.cbMinorTicksType->setCurrentIndex(type); m_initializing = false; } void AxisDock::axisMinorTicksNumberChanged(int number) { m_initializing = true; ui.sbMinorTicksNumber->setValue(number); m_initializing = false; } void AxisDock::axisMinorTicksIncrementChanged(qreal increment) { m_initializing = true; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) ui.sbMinorTicksIncrementNumeric->setValue(increment); else { dtsbMinorTicksIncrement->setValue(increment); } } m_initializing = false; } void AxisDock::axisMinorTicksPenChanged(const QPen& pen) { m_initializing = true; ui.cbMinorTicksLineStyle->setCurrentIndex(pen.style()); ui.kcbMinorTicksColor->setColor(pen.color()); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMinorTicksLengthChanged(qreal length) { m_initializing = true; ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(length,Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMinorTicksOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMinorTicksOpacity->setValue(round(opacity*100.0)); m_initializing = false; } //labels void AxisDock::axisLabelsFormatChanged(Axis::LabelsFormat format) { m_initializing = true; ui.cbLabelsFormat->setCurrentIndex(format); m_initializing = false; } void AxisDock::axisLabelsAutoPrecisionChanged(bool on) { m_initializing = true; ui.chkLabelsAutoPrecision->setChecked((int) on); m_initializing = false; } void AxisDock::axisLabelsPrecisionChanged(int precision) { m_initializing = true; ui.sbLabelsPrecision->setValue(precision); m_initializing = false; } void AxisDock::axisLabelsDateTimeFormatChanged(const QString& format) { m_initializing = true; ui.cbLabelsDateTimeFormat->setCurrentText(format); m_initializing = false; } void AxisDock::axisLabelsPositionChanged(Axis::LabelsPosition position) { m_initializing = true; ui.cbLabelsPosition->setCurrentIndex(position); m_initializing = false; } void AxisDock::axisLabelsOffsetChanged(double offset) { m_initializing = true; ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(offset, Worksheet::Point) ); m_initializing = false; } void AxisDock::axisLabelsRotationAngleChanged(qreal rotation) { m_initializing = true; ui.sbLabelsRotation->setValue(rotation); m_initializing = false; } void AxisDock::axisLabelsFontChanged(const QFont& font) { m_initializing = true; //we need to set the font size in points for KFontRequester QFont newFont(font); newFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrLabelsFont->setFont(newFont); m_initializing = false; } void AxisDock::axisLabelsFontColorChanged(const QColor& color) { m_initializing = true; ui.kcbLabelsFontColor->setColor(color); m_initializing = false; } void AxisDock::axisLabelsPrefixChanged(const QString& prefix) { m_initializing = true; ui.leLabelsPrefix->setText(prefix); m_initializing = false; } void AxisDock::axisLabelsSuffixChanged(const QString& suffix) { m_initializing = true; ui.leLabelsSuffix->setText(suffix); m_initializing = false; } void AxisDock::axisLabelsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLabelsOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //grid void AxisDock::axisMajorGridPenChanged(const QPen& pen) { m_initializing = true; ui.cbMajorGridStyle->setCurrentIndex((int) pen.style()); ui.kcbMajorGridColor->setColor(pen.color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, pen.color()); ui.sbMajorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); m_initializing = false; } void AxisDock::axisMajorGridOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMajorGridOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisMinorGridPenChanged(const QPen& pen) { m_initializing = true; ui.cbMinorGridStyle->setCurrentIndex((int) pen.style()); ui.kcbMinorGridColor->setColor(pen.color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, pen.color()); ui.sbMinorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); m_initializing = false; } void AxisDock::axisMinorGridOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMinorGridOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //************************************************************* //************************* Settings ************************** //************************************************************* void AxisDock::load() { //General ui.chkVisible->setChecked( m_axis->isVisible() ); ui.cbOrientation->setCurrentIndex( (int) m_axis->orientation() ); int index = (int)m_axis->position(); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); ui.lePosition->setText( QString::number( m_axis->offset()) ); ui.cbScale->setCurrentIndex( (int) m_axis->scale() ); ui.chkAutoScale->setChecked( m_axis->autoScale() ); ui.leStart->setText( QString::number(m_axis->start()) ); ui.leEnd->setText( QString::number(m_axis->end()) ); ui.sbMajorTicksIncrementNumeric->setDecimals(0); ui.sbMajorTicksIncrementNumeric->setSingleStep(m_axis->majorTicksIncrement()); //depending on range format of the axis (numeric vs. datetime), show/hide the corresponding widgets const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); //ranges ui.lStart->setVisible(numeric); ui.lEnd->setVisible(numeric); ui.leStart->setVisible(numeric); ui.leEnd->setVisible(numeric); ui.lStartDateTime->setVisible(!numeric); ui.dateTimeEditStart->setVisible(!numeric); ui.lEndDateTime->setVisible(!numeric); ui.dateTimeEditEnd->setVisible(!numeric); //tick labels format ui.lLabelsFormat->setVisible(numeric); ui.cbLabelsFormat->setVisible(numeric); ui.chkLabelsAutoPrecision->setVisible(numeric); ui.lLabelsPrecision->setVisible(numeric); ui.sbLabelsPrecision->setVisible(numeric); ui.cbLabelsDateTimeFormat->setVisible(numeric); ui.lLabelsDateTimeFormat->setVisible(!numeric); ui.cbLabelsDateTimeFormat->setVisible(!numeric); if (!numeric) { if (m_axis->orientation() == Axis::AxisHorizontal) { ui.dateTimeEditStart->setDisplayFormat(plot->xRangeDateTimeFormat()); ui.dateTimeEditEnd->setDisplayFormat(plot->xRangeDateTimeFormat()); } else { ui.dateTimeEditStart->setDisplayFormat(plot->yRangeDateTimeFormat()); ui.dateTimeEditEnd->setDisplayFormat(plot->yRangeDateTimeFormat()); } ui.dateTimeEditStart->setDateTime(QDateTime::fromMSecsSinceEpoch(m_axis->start())); ui.dateTimeEditEnd->setDateTime(QDateTime::fromMSecsSinceEpoch(m_axis->end())); } } ui.leZeroOffset->setText( QString::number(m_axis->zeroOffset()) ); ui.leScalingFactor->setText( QString::number(m_axis->scalingFactor()) ); //Line ui.cbLineStyle->setCurrentIndex( (int) m_axis->linePen().style() ); ui.kcbLineColor->setColor( m_axis->linePen().color() ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->linePen().widthF(),Worksheet::Point) ); ui.sbLineOpacity->setValue( round(m_axis->lineOpacity()*100.0) ); ui.cbArrowType->setCurrentIndex( (int)m_axis->arrowType() ); ui.cbArrowPosition->setCurrentIndex( (int)m_axis->arrowPosition() ); ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(m_axis->arrowSize(), Worksheet::Point) ); //Major ticks ui.cbMajorTicksDirection->setCurrentIndex( (int) m_axis->majorTicksDirection() ); ui.cbMajorTicksType->setCurrentIndex( (int) m_axis->majorTicksType() ); ui.sbMajorTicksNumber->setValue( m_axis->majorTicksNumber() ); ui.cbMajorTicksLineStyle->setCurrentIndex( (int) m_axis->majorTicksPen().style() ); ui.kcbMajorTicksColor->setColor( m_axis->majorTicksPen().color() ); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksPen().widthF(),Worksheet::Point) ); ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksLength(),Worksheet::Point) ); ui.sbMajorTicksOpacity->setValue( round(m_axis->majorTicksOpacity()*100.0) ); //Minor ticks ui.cbMinorTicksDirection->setCurrentIndex( (int) m_axis->minorTicksDirection() ); ui.cbMinorTicksType->setCurrentIndex( (int) m_axis->minorTicksType() ); ui.sbMinorTicksNumber->setValue( m_axis->minorTicksNumber() ); ui.cbMinorTicksLineStyle->setCurrentIndex( (int) m_axis->minorTicksPen().style() ); ui.kcbMinorTicksColor->setColor( m_axis->minorTicksPen().color() ); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksPen().widthF(),Worksheet::Point) ); ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksLength(),Worksheet::Point) ); ui.sbMinorTicksOpacity->setValue( round(m_axis->minorTicksOpacity()*100.0) ); //Extra ticks //TODO // Tick label ui.cbLabelsPosition->setCurrentIndex( (int) m_axis->labelsPosition() ); ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(m_axis->labelsOffset(),Worksheet::Point) ); ui.sbLabelsRotation->setValue( m_axis->labelsRotationAngle() ); ui.cbLabelsFormat->setCurrentIndex( (int) m_axis->labelsFormat() ); ui.chkLabelsAutoPrecision->setChecked( (int) m_axis->labelsAutoPrecision() ); ui.sbLabelsPrecision->setValue( (int)m_axis->labelsPrecision() ); ui.cbLabelsDateTimeFormat->setCurrentText(m_axis->labelsDateTimeFormat()); //we need to set the font size in points for KFontRequester QFont font = m_axis->labelsFont(); font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrLabelsFont->setFont( font ); ui.kcbLabelsFontColor->setColor( m_axis->labelsColor() ); ui.leLabelsPrefix->setText( m_axis->labelsPrefix() ); ui.leLabelsSuffix->setText( m_axis->labelsSuffix() ); ui.sbLabelsOpacity->setValue( round(m_axis->labelsOpacity()*100.0) ); //Grid ui.cbMajorGridStyle->setCurrentIndex( (int) m_axis->majorGridPen().style() ); ui.kcbMajorGridColor->setColor( m_axis->majorGridPen().color() ); ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->majorGridPen().widthF(),Worksheet::Point) ); ui.sbMajorGridOpacity->setValue( round(m_axis->majorGridOpacity()*100.0) ); ui.cbMinorGridStyle->setCurrentIndex( (int) m_axis->minorGridPen().style() ); ui.kcbMinorGridColor->setColor( m_axis->minorGridPen().color() ); ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorGridPen().widthF(),Worksheet::Point) ); ui.sbMinorGridOpacity->setValue( round(m_axis->minorGridOpacity()*100.0) ); GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); this->majorTicksTypeChanged(ui.cbMajorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, ui.kcbMajorTicksColor->color()); this->minorTicksTypeChanged(ui.cbMinorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, ui.kcbMinorTicksColor->color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, ui.kcbMajorGridColor->color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, ui.kcbMinorGridColor->color()); } /*! * Determine the number of decimals for using in a QDoubleSpinBox * \param diff * \return */ int AxisDock::determineDecimals(double diff) { diff /= 10; // step one decimal before double power10 = 1; for (int i = 0; i < 10; i++) { double nearest = round(diff * power10) / power10; if (nearest > 0) { return i; } power10 *= 10; } return 10; } /*! * Determine the step in a QDoubleSpinBox with specific decimals and diff * \param diff Difference between the largest value and smallest value * \param decimal * \return */ double AxisDock::determineStep(double diff, int decimal) { double ten = 1; if (decimal == 0) { for (unsigned int i = 1; i < 1000000000; i++) { if (diff/ten <= 10) { return ten/10; // use one decimal before } ten *= 10; } return 1; } return static_cast(1)/(pow(10,decimal)); } void AxisDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_axesList.size(); if (size > 1) m_axis->beginMacro(i18n("%1 axes: template \"%2\" loaded", size, name)); else m_axis->beginMacro(i18n("%1: template \"%2\" loaded", m_axis->name(), name)); this->loadConfig(config); m_axis->endMacro(); } void AxisDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "Axis" ); bool numeric = false; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); } //General ui.cbOrientation->setCurrentIndex( group.readEntry("Orientation", (int) m_axis->orientation()) ); int index = group.readEntry("Position", (int) m_axis->position()); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); ui.lePosition->setText( QString::number( group.readEntry("PositionOffset", m_axis->offset())) ); ui.cbScale->setCurrentIndex( group.readEntry("Scale", (int) m_axis->scale()) ); ui.chkAutoScale->setChecked(group.readEntry("AutoScale", m_axis->autoScale())); ui.leStart->setText( QString::number( group.readEntry("Start", m_axis->start())) ); ui.leEnd->setText( QString::number( group.readEntry("End", m_axis->end())) ); ui.leZeroOffset->setText( QString::number( group.readEntry("ZeroOffset", m_axis->zeroOffset())) ); ui.leScalingFactor->setText( QString::number( group.readEntry("ScalingFactor", m_axis->scalingFactor())) ); //Title KConfigGroup axisLabelGroup = config.group("AxisLabel"); labelWidget->loadConfig(axisLabelGroup); //Line ui.cbLineStyle->setCurrentIndex( group.readEntry("LineStyle", (int) m_axis->linePen().style()) ); ui.kcbLineColor->setColor( group.readEntry("LineColor", m_axis->linePen().color()) ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_axis->linePen().widthF()),Worksheet::Point) ); ui.sbLineOpacity->setValue( round(group.readEntry("LineOpacity", m_axis->lineOpacity())*100.0) ); ui.cbArrowType->setCurrentIndex( group.readEntry("ArrowType", (int) m_axis->arrowType()) ); ui.cbArrowPosition->setCurrentIndex( group.readEntry("ArrowPosition", (int) m_axis->arrowPosition()) ); ui.sbArrowSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ArrowSize", m_axis->arrowSize()), Worksheet::Point) ); //Major ticks ui.cbMajorTicksDirection->setCurrentIndex( group.readEntry("MajorTicksDirection", (int) m_axis->majorTicksDirection()) ); ui.cbMajorTicksType->setCurrentIndex( group.readEntry("MajorTicksType", (int) m_axis->majorTicksType()) ); ui.sbMajorTicksNumber->setValue( group.readEntry("MajorTicksNumber", m_axis->majorTicksNumber()) ); if (numeric) ui.sbMajorTicksIncrementNumeric->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); else dtsbMajorTicksIncrement->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); ui.cbMajorTicksLineStyle->setCurrentIndex( group.readEntry("MajorTicksLineStyle", (int) m_axis->majorTicksPen().style()) ); ui.kcbMajorTicksColor->setColor( group.readEntry("MajorTicksColor", m_axis->majorTicksPen().color()) ); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksWidth", m_axis->majorTicksPen().widthF()),Worksheet::Point) ); ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksLength", m_axis->majorTicksLength()),Worksheet::Point) ); ui.sbMajorTicksOpacity->setValue( round(group.readEntry("MajorTicksOpacity", m_axis->majorTicksOpacity())*100.0) ); //Minor ticks ui.cbMinorTicksDirection->setCurrentIndex( group.readEntry("MinorTicksDirection", (int) m_axis->minorTicksDirection()) ); ui.cbMinorTicksType->setCurrentIndex( group.readEntry("MinorTicksType", (int) m_axis->minorTicksType()) ); ui.sbMinorTicksNumber->setValue( group.readEntry("MinorTicksNumber", m_axis->minorTicksNumber()) ); if (numeric) ui.sbMinorTicksIncrementNumeric->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); else dtsbMinorTicksIncrement->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); ui.cbMinorTicksLineStyle->setCurrentIndex( group.readEntry("MinorTicksLineStyle", (int) m_axis->minorTicksPen().style()) ); ui.kcbMinorTicksColor->setColor( group.readEntry("MinorTicksColor", m_axis->minorTicksPen().color()) ); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksWidth", m_axis->minorTicksPen().widthF()),Worksheet::Point) ); ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksLength", m_axis->minorTicksLength()),Worksheet::Point) ); ui.sbMinorTicksOpacity->setValue( round(group.readEntry("MinorTicksOpacity", m_axis->minorTicksOpacity())*100.0) ); //Extra ticks //TODO // Tick label ui.cbLabelsFormat->setCurrentIndex( group.readEntry("LabelsFormat", (int) m_axis->labelsFormat()) ); ui.chkLabelsAutoPrecision->setChecked( group.readEntry("LabelsAutoPrecision", (int) m_axis->labelsAutoPrecision()) ); ui.sbLabelsPrecision->setValue( group.readEntry("LabelsPrecision", (int)m_axis->labelsPrecision()) ); ui.cbLabelsDateTimeFormat->setCurrentText( group.readEntry("LabelsDateTimeFormat", "yyyy-MM-dd hh:mm:ss") ); ui.cbLabelsPosition->setCurrentIndex( group.readEntry("LabelsPosition", (int) m_axis->labelsPosition()) ); ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LabelsOffset", m_axis->labelsOffset()), Worksheet::Point) ); ui.sbLabelsRotation->setValue( group.readEntry("LabelsRotation", m_axis->labelsRotationAngle()) ); //we need to set the font size in points for KFontRequester QFont font = m_axis->labelsFont(); font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrLabelsFont->setFont( group.readEntry("LabelsFont", font) ); ui.kcbLabelsFontColor->setColor( group.readEntry("LabelsFontColor", m_axis->labelsColor()) ); ui.leLabelsPrefix->setText( group.readEntry("LabelsPrefix", m_axis->labelsPrefix()) ); ui.leLabelsSuffix->setText( group.readEntry("LabelsSuffix", m_axis->labelsSuffix()) ); ui.sbLabelsOpacity->setValue( round(group.readEntry("LabelsOpacity", m_axis->labelsOpacity())*100.0) ); //Grid ui.cbMajorGridStyle->setCurrentIndex( group.readEntry("MajorGridStyle", (int) m_axis->majorGridPen().style()) ); ui.kcbMajorGridColor->setColor( group.readEntry("MajorGridColor", m_axis->majorGridPen().color()) ); ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorGridWidth", m_axis->majorGridPen().widthF()),Worksheet::Point) ); ui.sbMajorGridOpacity->setValue( round(group.readEntry("MajorGridOpacity", m_axis->majorGridOpacity())*100.0) ); ui.cbMinorGridStyle->setCurrentIndex( group.readEntry("MinorGridStyle", (int) m_axis->minorGridPen().style()) ); ui.kcbMinorGridColor->setColor( group.readEntry("MinorGridColor", m_axis->minorGridPen().color()) ); ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorGridWidth", m_axis->minorGridPen().widthF()),Worksheet::Point) ); ui.sbMinorGridOpacity->setValue( round(group.readEntry("MinorGridOpacity", m_axis->minorGridOpacity())*100.0) ); m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); this->majorTicksTypeChanged(ui.cbMajorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, ui.kcbMajorTicksColor->color()); this->minorTicksTypeChanged(ui.cbMinorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, ui.kcbMinorTicksColor->color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, ui.kcbMajorGridColor->color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, ui.kcbMinorGridColor->color()); m_initializing = false; } void AxisDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Axis" ); bool numeric = false; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); } //General group.writeEntry("Orientation", ui.cbOrientation->currentIndex()); if (ui.cbPosition->currentIndex() == 2) { group.writeEntry("Position", (int)Axis::AxisCentered); } else if (ui.cbPosition->currentIndex() == 3) { group.writeEntry("Position", (int)Axis::AxisCustom); } else { if ( ui.cbOrientation->currentIndex() == Axis::AxisHorizontal ) group.writeEntry("Position", ui.cbPosition->currentIndex()); else group.writeEntry("Position", ui.cbPosition->currentIndex()+2); } group.writeEntry("PositionOffset", ui.lePosition->text()); group.writeEntry("Scale", ui.cbScale->currentIndex()); group.writeEntry("Start", ui.leStart->text()); group.writeEntry("End", ui.leEnd->text()); group.writeEntry("ZeroOffset", ui.leZeroOffset->text()); group.writeEntry("ScalingFactor", ui.leScalingFactor->text()); //Title KConfigGroup axisLabelGroup = config.group("AxisLabel"); labelWidget->saveConfig(axisLabelGroup); //Line group.writeEntry("LineStyle", ui.cbLineStyle->currentIndex()); group.writeEntry("LineColor", ui.kcbLineColor->color()); group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(), Worksheet::Point)); group.writeEntry("LineOpacity", ui.sbLineOpacity->value()/100); //Major ticks group.writeEntry("MajorTicksDirection", ui.cbMajorTicksDirection->currentIndex()); group.writeEntry("MajorTicksType", ui.cbMajorTicksType->currentIndex()); group.writeEntry("MajorTicksNumber", ui.sbMajorTicksNumber->value()); if (numeric) group.writeEntry("MajorTicksIncrement", QString::number(ui.sbMajorTicksIncrementNumeric->value())); else group.writeEntry("MajorTicksIncrement", QString::number(dtsbMajorTicksIncrement->value())); group.writeEntry("MajorTicksLineStyle", ui.cbMajorTicksLineStyle->currentIndex()); group.writeEntry("MajorTicksColor", ui.kcbMajorTicksColor->color()); group.writeEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(ui.sbMajorTicksWidth->value(),Worksheet::Point)); group.writeEntry("MajorTicksLength", Worksheet::convertToSceneUnits(ui.sbMajorTicksLength->value(),Worksheet::Point)); group.writeEntry("MajorTicksOpacity", ui.sbMajorTicksOpacity->value()/100); //Minor ticks group.writeEntry("MinorTicksDirection", ui.cbMinorTicksDirection->currentIndex()); group.writeEntry("MinorTicksType", ui.cbMinorTicksType->currentIndex()); group.writeEntry("MinorTicksNumber", ui.sbMinorTicksNumber->value()); if (numeric) group.writeEntry("MinorTicksIncrement", QString::number(ui.sbMinorTicksIncrementNumeric->value())); else group.writeEntry("MinorTicksIncrement", QString::number(dtsbMinorTicksIncrement->value())); group.writeEntry("MinorTicksLineStyle", ui.cbMinorTicksLineStyle->currentIndex()); group.writeEntry("MinorTicksColor", ui.kcbMinorTicksColor->color()); group.writeEntry("MinorTicksWidth", Worksheet::convertFromSceneUnits(ui.sbMinorTicksWidth->value(),Worksheet::Point)); group.writeEntry("MinorTicksLength", Worksheet::convertFromSceneUnits(ui.sbMinorTicksLength->value(),Worksheet::Point)); group.writeEntry("MinorTicksOpacity", ui.sbMinorTicksOpacity->value()/100); //Extra ticks // TODO // Tick label group.writeEntry("LabelsFormat", ui.cbLabelsFormat->currentIndex()); group.writeEntry("LabelsAutoPrecision", ui.chkLabelsAutoPrecision->isChecked()); group.writeEntry("LabelsPrecision", ui.sbLabelsPrecision->value()); group.writeEntry("LabelsPosition", ui.cbLabelsPosition->currentIndex()); group.writeEntry("LabelsOffset", Worksheet::convertToSceneUnits(ui.sbLabelsOffset->value(), Worksheet::Point)); group.writeEntry("LabelsRotation", ui.sbLabelsRotation->value()); group.writeEntry("LabelsFont", ui.kfrLabelsFont->font()); group.writeEntry("LabelsFontColor", ui.kcbLabelsFontColor->color()); group.writeEntry("LabelsPrefix", ui.leLabelsPrefix->text()); group.writeEntry("LabelsSuffix", ui.leLabelsSuffix->text()); group.writeEntry("LabelsOpacity", ui.sbLabelsOpacity->value()/100); //Grid group.writeEntry("MajorGridStyle", ui.cbMajorGridStyle->currentIndex()); group.writeEntry("MajorGridColor", ui.kcbMajorGridColor->color()); group.writeEntry("MajorGridWidth", Worksheet::convertToSceneUnits(ui.sbMajorGridWidth->value(), Worksheet::Point)); group.writeEntry("MajorGridOpacity", ui.sbMajorGridOpacity->value()/100); group.writeEntry("MinorGridStyle", ui.cbMinorGridStyle->currentIndex()); group.writeEntry("MinorGridColor", ui.kcbMinorGridColor->color()); group.writeEntry("MinorGridWidth", Worksheet::convertToSceneUnits(ui.sbMinorGridWidth->value(), Worksheet::Point)); group.writeEntry("MinorGridOpacity", ui.sbMinorGridOpacity->value()/100); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/CursorDock.cpp b/src/kdefrontend/dockwidgets/CursorDock.cpp index ba634e5c3..cfcbc5557 100644 --- a/src/kdefrontend/dockwidgets/CursorDock.cpp +++ b/src/kdefrontend/dockwidgets/CursorDock.cpp @@ -1,134 +1,133 @@ /*************************************************************************** File : CursorDock.cpp Project : LabPlot Description : This dock represents the data from the cursors in the cartesian plots -------------------------------------------------------------------- Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CursorDock.h" #include "ui_cursordock.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/WorksheetPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/TreeModel.h" -CursorDock::CursorDock(QWidget *parent) : - QWidget(parent), - ui(new Ui::CursorDock) -{ +CursorDock::CursorDock(QWidget* parent) : QWidget(parent), ui(new Ui::CursorDock) { ui->setupUi(this); ui->tvCursorData->setModel(nullptr); connect(ui->bCollapseAll, &QPushButton::clicked, this, &CursorDock::collapseAll); connect(ui->bExpandAll, &QPushButton::clicked, this, &CursorDock::expandAll); connect(ui->cbCursor0en, &QCheckBox::clicked, this, &CursorDock::cursor0EnableChanged); connect(ui->cbCursor1en, &QCheckBox::clicked, this, &CursorDock::cursor1EnableChanged); } -void CursorDock::setPlots(QVector list) { +void CursorDock::setWorksheet(Worksheet* worksheet) { m_initializing = true; - m_plotList = list; - m_plot = list.first(); + + ui->tvCursorData->setModel(worksheet->cursorModel()); + m_plotList = worksheet->children(); + m_plot = m_plotList.first(); bool cursor0Enabled = m_plot->cursor0Enable(); bool cursor1Enabled = m_plot->cursor1Enable(); ui->cbCursor0en->setChecked(cursor0Enabled); ui->cbCursor1en->setChecked(cursor1Enabled); ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR0, !cursor0Enabled); ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR1, !cursor1Enabled); if (!cursor0Enabled) ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !cursor0Enabled); else if(cursor1Enabled) ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !cursor0Enabled); else ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, true); ui->tvCursorData->expandAll(); - connect(m_plot, &CartesianPlot::cursor0EnableChanged, this, &CursorDock::plotCursor0EnableChanged); - connect(m_plot, &CartesianPlot::cursor1EnableChanged, this, &CursorDock::plotCursor1EnableChanged); + // connect all plots as a workaround to not be able to know which plot is selected + for (auto connection: selectedPlotsConnection) + disconnect(connection); + for (auto* plot : m_plotList) { + selectedPlotsConnection << connect(plot, &CartesianPlot::cursor0EnableChanged, this, &CursorDock::plotCursor0EnableChanged); + selectedPlotsConnection << connect(plot, &CartesianPlot::cursor1EnableChanged, this, &CursorDock::plotCursor1EnableChanged); + } m_initializing = false; } CursorDock::~CursorDock() { delete ui; } void CursorDock::collapseAll() { ui->tvCursorData->collapseAll(); } void CursorDock::expandAll() { ui->tvCursorData->expandAll(); } -void CursorDock::setCursorTreeViewModel(TreeModel* model) { - ui->tvCursorData->setModel(model); -} - void CursorDock::cursor0EnableChanged(bool enable) { if (m_initializing) return; for (auto* plot : m_plotList) plot->setCursor0Enable(enable); } void CursorDock::cursor1EnableChanged(bool enable) { if (m_initializing) return; for (auto* plot : m_plotList) plot->setCursor1Enable(enable); } // ############################################################# // back from plot // ############################################################# void CursorDock::plotCursor0EnableChanged(bool enable) { m_initializing = true; ui->cbCursor0en->setChecked(enable); ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR0, !enable); if (!enable) ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); else if (ui->cbCursor1en->isChecked()) ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); m_initializing = false; } void CursorDock::plotCursor1EnableChanged(bool enable) { m_initializing = true; ui->cbCursor1en->setChecked(enable); ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR1, !enable); if (!enable) ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); else if (ui->cbCursor0en->isChecked()) ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); m_initializing = false; } - diff --git a/src/kdefrontend/dockwidgets/CursorDock.h b/src/kdefrontend/dockwidgets/CursorDock.h index 576d9c047..aa47ab30f 100644 --- a/src/kdefrontend/dockwidgets/CursorDock.h +++ b/src/kdefrontend/dockwidgets/CursorDock.h @@ -1,69 +1,71 @@ /*************************************************************************** File : CursorDock.cpp Project : LabPlot Description : This dock represents the data from the cursors in the cartesian plots -------------------------------------------------------------------- Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef CURSORDOCK_H #define CURSORDOCK_H #include namespace Ui { class CursorDock; } +class Worksheet; class CartesianPlot; -class TreeModel; /*! * \brief The CursorDock class * This class represents the data from the cursors from the cartesian plots in a treeview */ class CursorDock : public QWidget { Q_OBJECT public: - explicit CursorDock(QWidget *parent = nullptr); + explicit CursorDock(QWidget* parent = nullptr); ~CursorDock(); - void setCursorTreeViewModel(TreeModel* model); - void setPlots(QVector); + void setWorksheet(Worksheet*); + public slots: - void plotCursor0EnableChanged(bool enable); - void plotCursor1EnableChanged(bool enable); + void plotCursor0EnableChanged(bool); + void plotCursor1EnableChanged(bool); + private: void collapseAll(); void expandAll(); - void cursor0EnableChanged(bool enable); - void cursor1EnableChanged(bool enable); + void cursor0EnableChanged(bool); + void cursor1EnableChanged(bool); - Ui::CursorDock *ui; + Ui::CursorDock* ui; private: bool m_initializing{false}; QVector m_plotList; CartesianPlot* m_plot{nullptr}; + QList selectedPlotsConnection; }; #endif // CURSORDOCK_H diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp index 791f6be97..ffcefdcb7 100644 --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -1,950 +1,948 @@ /*************************************************************************** File : LiveDataDock.cpp Project : LabPlot Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) Copyright : (C) 2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017-2019 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 "LiveDataDock.h" #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include "kdefrontend/datasources/MQTTSubscriptionWidget.h" #include #include #include #endif LiveDataDock::LiveDataDock(QWidget* parent) : BaseDock(parent) #ifdef HAVE_MQTT , m_subscriptionWidget(new MQTTSubscriptionWidget(this)) #endif { ui.setupUi(this); m_leName = ui.leName; //leComment = // not available ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); connect(ui.leName, &QLineEdit::textChanged, this, &LiveDataDock::nameChanged); connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading); connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow); connect(ui.sbUpdateInterval, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged); connect(ui.sbKeepNValues, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::keepNValuesChanged); connect(ui.sbSampleSize, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::sampleSizeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged); #ifdef HAVE_MQTT connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); connect(ui.bLWT, &QPushButton::clicked, this, &LiveDataDock::showWillSettings); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &LiveDataDock::enableWill); ui.swSubscriptions->addWidget(m_subscriptionWidget); ui.swSubscriptions->setCurrentWidget(m_subscriptionWidget); ui.bLWT->setToolTip(i18n("Manage MQTT connection's will settings")); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); QString info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif } #ifdef HAVE_MQTT LiveDataDock::~LiveDataDock() { for (auto & host : m_hosts) delete host.client; delete m_subscriptionWidget; } #else LiveDataDock::~LiveDataDock() = default; #endif #ifdef HAVE_MQTT /*! * \brief Sets the MQTTClient of this dock widget * \param clients */ void LiveDataDock::setMQTTClient(MQTTClient* const client) { m_liveDataSource = nullptr; // prevent updates due to changes to input widgets if (m_mqttClient == client) return; auto oldclient = m_mqttClient; m_mqttClient = nullptr; // prevent updates due to changes to input widgets ui.leName->setText(client->name()); const QPair id(client->clientHostName(), client->clientPort()); ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(id.first).arg(id.second)); ui.sbUpdateInterval->setValue(client->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(client->updateType())); ui.cbReadingType->setCurrentIndex(static_cast(client->readingType())); if (client->updateType() == MQTTClient::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (client->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(client->keepNValues()); ui.sbKeepNValues->setEnabled(true); if (client->readingType() == MQTTClient::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else ui.sbSampleSize->setValue(client->sampleSize()); // disable "whole file" option const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); m_mqttClient = client; // updates may be applied from now on //show MQTT connected options ui.lTopics->show(); ui.swSubscriptions->setVisible(true); m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); ui.lLWT->show(); ui.bLWT->show(); m_previousHost = m_currentHost; //if there isn't a client with this hostname we instantiate a new one auto it = m_hosts.find(id); if (it == m_hosts.end()) { m_currentHost = &m_hosts[id]; m_currentHost->count = 1; m_currentHost->client = new QMqttClient; connect(client, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); connect(m_currentHost->client, &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription); m_currentHost->client->setHostname(id.first); m_currentHost->client->setPort(id.second); if (client->MQTTUseAuthentication()) { m_currentHost->client->setUsername(client->clientUserName()); m_currentHost->client->setPassword(client->clientPassword()); } if (client->MQTTUseID()) m_currentHost->client->setClientId(client->clientID()); m_currentHost->client->connectToHost(); } else { m_currentHost = &it.value(); ++m_currentHost->count; } if (m_previousMQTTClient == nullptr) { m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() { emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); }); //Fill the subscription tree(useful if the MQTTClient was loaded) QVector topics = client->topicNames(); for (const auto& topic : topics) addTopicToTree(topic); emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); } //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots //and clear the tree widgets else if (m_previousMQTTClient->clientHostName() != client->clientHostName()) { disconnect(m_updateSubscriptionConn); disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, m_previousMQTTClient, &MQTTClient::reparentTopic); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, m_previousMQTTClient, &MQTTClient::addBeforeRemoveSubscription); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, m_previousMQTTClient, &MQTTClient::removeMQTTSubscription); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, m_previousMQTTClient, &MQTTClient::addMQTTSubscription); m_previousHost->topicList = m_subscriptionWidget->getTopicList(); m_subscriptionWidget->setTopicList(m_currentHost->topicList); emit MQTTClearTopics(); //repopulating the tree widget with the already known topics of the client for (int i = 0; i < m_currentHost->addedTopics.size(); ++i) addTopicToTree(m_currentHost->addedTopics.at(i)); //fill subscriptions tree widget emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() { emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); }); connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription); } if (client->willUpdateType() == MQTTClient::OnClick && client->MQTTWillUse()) ui.bWillUpdateNow->show(); m_previousMQTTClient = oldclient; } #endif /*! * \brief Sets the live data source of this dock widget * \param sources */ void LiveDataDock::setLiveDataSource(LiveDataSource* const source) { #ifdef HAVE_MQTT m_mqttClient = nullptr; #endif - if (m_liveDataSource == source) - return; +// if (m_liveDataSource == source) +// return; m_liveDataSource = nullptr; // prevent updates due to changes to input widgets ui.leName->setText(source->name()); ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); const LiveDataSource::SourceType sourceType = source->sourceType(); const LiveDataSource::ReadingType readingType = source->readingType(); const LiveDataSource::UpdateType updateType = source->updateType(); const AbstractFileFilter::FileType fileType = source->fileType(); ui.sbUpdateInterval->setValue(source->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(updateType)); ui.cbReadingType->setCurrentIndex(static_cast(readingType)); switch (sourceType) { case LiveDataSource::FileOrPipe: ui.leSourceInfo->setText(source->fileName()); if (QFile::exists(source->fileName())) ui.leSourceInfo->setStyleSheet(QString()); else ui.leSourceInfo->setStyleSheet("QLineEdit{background:red;}"); break; case LiveDataSource::NetworkTcpSocket: case LiveDataSource::NetworkUdpSocket: ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(source->host()).arg(source->port())); break; case LiveDataSource::LocalSocket: ui.leSourceInfo->setText(source->localSocketName()); break; case LiveDataSource::SerialPort: ui.leSourceInfo->setText(source->serialPortName()); break; case LiveDataSource::MQTT: break; } if (updateType == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (source->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(source->keepNValues()); // disable "whole file" when having no file (i.e. socket or port) auto* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::WholeFile); if (sourceType == LiveDataSource::SourceType::FileOrPipe) { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //for file types other than ASCII and binary we support re-reading the whole file only //select "read whole file" and deactivate the combobox if (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary) { ui.cbReadingType->setCurrentIndex(LiveDataSource::WholeFile); ui.cbReadingType->setEnabled(false); } else ui.cbReadingType->setEnabled(true); } else { if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); } if (((sourceType == LiveDataSource::FileOrPipe || sourceType == LiveDataSource::NetworkUdpSocket) && (readingType == LiveDataSource::ContinuousFixed || readingType == LiveDataSource::FromEnd))) ui.sbSampleSize->setValue(source->sampleSize()); else { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } // disable "on new data"-option if not available model = qobject_cast(ui.cbUpdateType->model()); item = model->item(LiveDataSource::NewData); if (sourceType == LiveDataSource::NetworkTcpSocket || sourceType == LiveDataSource::NetworkUdpSocket || sourceType == LiveDataSource::SerialPort) item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); else item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui.lTopics->hide(); ui.bLWT->hide(); ui.lLWT->hide(); ui.bWillUpdateNow->hide(); ui.swSubscriptions->hide(); #ifdef HAVE_MQTT m_subscriptionWidget->hide(); m_subscriptionWidget->hide(); #endif m_liveDataSource = source; // updates may be applied from now on } /*! * \brief Modifies the sample size of the live data source or MQTTClient object * \param sampleSize */ void LiveDataDock::sampleSizeChanged(int sampleSize) { if (m_liveDataSource) m_liveDataSource->setSampleSize(sampleSize); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->setSampleSize(sampleSize); #endif } /*! * \brief Updates the live data source now */ void LiveDataDock::updateNow() { if (m_liveDataSource) m_liveDataSource->updateNow(); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->updateNow(); #endif } void LiveDataDock::nameChanged(const QString& name) { if (m_liveDataSource) { if (!m_liveDataSource->setName(name, false)) { ui.leName->setStyleSheet("background:red;"); ui.leName->setToolTip(i18n("Please choose another name, because this is already in use.")); return; } } #ifdef HAVE_MQTT else if (m_mqttClient) { if (!m_mqttClient->setName(name, false)) { ui.leName->setStyleSheet("background:red;"); ui.leName->setToolTip(i18n("Please choose another name, because this is already in use.")); return; } } #endif ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); } /*! * \brief LiveDataDock::updateTypeChanged * \param idx */ void LiveDataDock::updateTypeChanged(int idx) { if (m_liveDataSource) { DEBUG("LiveDataDock::updateTypeChanged()"); const LiveDataSource::UpdateType updateType = static_cast(idx); switch (updateType) { case LiveDataSource::TimeInterval: { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); const LiveDataSource::SourceType s = m_liveDataSource->sourceType(); const LiveDataSource::ReadingType r = m_liveDataSource->readingType(); const bool showSampleSize = ((s == LiveDataSource::FileOrPipe || s == LiveDataSource::NetworkUdpSocket) && (r == LiveDataSource::ContinuousFixed || r == LiveDataSource::FromEnd)); ui.lSampleSize->setVisible(showSampleSize); ui.sbSampleSize->setVisible(showSampleSize); m_liveDataSource->setUpdateType(updateType); m_liveDataSource->setUpdateInterval(ui.sbUpdateInterval->value()); - m_liveDataSource->setFileWatched(false); break; } case LiveDataSource::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); - m_liveDataSource->setFileWatched(true); m_liveDataSource->setUpdateType(updateType); } } #ifdef HAVE_MQTT else if (m_mqttClient) { DEBUG("LiveDataDock::updateTypeChanged()"); const MQTTClient::UpdateType type = static_cast(idx); if (type == MQTTClient::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); m_mqttClient->setUpdateType(type); m_mqttClient->setUpdateInterval(ui.sbUpdateInterval->value()); } else if (type == MQTTClient::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); m_mqttClient->setUpdateType(type); } } #endif } /*! * \brief Handles the change of the reading type in the dock widget * \param idx */ void LiveDataDock::readingTypeChanged(int idx) { if (m_liveDataSource) { const auto type = static_cast(idx); const LiveDataSource::SourceType sourceType = m_liveDataSource->sourceType(); const LiveDataSource::UpdateType updateType = m_liveDataSource->updateType(); if (sourceType == LiveDataSource::NetworkTcpSocket || sourceType == LiveDataSource::LocalSocket || sourceType == LiveDataSource::SerialPort || type == LiveDataSource::TillEnd || type == LiveDataSource::WholeFile || updateType == LiveDataSource::NewData) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } m_liveDataSource->setReadingType(type); } #ifdef HAVE_MQTT else if (m_mqttClient) { MQTTClient::ReadingType type = static_cast(idx); if (type == MQTTClient::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } m_mqttClient->setReadingType(type); } #endif } /*! * \brief Modifies the update interval of the live data source * \param updateInterval */ void LiveDataDock::updateIntervalChanged(int updateInterval) { if (m_liveDataSource) m_liveDataSource->setUpdateInterval(updateInterval); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->setUpdateInterval(updateInterval); #endif } /*! * \brief Modifies the number of samples to keep in each of the live data source * \param keepNValues */ void LiveDataDock::keepNValuesChanged(const int keepNValues) { if (m_liveDataSource) m_liveDataSource->setKeepNValues(keepNValues); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->setKeepNValues(keepNValues); #endif } /*! * \brief Pauses the reading of the live data source */ void LiveDataDock::pauseReading() { if (m_liveDataSource) m_liveDataSource->pauseReading(); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->pauseReading(); #endif } /*! * \brief Continues the reading of the live data source */ void LiveDataDock::continueReading() { if (m_liveDataSource) m_liveDataSource->continueReading(); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->continueReading(); #endif } /*! * \brief Handles the pausing/continuing of reading of the live data source */ void LiveDataDock::pauseContinueReading() { m_paused = !m_paused; if (m_paused) { pauseReading(); ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { continueReading(); ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } } #ifdef HAVE_MQTT /*! *\brief called when use will message checkbox's state is changed in the will settings widget, * Sets the mqttUseWill according to state for the m_mqttClient * * \param state the state of the checbox */ void LiveDataDock::useWillMessage(bool use) { qDebug()<<"Use will message: " << use; if (use) { m_mqttClient->setMQTTWillUse(true); if (m_mqttClient->willUpdateType() == MQTTClient::OnClick) ui.bWillUpdateNow->show(); } else { m_mqttClient->setMQTTWillUse(false); ui.bWillUpdateNow->hide(); } } /*! *\brief called when will message's QoS is changed in the will settings widget * sets the will QoS level for the m_mqttClient * * \param QoS the QoS level of the will message */ void LiveDataDock::willQoSChanged(int QoS) { m_mqttClient->setWillQoS(QoS); } /*! *\brief called when will message's retain flag is changed in the will settings widget * sets the retain flag for the will message in in m_mqttClient * * \param state the state of the will retain chechbox */ void LiveDataDock::willRetainChanged(bool useWillRetainMessages) { if (useWillRetainMessages) m_mqttClient->setWillRetain(true); else m_mqttClient->setWillRetain(false); } /*! *\brief called when will topic combobox's current item is changed in the will settings widget * sets the will topic for the m_mqttClient * * \param topic the current text of cbWillTopic */ void LiveDataDock::willTopicChanged(const QString& topic) { if (m_mqttClient->willTopic() != topic) m_mqttClient->clearLastMessage(); m_mqttClient->setWillTopic(topic); } /*! *\brief called when the selected will message type is changed in the will settings widget * sets the will message type for the m_mqttClient * * \param type the selected will message type */ void LiveDataDock::willMessageTypeChanged(MQTTClient::WillMessageType willMessageType) { m_mqttClient->setWillMessageType(willMessageType); } /*! *\brief called when the will own message is changed in the will settings widget * sets the will own message for the m_mqttClient * * \param message the will message given by the user */ void LiveDataDock::willOwnMessageChanged(const QString& message) { m_mqttClient->setWillOwnMessage(message); } /*! *\brief called when the selected update type for the will message is changed in the will settings widget * sets the will update type for the m_mqttClient * * \param type the selected will update type */ void LiveDataDock::willUpdateTypeChanged(int updateType) { m_mqttClient->setWillUpdateType(static_cast(updateType)); if (static_cast(updateType) == MQTTClient::TimePeriod) { ui.bWillUpdateNow->hide(); m_mqttClient->startWillTimer(); } else if (static_cast(updateType) == MQTTClient::OnClick) { ui.bWillUpdateNow->show(); //if update type is on click we stop the will timer m_mqttClient->stopWillTimer(); } } /*! *\brief called when the will update now button is pressed * updates the will message of m_mqttClient */ void LiveDataDock::willUpdateNow() { m_mqttClient->updateWillMessage(); } /*! *\brief called when the update interval for will message is changed in the will settings widget * sets the will update interval for the m_mqttClient, then starts the will timer for each one * * \param interval the new will update interval */ void LiveDataDock::willUpdateIntervalChanged(int interval) { m_mqttClient->setWillTimeInterval(interval); m_mqttClient->startWillTimer(); } /*! *\brief called when the will statistics are changed in the will settings widget * adds or removes the statistic represented by the index from m_mqttClient */ void LiveDataDock::statisticsChanged(MQTTClient::WillStatisticsType willStatisticsType) { if (willStatisticsType >= 0) { //if it's not already added and it's checked we add it if (!m_mqttClient->willStatistics()[static_cast(willStatisticsType)]) m_mqttClient->addWillStatistics(willStatisticsType); else //otherwise remove it m_mqttClient->removeWillStatistics(willStatisticsType); } } /*! *\brief called when the client connects to the broker successfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void LiveDataDock::onMQTTConnect() { if (!m_currentHost || !m_currentHost->client || !m_currentHost->client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) if (!m_currentHost->addedTopics.contains(topic.name())) { m_currentHost->addedTopics.push_back(topic.name()); addTopicToTree(topic.name()); } } /*! *\brief Adds topicName to twTopics * * \param topicName the name of the topic, which will be added to the tree widget */ void LiveDataDock::addTopicToTree(const QString &topicName) { QStringList name; QChar sep = '/'; QString rootName; if (topicName.contains(sep)) { QStringList list = topicName.split(sep, QString::SkipEmptyParts); if (!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); QTreeWidgetItem* currentItem; //check whether the first level of the topic can be found in twTopics int topItemIdx = -1; for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if ( topItemIdx < 0) { currentItem = new QTreeWidgetItem(name); m_subscriptionWidget->addTopic(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for (int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if (childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if (!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for (; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topicName; name.append(topicName); m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); if (rootName == subscriptionName[0]) { emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); break; } } //signals that a newTopic was added, in order to fill the completer of leTopics //we have to pass the whole topic name, not just the root name, for testing purposes emit newTopic(topicName); } /*! *\brief called when a client receives a message, if the clients hostname isn't identic with the host name of MQTTClient * if the message arrived from a new topic, the topic is added to the host data */ void LiveDataDock::mqttMessageReceivedInBackground(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) if (!m_currentHost->addedTopics.contains(topic.name())) m_currentHost->addedTopics.push_back(topic.name()); } /*! *\brief called when an MQTTClient is about to be deleted * removes every data connected to the MQTTClient, and disconnects the corresponding client from the host * * \param hostname the host name of the MQTTClient that will be deleted * \param name the host name of the MQTTClient that will be deleted */ void LiveDataDock::removeClient(const QString& hostname, quint16 port) { auto it = m_hosts.find(qMakePair(hostname, port)); if (it == m_hosts.end()) return; MQTTHost & host = it.value(); if (host.count > 1) { --host.count; return; } host.client->disconnectFromHost(); if (m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == hostname) { disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); m_previousMQTTClient = nullptr; } if (m_mqttClient->clientHostName() == hostname) { emit MQTTClearTopics(); m_mqttClient = nullptr; } delete host.client; m_hosts.erase(it); } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testSubscribe(const QString& topic) { QStringList topicList = topic.split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == topicList[0]) { currentItem = m_subscriptionWidget->topLevelTopic(i); break; } } if (currentItem) { for (int i = 1 ; i < topicList.size(); ++i) { if (topicList[i] == '#') break; for (int j = 0; j < currentItem->childCount(); ++j) { if (currentItem->child(j)->text(0) == topicList[i]) { currentItem = currentItem->child(j); break; } else if (j == currentItem->childCount() - 1) return false; } } } else return false; m_subscriptionWidget->testSubscribe(currentItem); return true; } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testUnsubscribe(const QString& topic) { QTreeWidgetItem* currentItem = nullptr; for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { if (MQTTSubscriptionWidget::checkTopicContains(m_subscriptionWidget->topLevelSubscription(i)->text(0), topic)) { currentItem = m_subscriptionWidget->topLevelSubscription(i); break; } } if (currentItem) { do { if (topic == currentItem->text(0)) { m_subscriptionWidget->testUnsubscribe(currentItem); return true; } else { for (int i = 0; i < currentItem->childCount(); ++i) { qDebug()<child(i)->text(0)<<" "<child(i)->text(0), topic)) { currentItem = currentItem->child(i); break; } else if (i == currentItem->childCount() - 1) return false; } } } while (currentItem); } else return false; return false; } void LiveDataDock::showWillSettings() { QMenu menu; const QVector& topics = m_mqttClient->topicNames(); MQTTWillSettingsWidget willSettingsWidget(&menu, m_mqttClient->willSettings(), topics); connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() { this->useWillMessage(willSettingsWidget.will().enabled); this->willMessageTypeChanged(willSettingsWidget.will().willMessageType); this->updateTypeChanged(willSettingsWidget.will().willUpdateType); this->willRetainChanged(willSettingsWidget.will().willRetain); this->willUpdateIntervalChanged(willSettingsWidget.will().willTimeInterval); this->willOwnMessageChanged(willSettingsWidget.will().willOwnMessage); this->willTopicChanged(willSettingsWidget.will().willTopic); this->statisticsChanged(willSettingsWidget.statisticsType()); menu.close(); }); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettingsWidget); menu.addAction(widgetAction); QPoint pos(ui.bLWT->sizeHint().width(), ui.bLWT->sizeHint().height()); menu.exec(ui.bLWT->mapToGlobal(pos)); } void LiveDataDock::enableWill(bool enable) { if(enable) { if(!ui.bLWT->isEnabled()) ui.bLWT->setEnabled(enable); } else ui.bLWT->setEnabled(enable); } #endif diff --git a/src/kdefrontend/spreadsheet/SortDialog.cpp b/src/kdefrontend/spreadsheet/SortDialog.cpp index ebbd3c640..90011c5a1 100644 --- a/src/kdefrontend/spreadsheet/SortDialog.cpp +++ b/src/kdefrontend/spreadsheet/SortDialog.cpp @@ -1,109 +1,108 @@ /*************************************************************************** File : SortDialog.h Project : LabPlot Description : Sorting options dialog -------------------------------------------------------------------- Copyright : (C) 2011-2018 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 "SortDialog.h" +#include "backend/core/column/Column.h" #include #include #include #include /*! \class SortDialog \brief Dialog for sorting the columns in a spreadsheet. \ingroup kdefrontend */ SortDialog::SortDialog(QWidget* parent) : QDialog(parent) { setWindowIcon(QIcon::fromTheme("view-sort-ascending")); setWindowTitle(i18nc("@title:window", "Sort Columns")); setSizeGripEnabled(true); setAttribute(Qt::WA_DeleteOnClose); ui.setupUi(this); ui.buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Sort")); connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &SortDialog::sortColumns); connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &SortDialog::reject); connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &SortDialog::accept); connect(ui.cbSorting, static_cast(&QComboBox::currentIndexChanged), this, &SortDialog::changeType); //restore saved settings if available KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("SortDialog")); if (conf.exists()) KWindowConfig::restoreWindowSize(windowHandle(), conf); else resize(QSize(300, 0).expandedTo(minimumSize())); ui.cbOrdering->setCurrentIndex(conf.readEntry(QLatin1String("Ordering"), 0)); ui.cbSorting->setCurrentIndex(conf.readEntry(QLatin1String("Sorting"), 0)); } SortDialog::~SortDialog() { //save the current settings KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("SortDialog")); // general settings conf.writeEntry(QLatin1String("Ordering"), ui.cbOrdering->currentIndex()); conf.writeEntry(QLatin1String("Sorting"), ui.cbSorting->currentIndex()); } void SortDialog::sortColumns() { - Column* leading; + Column* leading{nullptr}; if (ui.cbSorting->currentIndex() == Together) leading = m_columns.at(ui.cbColumns->currentIndex()); - else - leading = nullptr; emit sort(leading, m_columns, ui.cbOrdering->currentIndex() == Ascending); } void SortDialog::setColumns(QVector columns) { m_columns = columns; for (auto* col : m_columns) ui.cbColumns->addItem(col->name()); ui.cbColumns->setCurrentIndex(0); if (m_columns.size() == 1) { ui.lSorting->hide(); ui.cbSorting->hide(); ui.lColumns->hide(); ui.cbColumns->hide(); } } void SortDialog::changeType(int Type) { if (Type == Together) ui.cbColumns->setEnabled(true); else ui.cbColumns->setEnabled(false); } diff --git a/src/kdefrontend/spreadsheet/SortDialog.h b/src/kdefrontend/spreadsheet/SortDialog.h index 8be2faf67..15c90a22d 100644 --- a/src/kdefrontend/spreadsheet/SortDialog.h +++ b/src/kdefrontend/spreadsheet/SortDialog.h @@ -1,59 +1,59 @@ /*************************************************************************** File : SortDialog.h Project : LabPlot Description : Sorting options dialog -------------------------------------------------------------------- Copyright : (C) 2011 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 SORTDIALOG_H #define SORTDIALOG_H -#include "backend/core/column/Column.h" #include #include +class Column; class SortDialog : public QDialog { Q_OBJECT public: explicit SortDialog(QWidget* parent = nullptr); ~SortDialog() override; void setColumns(QVector); enum {Separately = 0, Together = 1}; enum {Ascending = 0, Descending = 1}; private slots: void sortColumns(); void changeType(int index); signals: void sort(Column*, QVector, bool ascending); private: Ui::SortDialogWidget ui; QVector m_columns; }; #endif diff --git a/src/kdefrontend/ui/datapickerimagewidget.ui b/src/kdefrontend/ui/datapickerimagewidget.ui index 9faf2170e..62eac8b21 100644 --- a/src/kdefrontend/ui/datapickerimagewidget.ui +++ b/src/kdefrontend/ui/datapickerimagewidget.ui @@ -1,1095 +1,1090 @@ DatapickerImageWidget 0 0 784 1181 0 0 0 General Name: 0 0 true Comment: 0 0 true Qt::Vertical QSizePolicy::Fixed 20 18 75 true Plot Image: Specify the name of the image file. true 0 0 Select the image file to import Type: - + Rotation: 0 0 ° -360.000000000000000 360.000000000000000 Qt::Vertical QSizePolicy::Fixed 20 18 1. 0 0 -9999.000000000000000 9999.000000000000000 0 0 2 -9999.000000000000000 9999.000000000000000 0 0 -9999.000000000000000 9999.000000000000000 y= Qt::AlignCenter x= Qt::AlignCenter z= Qt::AlignCenter 0 0 -9999.000000000000000 9999.000000000000000 0 0 -9999.000000000000000 9999.000000000000000 y= Qt::AlignCenter 0 0 -9999.000000000000000 9999.000000000000000 0 0 -9999.000000000000000 9999.000000000000000 x= Qt::AlignCenter Qt::Horizontal 40 20 Qt::Horizontal 40 20 z= Qt::AlignCenter 0 0 -9999.000000000000000 9999.000000000000000 y= Qt::AlignCenter Qt::Horizontal 40 20 0 0 -9999.000000000000000 9999.000000000000000 z= Qt::AlignCenter x= Qt::AlignCenter 2. 3. x+y+z= 75 true Segments Length: 0 0 px Point separation: Qt::Horizontal QSizePolicy::Fixed 13 23 0 0 px Qt::Vertical 20 231 Qt::Vertical QSizePolicy::Fixed 20 18 Qt::Vertical QSizePolicy::Fixed 20 18 75 true Reference Points Ref. Points Symbol 75 true General Style: Qt::Horizontal QSizePolicy::Fixed 13 23 - + 0 0 Size: pt 0.500000000000000 Rotation: 0 0 ° -360 360 10 0 Opacity: 0 0 The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. % 0 100 10 100 Qt::Vertical QSizePolicy::Fixed 20 18 75 true Filling Style: - + Color: 0 0 Qt::Vertical QSizePolicy::Fixed 20 18 75 true Border Style: - + Color: 0 0 Width: pt 0.500000000000000 Qt::Vertical 20 40 Visible Edit Image Intensity: - + 0 0 Vertical position relative to label's parent Qt::Vertical QSizePolicy::Fixed 20 18 Value: Hue: Qt::Vertical 20 40 Current image: Saturation: Foreground: Qt::Horizontal QSizePolicy::Fixed 13 23 KColorButton QPushButton
kcolorbutton.h
- - KComboBox - QComboBox -
kcombobox.h
-
leFileName bOpen cbGraphType sbPositionX1 sbPositionY1 sbPositionX2 sbPositionY2 sbPositionX3 sbPositionY3 cbPlotImageType lIntensity lForeground lHue lSaturation lValue
diff --git a/src/kdefrontend/ui/datasources/importfilewidget.ui b/src/kdefrontend/ui/datasources/importfilewidget.ui index 8a4c841c0..b2cc75f7d 100644 --- a/src/kdefrontend/ui/datasources/importfilewidget.ui +++ b/src/kdefrontend/ui/datasources/importfilewidget.ui @@ -1,834 +1,848 @@ ImportFileWidget 0 0 706 - 1377 + 1406 0 0 6 0 0 0 0 0 0 Data Source - - - - - - - Connection: - - - - - - - - - - Field: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Port: - - - - + QAbstractItemView::NoEditTriggers false 16 16 - + - LWT: + Topics: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - + + - - + + + + false + + + + + + + Name: + + + + + 0 0 + + Select the file to import + - - + + false + + Save the current filter settings + - - + + + + + + + Connection: + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + false - + 0 0 + + Manage filters + + + + - - + + false - + 0 0 - - Show file info + + + + + + Source: + + + + + + Port: + + + + + - - + + + + + + + Port: + + + + + + + Filter: + + + + + + + Field: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + false 0 0 - Manage filters + Show file info - - - - - - - Source: + + + + QFrame::NoFrame - - - - - - Port: + + QFrame::Plain + + + + + + File or Named Pipe Network TCP Socket Network UDP Socket Local Socket Serial Port - - - - Host: - - - - - + + 0 0 - - Select the file to import - - - - - - - - Baud rate: - - - - - + + - Type: + Host: - - + + - Name: - - - - - - - false - - - Save the current filter settings + LWT: - + - Filter: + Type: - - + + Qt::Horizontal + + QSizePolicy::Fixed + - 40 - 0 + 10 + 23 - - + + - Topics: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - QFrame::NoFrame - - - QFrame::Plain + Baud rate: - - + + - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 23 - + + + + Relative Path: - + false 0 0 true 0 0 0 0 0 0 0 Data format 0 0 0 0 0 0 0 0 0 0 0 0 0 Preview 0 0 Number of rows to preview: 1 10000 100 Qt::Horizontal 23 20 Refresh true QTextEdit::NoWrap true 0 0 Data portion to read Start row: Specify the end row to import; -1 stands for the last row -1 2147483647 -1 Start column: Specify the start column for import 1 2147483647 Qt::Horizontal QSizePolicy::Fixed 40 20 Specify the start row for import 1 2147483647 1 Qt::Horizontal 108 20 Qt::Horizontal QSizePolicy::Fixed 40 20 End column: Specify the end column to import; -1 stands for the last column -1 2147483647 -1 Qt::Horizontal 108 20 End row: Qt::Vertical 20 10 Update Options - - - - Keep last values: - - - - - - - Read: - - - - - - - - 0 - 0 - - + + - Periodically + Continuously Fixed - On New Data + From End + + + + + Till the End - - - - If this option is checked, only the link to the file is stored in the project file but not its content. - + + - Link the file - - - true + Update: - - + + - Update: + Read: Update interval: All 999999 - - - - - 0 - 0 - + + + + Keep last values: - - ms + + + + + + Sample size: + + + + - 5 + 1 - 60000 + 10000 - 1000 + 1 - - - - - Continuously Fixed - - + + + + + 0 + 0 + + - From End + Periodically - Till the End + On New Data - - - - Sample size: + + + + + 0 + 0 + + + + ms - - - - - 1 + 5 - 10000 + 60000 - 1 + 1000 + + + + + + + + + + Link the file + + + true KComboBox QComboBox
kcombobox.h
diff --git a/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui b/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui index de67af19e..77935149e 100644 --- a/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui +++ b/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui @@ -1,1444 +1,1473 @@ CartesianPlotDock 0 0 528 - 1059 + 1473 - 4 + 0 General Name: Qt::Horizontal QSizePolicy::Fixed 10 23 true Comment: true Qt::Vertical QSizePolicy::Fixed 28 18 75 true Geometry Left: cm 1000.000000000000000 0.100000000000000 Top: cm 1000.000000000000000 0.100000000000000 Width: cm 1000.000000000000000 0.100000000000000 Height: cm 1000.000000000000000 0.100000000000000 Qt::Vertical QSizePolicy::Fixed 28 40 75 true Data range Show &last points Show f&irst points Free ranges true Qt::Vertical QSizePolicy::Fixed 28 18 75 true x-Range: Auto Format: Minimum: Auto true Maximum: Auto true Minimum: true Maximum: true Scaling: Qt::Vertical QSizePolicy::Fixed 28 18 75 true y-Range: Auto Format: Minimum: Auto true Maximum: Auto true Minimum: Maximum: Scaling: Qt::Vertical 20 178 Visible Title Range Breaks 75 true x-Range Enabled: Qt::Horizontal QSizePolicy::Fixed 13 23 Qt::Horizontal 25 20 0 0 QFrame::NoFrame QFrame::Raised 0 Add new scale breaking Remove scale breaking Current scale breaking From: To: Position: Style: Qt::Vertical QSizePolicy::Fixed 28 18 75 true y-Range Enabled: Qt::Horizontal 31 20 0 0 QFrame::NoFrame QFrame::Raised 0 0 0 0 0 Add new scale breaking Remove scale breaking Current scale breaking From: To: Position: Style: Qt::Vertical 20 13 % 10 % 10 Plot Area pt 0.500000000000000 Style: Opacity: cm 1000.000000000000000 0.100000000000000 Opacity: Type: Style: Qt::Vertical QSizePolicy::Fixed 72 18 0 0 The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. % 0 100 10 100 Qt::Vertical QSizePolicy::Fixed 72 18 Horizontal: 0 0 Width: cm 1000.000000000000000 0.100000000000000 Qt::Horizontal QSizePolicy::Fixed 10 23 75 true Border 75 true Padding 75 true Background Color: Right cm 1000.000000000000000 0.100000000000000 First color: 0 0 Select the file to import Style: cm 0.500000000000000 Specify the name of the image file. true Corner radius: 0 0 The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. % 0 100 10 100 Qt::Vertical 20 79 0 0 Style: Second color: Vertical: Bottom File name: 0 0 cm 1000.000000000000000 0.100000000000000 Symmetric Cursor - + Qt::Vertical 20 1238 - - + + + + + 0 0 - Linecolor + Width: - - + + + + px + + + + + - - + + 0 0 - Linewidth + Style: - - + + 0 0 - Linestyle + Color: - + - - - - px + + + + + 75 + true + - - + + Line + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + KColorButton QPushButton
kcolorbutton.h
tabWidget leName leComment sbLeft sbTop sbWidth sbHeight rbRangeLast leRangeLast rbRangeFirst leRangeFirst rbRangeFree chkAutoScaleX cbXRangeFormat leXMin leXMax dateTimeEditXMin dateTimeEditXMax cbXScaling chkAutoScaleY cbYRangeFormat leYMin leYMax dateTimeEditYMin dateTimeEditYMax cbYScaling chkVisible chkXBreak bAddXBreak bRemoveXBreak cbXBreak leXBreakStart leXBreakEnd sbXBreakPosition cbXBreakStyle chkYBreak bAddYBreak bRemoveYBreak cbYBreak leYBreakStart leYBreakEnd sbYBreakPosition cbYBreakStyle cbBackgroundType leBackgroundFileName bOpen cbBackgroundColorStyle cbBackgroundImageStyle cbBackgroundBrushStyle kcbBackgroundFirstColor kcbBackgroundSecondColor sbBackgroundOpacity cbBorderStyle kcbBorderColor sbBorderWidth sbBorderCornerRadius sbBorderOpacity sbPaddingHorizontal sbPaddingVertical sbPaddingRight sbPaddingBottom
diff --git a/src/kdefrontend/ui/labelwidget.ui b/src/kdefrontend/ui/labelwidget.ui index 2b476043a..709d7ca1a 100644 --- a/src/kdefrontend/ui/labelwidget.ui +++ b/src/kdefrontend/ui/labelwidget.ui @@ -1,677 +1,671 @@ LabelWidget 0 0 - 633 - 1051 + 668 + 1309 - - - - - 0 - 0 - - + + Vertical position relative to label's parent - - cm - - - -99.989999999999995 - - - 0.500000000000000 - - - - - - - - - - Rotation: - - - + + 75 true QFrame::NoFrame - Text: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Font: - - - - - - - Distance to the axis tick labels - - - pt - - - -99.989999999999995 - - - 0.500000000000000 + Border - - + + - - + + - Vert. align.: - - - - - - - pt - - - 0.500000000000000 + Shape: - - + + - Color: + Main font: - - - - - 0 - 0 - - - - The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - - % - - - - - - 0 - - - 100 - - - 10 - - - 100 - - + + - - - - - 75 - true - - - - QFrame::NoFrame + + + + Qt::Vertical - - Border + + + 20 + 37 + - - - - + - - + + - + 0 0 - Vertical position relative to label's parent - - - y: + Horizontal position relative to label's parent - - - 0 0 QFrame::NoFrame QFrame::Raised 0 0 0 0 0 - - + + + + + 0 + 0 + + + + TeX mode + true - - + + true - - + + - - true + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 20 + 18 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + - - + + true - + true - - + + true - - + + - - - - - - + + true - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 20 - 18 - - - - - - - - - 0 - 0 - - - - TeX mode - + + true - - - - - 0 - 0 - - - - - 16777215 - 16777215 - + + + + - - - - Qt::Horizontal + + + + Background color: - - QSizePolicy::Fixed + + + + + + Visible - + + + + + + + 0 + 0 + + + + Vertical position relative to label's parent + + + cm + + + -99.989999999999995 + + + 0.500000000000000 + + + + + + + Font: + + + + + + + + 75 + true + + + + QFrame::NoFrame + + + Text: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Distance to the axis tick labels + + + pt + + + -99.989999999999995 + + + 0.500000000000000 + + + + + + + ° + + + -360 + + + 360 + + + 5 + + + + + + + Vert. align.: + + + + + + + Distance to the axis tick labels + + + pt + + + -99.989999999999995 + + + 0.500000000000000 + + + + + + + + 0 + 0 + + + - 13 - 23 + 20 + 20 - - - - + - - + + Qt::Vertical + + QSizePolicy::Fixed + - 20 - 37 + 58 + 20 - - - - + + + + + + + 0 + 0 + + + + + + + + + + + Horizontal position relative to label's parent + - Size: + x: - - - - - + + - Background color: + Hor. align.: 0 0 Horizontal position relative to label's parent cm -99.989999999999995 0.500000000000000 - - + + - Qt::Vertical + Qt::Horizontal QSizePolicy::Fixed - 58 - 20 + 10 + 23 - - - - Vertical position relative to label's parent + + + + + 0 + 0 + - - - - - 75 - true - - - - QFrame::NoFrame - + + - Geometry + Size: - - + + + + Distance to the axis tick labels + - Width: + Offset X: - - - - Shape: + + + + pt + + + 0.500000000000000 - - - - Style: + + + + Qt::Vertical - + + QSizePolicy::Fixed + + + + 58 + 20 + + + - - + + - Foreground color: + Width: - - + + - + 0 0 - Horizontal position relative to label's parent + The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - - - - - Horizontal position relative to label's parent + + % - - x: + + - - - - - - - 0 - 0 - + + 0 - - - - - - - 0 - 0 - + + 100 - - - 20 - 20 - + + 10 + + + 100 - - + + + + + 75 + true + + + + QFrame::NoFrame + - Opacity: + Geometry 0 0 20 20 - - - - Distance to the axis tick labels - + + - Offset Y: + Rotation: - - - - Main font: - - + + - - - - ° - - - -360 - - - 360 - - - 5 - - + + - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 58 - 20 - + + + + Foreground color: - + - - + + Distance to the axis tick labels - - pt - - - -99.989999999999995 - - - 0.500000000000000 + + Offset Y: - - + + - Hor. align.: + Color: - - - - - 0 - 0 - + + + + Style: - - - - Distance to the axis tick labels - + + + + + - Offset X: + Opacity: - - + + + + Vertical position relative to label's parent + - Visible + y: - - KComboBox - QComboBox -
kcombobox.h
-
KFontRequester QWidget
kfontrequester.h
KColorButton QPushButton
kcolorbutton.h
+ + KComboBox + QComboBox +
kcombobox.h
+
ResizableTextEdit QTextEdit
src/kdefrontend/widgets/ResizableTextEdit.h
diff --git a/src/kdefrontend/ui/spreadsheet/sortdialogwidget.ui b/src/kdefrontend/ui/spreadsheet/sortdialogwidget.ui index 39fa40a24..42248d1a6 100644 --- a/src/kdefrontend/ui/spreadsheet/sortdialogwidget.ui +++ b/src/kdefrontend/ui/spreadsheet/sortdialogwidget.ui @@ -1,123 +1,110 @@ SortDialogWidget 0 0 - 448 + 600 237 0 0 Order: 0 0 Ascending .. Descending .. Sort columns: Separately Together Leading column: Qt::Vertical 20 1 - - - - Qt::Horizontal - - - - 209 - 20 - - - - - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/kdefrontend/widgets/DatapickerImageWidget.cpp b/src/kdefrontend/widgets/DatapickerImageWidget.cpp index 6397816e1..b9c1b78d9 100644 --- a/src/kdefrontend/widgets/DatapickerImageWidget.cpp +++ b/src/kdefrontend/widgets/DatapickerImageWidget.cpp @@ -1,863 +1,897 @@ /*************************************************************************** File : DatapickerImageWidget.cpp Project : LabPlot Description : widget for datapicker properties -------------------------------------------------------------------- Copyright : (C) 2015-2016 by Ankit Wagadre (wagadre.ankit@gmail.com) - Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2015-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 "DatapickerImageWidget.h" #include "backend/datapicker/DatapickerPoint.h" #include "commonfrontend/widgets/qxtspanslider.h" #include "kdefrontend/GuiTools.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Symbol.h" #include "backend/datapicker/ImageEditor.h" #include #include #include #include #include #include #include #include #include #include #include HistogramView::HistogramView(QWidget* parent, int range) : QGraphicsView(parent), m_scene(new QGraphicsScene()), m_range(range) { setTransform(QTransform()); QRectF pageRect( 0, 0, 1000, 100 ); m_scene->setSceneRect(pageRect); setScene(m_scene); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_lowerSlider = new QGraphicsRectItem(pageRect, nullptr); m_lowerSlider->setPen(QPen(Qt::black, 0.5)); m_lowerSlider->setBrush(Qt::blue); m_lowerSlider->setOpacity(0.2); m_scene->addItem(m_lowerSlider); m_upperSlider = new QGraphicsRectItem(pageRect, nullptr); m_upperSlider->setPen(QPen(Qt::black, 0.5)); m_upperSlider->setBrush(Qt::blue); m_upperSlider->setOpacity(0.2); m_scene->addItem(m_upperSlider); } void HistogramView::setScalePixmap(const QString& file) { // scene rect is 1000*100 where upper 1000*80 is for histogram graph // and lower 1000*20 is for histogram scale QGraphicsPixmapItem* pixmap = new QGraphicsPixmapItem(QPixmap(file).scaled( 1000, 20, Qt::IgnoreAspectRatio), nullptr); pixmap->setZValue(-1); pixmap->setPos(0, 90); m_scene->addItem(pixmap); } void HistogramView::setSpan(int l, int h) { l = l*1000/m_range; h = h*1000/m_range; m_lowerSlider->setPos(QPointF(l - 1000, 0)); m_upperSlider->setPos(QPointF(h, 0)); invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } void HistogramView::resizeEvent(QResizeEvent *event) { fitInView(m_scene->sceneRect(), Qt::IgnoreAspectRatio); QGraphicsView::resizeEvent(event); } void HistogramView::drawBackground(QPainter* painter, const QRectF& rect) { if (!bins) return; painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); int max = 1; for (int i = 0; i <= m_range; i++) if (bins [i] > max) max = bins [i]; // convert y-scale count to log scale so small counts are still visible // scene rect is 1000*100 where upper 1000*80 is for histogram graph // and lower 1000*20 is for histogram scale QPainterPath path(QPointF(0, (log(bins[0])*100/log(max)))); for (int i = 1; i <= m_range; i++) { int x = i*1000/m_range; int y = 80; if ( bins[i] > 1 ) y = 80 - (log(bins[i])*80/log(max)); path.lineTo(QPointF(x, y)); } painter->drawPath(path); invalidateScene(rect, QGraphicsScene::BackgroundLayer); painter->restore(); } DatapickerImageWidget::DatapickerImageWidget(QWidget* parent) : BaseDock(parent), m_image(nullptr) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; ui.leFileName->setClearButtonEnabled(true); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leFileName->setCompleter(new QCompleter(new QDirModel, this)); auto* editTabLayout = static_cast(ui.tEdit->layout()); editTabLayout->setContentsMargins(2,2,2,2); editTabLayout->setHorizontalSpacing(2); editTabLayout->setVerticalSpacing(4); ssHue = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssHue->setToolTip(i18n("Select the range for the hue.\nEverything outside of this range will be set to white.")); ssHue->setRange(0, 360); editTabLayout->addWidget(ssHue, 3, 2); ssSaturation = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssSaturation->setToolTip(i18n("Select the range for the saturation.\nEverything outside of this range will be set to white.")); ssSaturation->setRange(0,100); editTabLayout->addWidget(ssSaturation, 5, 2); ssValue = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssValue->setToolTip(i18n("Select the range for the value, the degree of lightness of the color.\nEverything outside of this range will be set to white.")); ssValue->setRange(0,100); editTabLayout->addWidget(ssValue, 7, 2); ssIntensity = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssIntensity->setToolTip(i18n("Select the range for the intensity.\nEverything outside of this range will be set to white.")); ssIntensity->setRange(0, 100); editTabLayout->addWidget(ssIntensity, 9, 2); ssForeground = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssForeground->setToolTip(i18n("Select the range for the colors that are not part of the background color.\nEverything outside of this range will be set to white.")); ssForeground->setRange(0, 100); editTabLayout->addWidget(ssForeground, 11, 2); ui.cbGraphType->addItem(i18n("Cartesian (x, y)")); ui.cbGraphType->addItem(i18n("Polar (x, y°)")); ui.cbGraphType->addItem(i18n("Polar (x, y(rad))")); ui.cbGraphType->addItem(i18n("Logarithmic (ln(x), y)")); ui.cbGraphType->addItem(i18n("Logarithmic (x, ln(y))")); ui.cbGraphType->addItem(i18n("Ternary (x, y, z)")); ui.lTernaryScale->setHidden(true); ui.sbTernaryScale->setHidden(true); ui.lPositionZ1->setHidden(true); ui.lPositionZ2->setHidden(true); ui.lPositionZ3->setHidden(true); ui.sbPositionZ1->setHidden(true); ui.sbPositionZ2->setHidden(true); ui.sbPositionZ3->setHidden(true); ui.cbPlotImageType->addItem(i18n("No Image")); ui.cbPlotImageType->addItem(i18n("Original Image")); ui.cbPlotImageType->addItem(i18n("Processed Image")); QString valueFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "labplot2/pics/colorchooser/colorchooser_value.xpm"); QString hueFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "labplot2/pics/colorchooser/colorchooser_hue.xpm"); QString saturationFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "labplot2/pics/colorchooser/colorchooser_saturation.xpm"); gvHue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Hue)); gvHue->setToolTip(i18n("Select the range for the hue.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvHue, 2, 2); gvHue->setScalePixmap(hueFile); gvSaturation = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Saturation)); gvSaturation->setToolTip(i18n("Select the range for the saturation.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvSaturation, 4, 2); gvSaturation->setScalePixmap(saturationFile); gvValue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Value)); gvValue->setToolTip(i18n("Select the range for the value, the degree of lightness of the color.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvValue, 6,2); gvValue->setScalePixmap(valueFile); gvIntensity = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Intensity)); gvIntensity->setToolTip(i18n("Select the range for the intensity.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvIntensity, 8, 2); gvIntensity->setScalePixmap(valueFile); gvForeground = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Foreground)); gvForeground->setToolTip(i18n("Select the range for the colors that are not part of the background color.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvForeground, 10, 2); gvForeground->setScalePixmap(valueFile); - connect( ssIntensity, SIGNAL(spanChanged(int,int)), gvIntensity, SLOT(setSpan(int,int)) ); - connect( ssForeground, SIGNAL(spanChanged(int,int)), gvForeground, SLOT(setSpan(int,int)) ); - connect( ssHue, SIGNAL(spanChanged(int,int)), gvHue, SLOT(setSpan(int,int)) ); - connect( ssSaturation, SIGNAL(spanChanged(int,int)), gvSaturation, SLOT(setSpan(int,int)) ); - connect( ssValue, SIGNAL(spanChanged(int,int)), gvValue, SLOT(setSpan(int,int)) ); - //SLOTS //general connect(ui.leName, &QLineEdit::textChanged, this, &DatapickerImageWidget::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &DatapickerImageWidget::commentChanged); - connect( ui.bOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile())); - connect( ui.leFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); - connect( ui.leFileName, SIGNAL(textChanged(QString)), this, SLOT(fileNameChanged()) ); + connect(ui.bOpen, &QPushButton::clicked, this, &DatapickerImageWidget::selectFile); + connect(ui.leFileName, &QLineEdit::returnPressed, this, &DatapickerImageWidget::fileNameChanged); + connect(ui.leFileName, &QLineEdit::textChanged, this, &DatapickerImageWidget::fileNameChanged); // edit image - connect( ui.cbPlotImageType, SIGNAL(currentIndexChanged(int)), this, SLOT(plotImageTypeChanged(int)) ); - connect( ui.sbRotation, SIGNAL(valueChanged(double)), this, SLOT(rotationChanged(double)) ); - connect( ssIntensity, SIGNAL(spanChanged(int,int)), this, SLOT(intensitySpanChanged(int,int)) ); - connect( ssForeground, SIGNAL(spanChanged(int,int)), this, SLOT(foregroundSpanChanged(int,int)) ); - connect( ssHue, SIGNAL(spanChanged(int,int)), this, SLOT(hueSpanChanged(int,int)) ); - connect( ssSaturation, SIGNAL(spanChanged(int,int)), this, SLOT(saturationSpanChanged(int,int)) ); - connect( ssValue, SIGNAL(spanChanged(int,int)), this, SLOT(valueSpanChanged(int,int)) ); - connect( ui.sbMinSegmentLength, SIGNAL(valueChanged(int)), this, SLOT(minSegmentLengthChanged(int)) ); - connect( ui.sbPointSeparation, SIGNAL(valueChanged(int)), this, SLOT(pointSeparationChanged(int)) ); + connect(ui.cbPlotImageType, static_cast(&QComboBox::currentIndexChanged), + this, &DatapickerImageWidget::plotImageTypeChanged); + connect(ui.sbRotation, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::rotationChanged); + connect(ssIntensity, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::intensitySpanChanged); + connect(ssIntensity, &QxtSpanSlider::spanChanged, gvIntensity, &HistogramView::setSpan); + connect(ssForeground, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::foregroundSpanChanged); + connect(ssForeground, &QxtSpanSlider::spanChanged, gvForeground, &HistogramView::setSpan ); + connect(ssHue, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::hueSpanChanged); + connect(ssHue, &QxtSpanSlider::spanChanged, gvHue, &HistogramView::setSpan ); + connect(ssSaturation, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::saturationSpanChanged); + connect(ssSaturation, &QxtSpanSlider::spanChanged, gvSaturation, &HistogramView::setSpan ); + connect(ssValue, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::valueSpanChanged); + connect(ssValue, &QxtSpanSlider::spanChanged, gvValue, &HistogramView::setSpan ); + connect(ui.sbMinSegmentLength, static_cast(&QSpinBox::valueChanged), + this, &DatapickerImageWidget::minSegmentLengthChanged); + connect(ui.sbPointSeparation, static_cast(&QSpinBox::valueChanged), + this, &DatapickerImageWidget::pointSeparationChanged); //axis point - connect( ui.cbGraphType, SIGNAL(currentIndexChanged(int)), this, SLOT(graphTypeChanged()) ); - connect( ui.sbTernaryScale, SIGNAL(valueChanged(double)), this, SLOT(ternaryScaleChanged(double)) ); - connect( ui.sbPositionX1, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionY1, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionX2, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionY2, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionX3, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionY3, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionZ1, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionZ2, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); - connect( ui.sbPositionZ3, SIGNAL(valueChanged(double)), this, SLOT(logicalPositionChanged()) ); + connect(ui.cbGraphType, static_cast(&QComboBox::currentIndexChanged), + this, &DatapickerImageWidget::graphTypeChanged); + connect(ui.sbTernaryScale, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::ternaryScaleChanged); + connect(ui.sbPositionX1, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionY1, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionX2, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionY2, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionX3, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionY3, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionZ1, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionZ2, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); + connect(ui.sbPositionZ3, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::logicalPositionChanged); //SYMBOL - connect( ui.cbSymbolStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(pointsStyleChanged(int)) ); - connect( ui.sbSymbolSize, SIGNAL(valueChanged(double)), this, SLOT(pointsSizeChanged(double)) ); - connect( ui.sbSymbolRotation, SIGNAL(valueChanged(int)), this, SLOT(pointsRotationChanged(int)) ); - connect( ui.sbSymbolOpacity, SIGNAL(valueChanged(int)), this, SLOT(pointsOpacityChanged(int)) ); + connect(ui.cbSymbolStyle, static_cast(&QComboBox::currentIndexChanged), + this, &DatapickerImageWidget::pointsStyleChanged); + connect(ui.sbSymbolSize, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::pointsSizeChanged); + connect(ui.sbSymbolRotation,static_cast(&QSpinBox::valueChanged), + this, &DatapickerImageWidget::pointsRotationChanged); + connect(ui.sbSymbolOpacity, static_cast(&QSpinBox::valueChanged), + this, &DatapickerImageWidget::pointsOpacityChanged); //Filling - connect( ui.cbSymbolFillingStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(pointsFillingStyleChanged(int)) ); - connect( ui.kcbSymbolFillingColor, SIGNAL(changed(QColor)), this, SLOT(pointsFillingColorChanged(QColor)) ); + connect(ui.cbSymbolFillingStyle, static_cast(&QComboBox::currentIndexChanged), + this, &DatapickerImageWidget::pointsFillingStyleChanged); + connect(ui.kcbSymbolFillingColor, &KColorButton::changed, this, &DatapickerImageWidget::pointsFillingColorChanged); //border - connect( ui.cbSymbolBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(pointsBorderStyleChanged(int)) ); - connect( ui.kcbSymbolBorderColor, SIGNAL(changed(QColor)), this, SLOT(pointsBorderColorChanged(QColor)) ); - connect( ui.sbSymbolBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(pointsBorderWidthChanged(double)) ); + connect(ui.cbSymbolBorderStyle, static_cast(&QComboBox::currentIndexChanged), + this, &DatapickerImageWidget::pointsBorderStyleChanged); + connect(ui.kcbSymbolBorderColor, &KColorButton::changed, this, &DatapickerImageWidget::pointsBorderColorChanged); + connect(ui.sbSymbolBorderWidth, static_cast(&QDoubleSpinBox::valueChanged), + this, &DatapickerImageWidget::pointsBorderWidthChanged); - connect( ui.chbSymbolVisible, SIGNAL(clicked(bool)), this, SLOT(pointsVisibilityChanged(bool)) ); + connect(ui.chbSymbolVisible, &QCheckBox::clicked, this, &DatapickerImageWidget::pointsVisibilityChanged); init(); } void DatapickerImageWidget::init() { m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); QPainter pa; int iconSize = 20; QPixmap pm(iconSize, iconSize); QPen pen(Qt::SolidPattern, 0); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); //TODO: constant? for (int i = 1; i < 19; ++i) { auto style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen( pen ); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; } void DatapickerImageWidget::setImages(QList list) { + m_initializing = true; m_imagesList = list; m_image = list.first(); m_aspect = list.first(); if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_image->parentAspect()->name()); ui.leComment->setText(m_image->parentAspect()->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } this->load(); initConnections(); handleWidgetActions(); updateSymbolWidgets(); + m_initializing = false; } void DatapickerImageWidget::initConnections() { - connect( m_image->parentAspect(), SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(imageDescriptionChanged(const AbstractAspect*))); - connect( m_image, SIGNAL(fileNameChanged(QString)), this, SLOT(imageFileNameChanged(QString)) ); - connect( m_image, SIGNAL(rotationAngleChanged(float)), this, SLOT(imageRotationAngleChanged(float)) ); - connect( m_image, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), - this,SLOT(updateSymbolWidgets()) ); - connect( m_image, SIGNAL(aspectAdded(const AbstractAspect*)), this,SLOT(updateSymbolWidgets()) ); - connect( m_image, SIGNAL(axisPointsChanged(DatapickerImage::ReferencePoints)), this, SLOT(imageAxisPointsChanged(DatapickerImage::ReferencePoints)) ); - connect( m_image, SIGNAL(settingsChanged(DatapickerImage::EditorSettings)), this, SLOT(imageEditorSettingsChanged(DatapickerImage::EditorSettings)) ); - connect( m_image, SIGNAL(minSegmentLengthChanged(int)), this, SLOT(imageMinSegmentLengthChanged(int)) ); - connect( m_image, SIGNAL(pointStyleChanged(Symbol::Style)), this, SLOT(symbolStyleChanged(Symbol::Style))); - connect( m_image, SIGNAL(pointSizeChanged(qreal)), this, SLOT(symbolSizeChanged(qreal))); - connect( m_image, SIGNAL(pointRotationAngleChanged(qreal)), this, SLOT(symbolRotationAngleChanged(qreal))); - connect( m_image, SIGNAL(pointOpacityChanged(qreal)), this, SLOT(symbolOpacityChanged(qreal))); - connect( m_image, SIGNAL(pointBrushChanged(QBrush)), this, SLOT(symbolBrushChanged(QBrush)) ); - connect( m_image, SIGNAL(pointPenChanged(QPen)), this, SLOT(symbolPenChanged(QPen)) ); - connect( m_image, SIGNAL(pointVisibilityChanged(bool)), this, SLOT(symbolVisibleChanged(bool)) ); + connect(m_image->parentAspect(), &AbstractAspect::aspectDescriptionChanged, this, &DatapickerImageWidget::imageDescriptionChanged); + connect(m_image, &DatapickerImage::fileNameChanged, this, &DatapickerImageWidget::imageFileNameChanged); + connect(m_image, &DatapickerImage::rotationAngleChanged, this, &DatapickerImageWidget::imageRotationAngleChanged); + connect(m_image, &AbstractAspect::aspectRemoved, this, &DatapickerImageWidget::updateSymbolWidgets); + connect(m_image, &AbstractAspect::aspectAdded, this, &DatapickerImageWidget::updateSymbolWidgets); + connect(m_image, &DatapickerImage::axisPointsChanged, this, &DatapickerImageWidget::imageAxisPointsChanged); + connect(m_image, &DatapickerImage::settingsChanged, this, &DatapickerImageWidget::imageEditorSettingsChanged); + connect(m_image, &DatapickerImage::minSegmentLengthChanged, this, &DatapickerImageWidget::imageMinSegmentLengthChanged); + connect(m_image, &DatapickerImage::pointStyleChanged, this, &DatapickerImageWidget::symbolStyleChanged); + connect(m_image, &DatapickerImage::pointSizeChanged, this, &DatapickerImageWidget::symbolSizeChanged); + connect(m_image, &DatapickerImage::pointRotationAngleChanged, this, &DatapickerImageWidget::symbolRotationAngleChanged); + connect(m_image, &DatapickerImage::pointOpacityChanged, this, &DatapickerImageWidget::symbolOpacityChanged); + connect(m_image, &DatapickerImage::pointBrushChanged, this, &DatapickerImageWidget::symbolBrushChanged); + connect(m_image, &DatapickerImage::pointPenChanged, this, &DatapickerImageWidget::symbolPenChanged); + connect(m_image, &DatapickerImage::pointVisibilityChanged, this, &DatapickerImageWidget::symbolVisibleChanged); } void DatapickerImageWidget::handleWidgetActions() { QString fileName = ui.leFileName->text().trimmed(); bool b = !fileName.isEmpty(); ui.tEdit->setEnabled(b); ui.cbGraphType->setEnabled(b); ui.sbRotation->setEnabled(b); ui.sbPositionX1->setEnabled(b); ui.sbPositionX2->setEnabled(b); ui.sbPositionX3->setEnabled(b); ui.sbPositionY1->setEnabled(b); ui.sbPositionY2->setEnabled(b); ui.sbPositionY3->setEnabled(b); ui.sbMinSegmentLength->setEnabled(b); ui.sbPointSeparation->setEnabled(b); if (b) { //upload histogram to view gvIntensity->bins = m_image->intensityBins; gvForeground->bins = m_image->foregroundBins; gvHue->bins = m_image->hueBins; gvSaturation->bins = m_image->saturationBins; gvValue->bins = m_image->valueBins; } } //********************************************************** //****** SLOTs for changes triggered in DatapickerImageWidget ******** //********************************************************** //"General"-tab void DatapickerImageWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "DatapickerImageWidget"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); formats.isEmpty() ? formats += f : formats += " " + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leFileName->setText( path ); handleWidgetActions(); for (auto* image : m_imagesList) image->setFileName(path); } void DatapickerImageWidget::fileNameChanged() { if (m_initializing) return; handleWidgetActions(); QString fileName = ui.leFileName->text(); + if (!fileName.isEmpty() && !QFile::exists(fileName)) + ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); + else + ui.leFileName->setStyleSheet(QString()); + for (auto* image : m_imagesList) image->setFileName(fileName); } void DatapickerImageWidget::graphTypeChanged() { if (m_initializing) return; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.type = DatapickerImage::GraphType(ui.cbGraphType->currentIndex()); if (points.type != DatapickerImage::Ternary) { ui.lTernaryScale->setHidden(true); ui.sbTernaryScale->setHidden(true); ui.lPositionZ1->setHidden(true); ui.lPositionZ2->setHidden(true); ui.lPositionZ3->setHidden(true); ui.sbPositionZ1->setHidden(true); ui.sbPositionZ2->setHidden(true); ui.sbPositionZ3->setHidden(true); } else { ui.lTernaryScale->setHidden(false); ui.sbTernaryScale->setHidden(false); ui.lPositionZ1->setHidden(false); ui.lPositionZ2->setHidden(false); ui.lPositionZ3->setHidden(false); ui.sbPositionZ1->setHidden(false); ui.sbPositionZ2->setHidden(false); ui.sbPositionZ3->setHidden(false); } for (auto* image : m_imagesList) image->setAxisPoints(points); } void DatapickerImageWidget::ternaryScaleChanged(double value) { if (m_initializing) return; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.ternaryScale = value; for (auto* image : m_imagesList) image->setAxisPoints(points); } void DatapickerImageWidget::logicalPositionChanged() { if (m_initializing) return; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.logicalPos[0].setX(ui.sbPositionX1->value()); points.logicalPos[0].setY(ui.sbPositionY1->value()); points.logicalPos[1].setX(ui.sbPositionX2->value()); points.logicalPos[1].setY(ui.sbPositionY2->value()); points.logicalPos[2].setX(ui.sbPositionX3->value()); points.logicalPos[2].setY(ui.sbPositionY3->value()); points.logicalPos[0].setZ(ui.sbPositionZ1->value()); points.logicalPos[1].setZ(ui.sbPositionZ2->value()); points.logicalPos[2].setZ(ui.sbPositionZ3->value()); for (auto* image : m_imagesList) image->setAxisPoints(points); } void DatapickerImageWidget::pointsStyleChanged(int index) { auto style = Symbol::Style(index + 1); //enable/disable the filling options in the GUI depending on the currently selected points. if (style != Symbol::Line && style != Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); if (m_initializing) return; for (auto* image : m_imagesList) image->setPointStyle(style); } void DatapickerImageWidget::pointsSizeChanged(double value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPointSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void DatapickerImageWidget::pointsRotationChanged(int value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPointRotationAngle(value); } void DatapickerImageWidget::pointsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* image : m_imagesList) image->setPointOpacity(opacity); } void DatapickerImageWidget::pointsFillingStyleChanged(int index) { auto brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; for (auto* image : m_imagesList) { brush = image->pointBrush(); brush.setStyle(brushStyle); image->setPointBrush(brush); } } void DatapickerImageWidget::pointsFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; for (auto* image : m_imagesList) { brush = image->pointBrush(); brush.setColor(color); image->setPointBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void DatapickerImageWidget::pointsBorderStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; for (auto* image : m_imagesList) { pen = image->pointPen(); pen.setStyle(penStyle); image->setPointPen(pen); } } void DatapickerImageWidget::pointsBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* image : m_imagesList) { pen = image->pointPen(); pen.setColor(color); image->setPointPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void DatapickerImageWidget::pointsBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* image : m_imagesList) { pen = image->pointPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); image->setPointPen(pen); } } void DatapickerImageWidget::pointsVisibilityChanged(bool state) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPointVisibility(state); } void DatapickerImageWidget::intensitySpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.intensityThresholdHigh = upperLimit; settings.intensityThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::foregroundSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.foregroundThresholdHigh = upperLimit; settings.foregroundThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::hueSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.hueThresholdHigh = upperLimit; settings.hueThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::saturationSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.saturationThresholdHigh = upperLimit; settings.saturationThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::valueSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.valueThresholdHigh = upperLimit; settings.valueThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::plotImageTypeChanged(int index) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPlotImageType(DatapickerImage::PlotImageType(index)); } void DatapickerImageWidget::rotationChanged(double value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setRotationAngle(value); } void DatapickerImageWidget::minSegmentLengthChanged(int value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setminSegmentLength(value); } void DatapickerImageWidget::pointSeparationChanged(int value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPointSeparation(value); } //******************************************************************* //******** SLOTs for changes triggered in DatapickerImage *********** //******************************************************************* /*! * called when the name or comment of image's parent (datapicker) was changed. */ void DatapickerImageWidget::imageDescriptionChanged(const AbstractAspect* aspect) { if (m_image->parentAspect() != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void DatapickerImageWidget::imageFileNameChanged(const QString& name) { m_initializing = true; ui.leFileName->setText(name); m_initializing = false; } void DatapickerImageWidget::imageRotationAngleChanged(float angle) { m_initializing = true; ui.sbRotation->setValue(angle); m_initializing = false; } void DatapickerImageWidget::imageAxisPointsChanged(const DatapickerImage::ReferencePoints& axisPoints) { m_initializing = true; ui.cbGraphType->setCurrentIndex((int) axisPoints.type); ui.sbTernaryScale->setValue(axisPoints.ternaryScale); ui.sbPositionX1->setValue(axisPoints.logicalPos[0].x()); ui.sbPositionY1->setValue(axisPoints.logicalPos[0].y()); ui.sbPositionX2->setValue(axisPoints.logicalPos[1].x()); ui.sbPositionY2->setValue(axisPoints.logicalPos[1].y()); ui.sbPositionX3->setValue(axisPoints.logicalPos[2].x()); ui.sbPositionY3->setValue(axisPoints.logicalPos[2].y()); ui.sbPositionZ1->setValue(axisPoints.logicalPos[0].z()); ui.sbPositionZ2->setValue(axisPoints.logicalPos[1].z()); ui.sbPositionZ3->setValue(axisPoints.logicalPos[2].z()); m_initializing = false; } void DatapickerImageWidget::imageEditorSettingsChanged(const DatapickerImage::EditorSettings& settings) { m_initializing = true; ssIntensity->setSpan(settings.intensityThresholdLow, settings.intensityThresholdHigh); ssForeground->setSpan(settings.foregroundThresholdLow, settings.foregroundThresholdHigh); ssHue->setSpan(settings.hueThresholdLow, settings.hueThresholdHigh); ssSaturation->setSpan(settings.saturationThresholdLow, settings.saturationThresholdHigh); ssValue->setSpan(settings.valueThresholdLow, settings.valueThresholdHigh); gvIntensity->setSpan(settings.intensityThresholdLow, settings.intensityThresholdHigh); gvForeground->setSpan(settings.foregroundThresholdLow, settings.foregroundThresholdHigh); gvHue->setSpan(settings.hueThresholdLow, settings.hueThresholdHigh); gvSaturation->setSpan(settings.saturationThresholdLow, settings.saturationThresholdHigh); gvValue->setSpan(settings.valueThresholdLow, settings.valueThresholdHigh); m_initializing = false; } void DatapickerImageWidget::imageMinSegmentLengthChanged(const int value) { m_initializing = true; ui.sbMinSegmentLength->setValue(value); m_initializing = false; } void DatapickerImageWidget::updateSymbolWidgets() { int pointCount = m_image->childCount(AbstractAspect::IncludeHidden); if (pointCount) ui.tSymbol->setEnabled(true); else ui.tSymbol->setEnabled(false); } void DatapickerImageWidget::symbolStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style - 1); m_initializing = false; } void DatapickerImageWidget::symbolSizeChanged(qreal size) { m_initializing = true; ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } void DatapickerImageWidget::symbolRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbSymbolRotation->setValue(round(angle)); m_initializing = false; } void DatapickerImageWidget::symbolOpacityChanged(qreal opacity) { m_initializing = true; ui.sbSymbolOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void DatapickerImageWidget::symbolBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void DatapickerImageWidget::symbolPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); m_initializing = false; } void DatapickerImageWidget::symbolVisibleChanged(bool on) { m_initializing = true; ui.chbSymbolVisible->setChecked(on); m_initializing = false; } //********************************************************** //******************** SETTINGS **************************** //********************************************************** void DatapickerImageWidget::load() { if (!m_image) return; m_initializing = true; ui.leFileName->setText( m_image->fileName() ); + + //highlight the text field for the background image red if an image is used and cannot be found + if (!m_image->fileName().isEmpty() && !QFile::exists(m_image->fileName())) + ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); + else + ui.leFileName->setStyleSheet(QString()); + ui.cbGraphType->setCurrentIndex((int) m_image->axisPoints().type); ui.sbTernaryScale->setValue(m_image->axisPoints().ternaryScale); ui.sbPositionX1->setValue(m_image->axisPoints().logicalPos[0].x()); ui.sbPositionY1->setValue(m_image->axisPoints().logicalPos[0].y()); ui.sbPositionX2->setValue(m_image->axisPoints().logicalPos[1].x()); ui.sbPositionY2->setValue(m_image->axisPoints().logicalPos[1].y()); ui.sbPositionX3->setValue(m_image->axisPoints().logicalPos[2].x()); ui.sbPositionY3->setValue(m_image->axisPoints().logicalPos[2].y()); ui.sbPositionZ1->setValue(m_image->axisPoints().logicalPos[0].z()); ui.sbPositionZ2->setValue(m_image->axisPoints().logicalPos[1].z()); ui.sbPositionZ3->setValue(m_image->axisPoints().logicalPos[2].z()); ui.cbPlotImageType->setCurrentIndex((int) m_image->plotImageType()); ssIntensity->setSpan(m_image->settings().intensityThresholdLow, m_image->settings().intensityThresholdHigh); ssForeground->setSpan(m_image->settings().foregroundThresholdLow, m_image->settings().foregroundThresholdHigh); ssHue->setSpan(m_image->settings().hueThresholdLow, m_image->settings().hueThresholdHigh); ssSaturation->setSpan(m_image->settings().saturationThresholdLow, m_image->settings().saturationThresholdHigh); ssValue->setSpan(m_image->settings().valueThresholdLow, m_image->settings().valueThresholdHigh); gvIntensity->setSpan(m_image->settings().intensityThresholdLow, m_image->settings().intensityThresholdHigh); gvForeground->setSpan(m_image->settings().foregroundThresholdLow, m_image->settings().foregroundThresholdHigh); gvHue->setSpan(m_image->settings().hueThresholdLow, m_image->settings().hueThresholdHigh); gvSaturation->setSpan(m_image->settings().saturationThresholdLow, m_image->settings().saturationThresholdHigh); gvValue->setSpan(m_image->settings().valueThresholdLow, m_image->settings().valueThresholdHigh); ui.sbPointSeparation->setValue(m_image->pointSeparation()); ui.sbMinSegmentLength->setValue(m_image->minSegmentLength()); ui.cbSymbolStyle->setCurrentIndex( (int)m_image->pointStyle() - 1 ); ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_image->pointSize(), Worksheet::Point) ); ui.sbSymbolRotation->setValue( m_image->pointRotationAngle() ); ui.sbSymbolOpacity->setValue( round(m_image->pointOpacity()*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( (int) m_image->pointBrush().style() ); ui.kcbSymbolFillingColor->setColor( m_image->pointBrush().color() ); ui.cbSymbolBorderStyle->setCurrentIndex( (int) m_image->pointPen().style() ); ui.kcbSymbolBorderColor->setColor( m_image->pointPen().color() ); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_image->pointPen().widthF(), Worksheet::Point) ); ui.chbSymbolVisible->setChecked( m_image->pointVisibility() ); m_initializing = false; } diff --git a/src/kdefrontend/widgets/DatapickerImageWidget.h b/src/kdefrontend/widgets/DatapickerImageWidget.h index 08fc22eb6..b2335b7e5 100644 --- a/src/kdefrontend/widgets/DatapickerImageWidget.h +++ b/src/kdefrontend/widgets/DatapickerImageWidget.h @@ -1,143 +1,142 @@ /*************************************************************************** File : DatapickerImageWidget.h Project : LabPlot Description : widget for datapicker properties -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef DATAPICKERIMAGEWIDGET_H #define DATAPICKERIMAGEWIDGET_H #include #include "ui_datapickerimagewidget.h" #include "backend/datapicker/DatapickerImage.h" #include "kdefrontend/dockwidgets/BaseDock.h" class QxtSpanSlider; class HistogramView : public QGraphicsView { Q_OBJECT public: explicit HistogramView(QWidget*, int); void setScalePixmap(const QString&); int *bins{nullptr}; public slots: void setSpan(int, int); private: void resizeEvent(QResizeEvent *event) override; void drawBackground(QPainter*, const QRectF&) override; QGraphicsRectItem* m_lowerSlider; QGraphicsRectItem* m_upperSlider; QGraphicsScene* m_scene; int m_range; }; class DatapickerImageWidget : public BaseDock { Q_OBJECT public: explicit DatapickerImageWidget(QWidget*); void setImages(QList); void load(); private: Ui::DatapickerImageWidget ui; void init(); void initConnections(); DatapickerImage* m_image; QList m_imagesList; - bool m_initializing; QxtSpanSlider* ssIntensity; QxtSpanSlider* ssForeground; QxtSpanSlider* ssHue; QxtSpanSlider* ssSaturation; QxtSpanSlider* ssValue; HistogramView* gvIntensity; HistogramView* gvForeground; HistogramView* gvHue; HistogramView* gvSaturation; HistogramView* gvValue; private slots: //SLOTs for changes triggered in DatapickerImageWidget //"General"-tab void fileNameChanged(); void selectFile(); void plotImageTypeChanged(int); //"Edit image"-tab void rotationChanged(double); void intensitySpanChanged(int, int); void foregroundSpanChanged(int, int); void hueSpanChanged(int, int); void saturationSpanChanged(int, int); void valueSpanChanged(int, int); void minSegmentLengthChanged(int); void pointSeparationChanged(int); void graphTypeChanged(); void ternaryScaleChanged(double); void logicalPositionChanged(); //symbol propeties void pointsStyleChanged(int); void pointsSizeChanged(double); void pointsRotationChanged(int); void pointsOpacityChanged(int); void pointsFillingStyleChanged(int); void pointsFillingColorChanged(const QColor&); void pointsBorderStyleChanged(int); void pointsBorderColorChanged(const QColor&); void pointsBorderWidthChanged(double); void pointsVisibilityChanged(bool); //SLOTs for changes triggered in DatapickerImageWidget void imageDescriptionChanged(const AbstractAspect*); void imageFileNameChanged(const QString&); void imageRotationAngleChanged(float); void imageAxisPointsChanged(const DatapickerImage::ReferencePoints&); void imageEditorSettingsChanged(const DatapickerImage::EditorSettings&); void imageMinSegmentLengthChanged(const int); void updateSymbolWidgets(); void handleWidgetActions(); //symbol void symbolStyleChanged(Symbol::Style); void symbolSizeChanged(qreal); void symbolRotationAngleChanged(qreal); void symbolOpacityChanged(qreal); void symbolBrushChanged(const QBrush&); void symbolPenChanged(const QPen&); void symbolVisibleChanged(bool); }; #endif //DATAPICKERIMAGEWIDGET_H diff --git a/src/kdefrontend/worksheet/DynamicPresenterWidget.cpp b/src/kdefrontend/worksheet/DynamicPresenterWidget.cpp index a36af424b..7db817bf4 100644 --- a/src/kdefrontend/worksheet/DynamicPresenterWidget.cpp +++ b/src/kdefrontend/worksheet/DynamicPresenterWidget.cpp @@ -1,110 +1,106 @@ /*************************************************************************** File : DynamicPresenterWidget.cpp Project : LabPlot Description : Widget for dynamic presenting of worksheets -------------------------------------------------------------------- Copyright : (C) 2016 by Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DynamicPresenterWidget.h" #include "commonfrontend/worksheet/WorksheetView.h" #include "SlidingPanel.h" #include #include #include #include -#include - DynamicPresenterWidget::DynamicPresenterWidget(Worksheet* worksheet, QWidget* parent) : QWidget(parent), m_view(new WorksheetView(worksheet)), m_timeLine(new QTimeLine(600)) { setAttribute(Qt::WA_DeleteOnClose); setFocus(); m_view->setParent(this); m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_view->fitInView(m_view->sceneRect(), Qt::KeepAspectRatio); m_view->adjustSize(); const QRect& screenSize = QGuiApplication::primaryScreen()->availableGeometry(); const int moveRight = (screenSize.width() - m_view->width()) / 2.0; const int moveDown = (screenSize.height() - m_view->height()) / 2.0; m_view->move(moveRight, moveDown); m_view->show(); m_panel = new SlidingPanel(this, worksheet->name()); qApp->installEventFilter(this); connect(m_timeLine, &QTimeLine::valueChanged, m_panel, &SlidingPanel::movePanel); connect(m_panel->quitButton(), &QPushButton::clicked, this, &DynamicPresenterWidget::close); grabMouse(); - - slideUp(); } DynamicPresenterWidget::~DynamicPresenterWidget() { delete m_timeLine; delete m_view; } bool DynamicPresenterWidget::eventFilter(QObject* watched, QEvent* event) { Q_UNUSED(watched); if (event->type() == QEvent::MouseMove) { if (m_panel->y() != 0 && m_panel->rect().contains(QCursor::pos())) slideDown(); else if (m_panel->y() == 0 && !m_panel->rect().contains(QCursor::pos())) slideUp(); } return false; } void DynamicPresenterWidget::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) close(); } void DynamicPresenterWidget::slideDown() { m_timeLine->setDirection(QTimeLine::Forward); startTimeline(); } void DynamicPresenterWidget::slideUp() { m_timeLine->setDirection(QTimeLine::Backward); startTimeline(); } void DynamicPresenterWidget::startTimeline() { if (m_timeLine->state() != QTimeLine::Running) m_timeLine->start(); } void DynamicPresenterWidget::focusOutEvent(QFocusEvent *e) { if (m_view->hasFocus()) setFocus(); if (e->reason() & Qt::BacktabFocusReason) close(); } diff --git a/src/kdefrontend/worksheet/PresenterWidget.cpp b/src/kdefrontend/worksheet/PresenterWidget.cpp index aed9c3fd9..f797b2d9f 100644 --- a/src/kdefrontend/worksheet/PresenterWidget.cpp +++ b/src/kdefrontend/worksheet/PresenterWidget.cpp @@ -1,100 +1,95 @@ /*************************************************************************** File : PresenterWidget.cpp Project : LabPlot Description : Widget for static presenting of worksheets -------------------------------------------------------------------- Copyright : (C) 2016 by Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "PresenterWidget.h" #include "SlidingPanel.h" #include #include #include #include #include #include -#include - PresenterWidget::PresenterWidget(const QPixmap &pixmap, const QString& worksheetName, QWidget *parent) : QWidget(parent), m_imageLabel(new QLabel(this)), m_timeLine(new QTimeLine(600)) { setAttribute(Qt::WA_DeleteOnClose); m_imageLabel->setPixmap(pixmap); m_imageLabel->adjustSize(); const QRect& screenSize = QGuiApplication::primaryScreen()->availableGeometry(); const int moveRight = (screenSize.width() - m_imageLabel->width()) / 2.0; const int moveDown = (screenSize.height() - m_imageLabel->height()) / 2.0; m_imageLabel->move(moveRight, moveDown); m_panel = new SlidingPanel(this, worksheetName); qApp->installEventFilter(this); connect(m_timeLine, &QTimeLine::valueChanged, m_panel, &SlidingPanel::movePanel); connect(m_panel->quitButton(), &QPushButton::clicked, this, &PresenterWidget::close); - - slideUp(); - setFocus(); } PresenterWidget::~PresenterWidget() { delete m_imageLabel; delete m_timeLine; } bool PresenterWidget::eventFilter(QObject* watched, QEvent* event) { Q_UNUSED(watched); if (event->type() == QEvent::MouseMove) { if (m_panel->y() != 0 && m_panel->rect().contains(QCursor::pos())) slideDown(); else if (m_panel->y() == 0 && !m_panel->rect().contains(QCursor::pos())) slideUp(); } return false; } void PresenterWidget::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) close(); } void PresenterWidget::focusOutEvent(QFocusEvent*) { close(); } void PresenterWidget::slideDown() { m_timeLine->setDirection(QTimeLine::Forward); startTimeline(); } void PresenterWidget::slideUp() { m_timeLine->setDirection(QTimeLine::Backward); startTimeline(); } void PresenterWidget::startTimeline() { if (m_timeLine->state() != QTimeLine::Running) m_timeLine->start(); }