diff --git a/.arcconfig b/.arcconfig deleted file mode 100644 index 377c7ecb1..000000000 --- a/.arcconfig +++ /dev/null @@ -1,3 +0,0 @@ -{ - "phabricator.uri" : "https://phabricator.kde.org/" -} diff --git a/data/pics/CMakeLists.txt b/data/pics/CMakeLists.txt index 3090d3937..c81e74567 100644 --- a/data/pics/CMakeLists.txt +++ b/data/pics/CMakeLists.txt @@ -1,52 +1,54 @@ ecm_install_icons( ICONS sc-apps-labplot-1x-zoom.svg sc-apps-labplot-2x-zoom.svg sc-apps-labplot-3x-zoom.svg sc-apps-labplot-4x-zoom.svg sc-apps-labplot-5x-zoom.svg sc-apps-labplot-auto-scale-all.svg sc-apps-labplot-auto-scale-x.svg sc-apps-labplot-auto-scale-y.svg sc-apps-labplot-axis-horizontal.svg sc-apps-labplot-axis-vertical.svg sc-apps-labplot-cursor-arrow.svg sc-apps-labplot-editbreaklayout.svg sc-apps-labplot-editgrid.svg sc-apps-labplot-edithlayout.svg sc-apps-labplot-editvlayout.svg sc-apps-labplot-format-text-symbol.svg +sc-apps-labplot-json-array.svg +sc-apps-labplot-json-object.svg sc-apps-labplot-matrix-new.svg sc-apps-labplot-matrix.svg sc-apps-labplot-plot-axis-points.svg sc-apps-labplot-shift-down-y.svg sc-apps-labplot-shift-left-x.svg sc-apps-labplot-shift-right-x.svg sc-apps-labplot-shift-up-y.svg sc-apps-labplot-spreadsheet-new.svg sc-apps-labplot-spreadsheet.svg sc-apps-labplot-TeX-logo.svg sc-apps-labplot-transform-move.svg sc-apps-labplot-workbook-new.svg sc-apps-labplot-workbook.svg sc-apps-labplot-worksheet-new.svg sc-apps-labplot-worksheet.svg sc-apps-labplot-xy-curve-points.svg sc-apps-labplot-xy-curve-segments.svg sc-apps-labplot-xy-curve.svg sc-apps-labplot-xy-equation-curve.svg sc-apps-labplot-xy-fit-curve.svg sc-apps-labplot-xy-plot-four-axes.svg sc-apps-labplot-xy-plot-two-axes-centered-origin.svg sc-apps-labplot-xy-plot-two-axes-centered.svg sc-apps-labplot-xy-plot-two-axes.svg sc-apps-labplot-zoom-in-x.svg sc-apps-labplot-zoom-in-y.svg sc-apps-labplot-zoom-out-x.svg sc-apps-labplot-zoom-out-y.svg sc-apps-labplot-zoom-select.svg sc-apps-labplot-zoom-select-x.svg sc-apps-labplot-zoom-select-y.svg sc-apps-labplot-zoom.svg DESTINATION ${KDE_INSTALL_ICONDIR}/ THEME hicolor) diff --git a/data/pics/sc-apps-labplot-json-array.svg b/data/pics/sc-apps-labplot-json-array.svg new file mode 100644 index 000000000..1cd22fc56 --- /dev/null +++ b/data/pics/sc-apps-labplot-json-array.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/data/pics/sc-apps-labplot-json-object.svg b/data/pics/sc-apps-labplot-json-object.svg new file mode 100644 index 000000000..8d382a9c8 --- /dev/null +++ b/data/pics/sc-apps-labplot-json-object.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31e6a03ca..445e6be9c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,399 +1,403 @@ find_package(SharedMimeInfo REQUIRED) set(KDE_FRONTEND true) set(KDEFRONTEND_DIR kdefrontend) set(BACKEND_DIR backend) set(COMMONFRONTEND_DIR commonfrontend) set(CANTOR_DIR cantor) set(TOOLS_DIR tools) set(CMAKE_AUTOMOC ON) set(SRC_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE) set(GUI_SOURCES ${KDEFRONTEND_DIR}/GuiObserver.cpp ${KDEFRONTEND_DIR}/GuiTools.cpp ${KDEFRONTEND_DIR}/HistoryDialog.cpp ${KDEFRONTEND_DIR}/MainWin.cpp ${KDEFRONTEND_DIR}/SettingsDialog.cpp ${KDEFRONTEND_DIR}/SettingsGeneralPage.cpp ${KDEFRONTEND_DIR}/SettingsWorksheetPage.cpp ${KDEFRONTEND_DIR}/SettingsPage.h ${KDEFRONTEND_DIR}/TemplateHandler.cpp ${KDEFRONTEND_DIR}/ThemeHandler.cpp ${KDEFRONTEND_DIR}/datasources/AsciiOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/BinaryOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/DatabaseManagerDialog.cpp ${KDEFRONTEND_DIR}/datasources/DatabaseManagerWidget.cpp ${KDEFRONTEND_DIR}/datasources/HDF5OptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/FileInfoDialog.cpp ${KDEFRONTEND_DIR}/datasources/ImageOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/ImportDialog.cpp ${KDEFRONTEND_DIR}/datasources/ImportFileWidget.cpp ${KDEFRONTEND_DIR}/datasources/ImportFileDialog.cpp ${KDEFRONTEND_DIR}/datasources/ImportProjectDialog.cpp ${KDEFRONTEND_DIR}/datasources/ImportSQLDatabaseDialog.cpp ${KDEFRONTEND_DIR}/datasources/ImportSQLDatabaseWidget.cpp ${KDEFRONTEND_DIR}/datasources/NetCDFOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/ROOTOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/FITSOptionsWidget.cpp + ${KDEFRONTEND_DIR}/datasources/JsonOptionsWidget.cpp ${KDEFRONTEND_DIR}/dockwidgets/AxisDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/NoteDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/CartesianPlotDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/CartesianPlotLegendDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/HistogramDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/BarChartPlotDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/CustomPointDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/ColumnDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/LiveDataDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/MatrixDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/ProjectDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/SpreadsheetDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYEquationCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYDataReductionCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYDifferentiationCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYIntegrationCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYInterpolationCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYSmoothCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYFitCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYFourierFilterCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/XYFourierTransformCurveDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/WorksheetDock.cpp ${KDEFRONTEND_DIR}/matrix/MatrixFunctionDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/PlotDataDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/EquidistantValuesDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/ExportSpreadsheetDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/DropValuesDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/FunctionValuesDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/RandomValuesDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/SortDialog.cpp ${KDEFRONTEND_DIR}/spreadsheet/StatisticsDialog.cpp ${KDEFRONTEND_DIR}/worksheet/ExportWorksheetDialog.cpp ${KDEFRONTEND_DIR}/worksheet/GridDialog.cpp ${KDEFRONTEND_DIR}/worksheet/DynamicPresenterWidget.cpp ${KDEFRONTEND_DIR}/worksheet/PresenterWidget.cpp ${KDEFRONTEND_DIR}/worksheet/SlidingPanel.cpp ${KDEFRONTEND_DIR}/widgets/ConstantsWidget.cpp ${KDEFRONTEND_DIR}/widgets/ThemesComboBox.cpp ${KDEFRONTEND_DIR}/widgets/ThemesWidget.cpp ${KDEFRONTEND_DIR}/widgets/ExpressionTextEdit.cpp ${KDEFRONTEND_DIR}/widgets/FitOptionsWidget.cpp ${KDEFRONTEND_DIR}/widgets/FitParametersWidget.cpp ${KDEFRONTEND_DIR}/widgets/FunctionsWidget.cpp ${KDEFRONTEND_DIR}/widgets/LabelWidget.cpp ${KDEFRONTEND_DIR}/widgets/DatapickerImageWidget.cpp ${KDEFRONTEND_DIR}/widgets/DatapickerCurveWidget.cpp ${KDEFRONTEND_DIR}/widgets/FITSHeaderEditWidget.cpp ${KDEFRONTEND_DIR}/widgets/FITSHeaderEditNewKeywordDialog.cpp ${KDEFRONTEND_DIR}/widgets/FITSHeaderEditAddUnitDialog.cpp ${KDEFRONTEND_DIR}/widgets/FITSHeaderEditDialog.cpp ${KDEFRONTEND_DIR}/widgets/ResizableTextEdit.cpp ) set(UI_SOURCES ${KDEFRONTEND_DIR}/ui/constantswidget.ui ${KDEFRONTEND_DIR}/ui/functionswidget.ui ${KDEFRONTEND_DIR}/ui/fitoptionswidget.ui ${KDEFRONTEND_DIR}/ui/fitparameterswidget.ui ${KDEFRONTEND_DIR}/ui/labelwidget.ui ${KDEFRONTEND_DIR}/ui/settingsgeneralpage.ui ${KDEFRONTEND_DIR}/ui/settingsworksheetpage.ui ${KDEFRONTEND_DIR}/ui/settingsprintingpage.ui ${KDEFRONTEND_DIR}/ui/datasources/asciioptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/binaryoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/databasemanagerwidget.ui ${KDEFRONTEND_DIR}/ui/datasources/hdf5optionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/imageoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/importfilewidget.ui ${KDEFRONTEND_DIR}/ui/datasources/importprojectwidget.ui ${KDEFRONTEND_DIR}/ui/datasources/importsqldatabasewidget.ui ${KDEFRONTEND_DIR}/ui/datasources/netcdfoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/rootoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/fitsoptionswidget.ui + ${KDEFRONTEND_DIR}/ui/datasources/jsonoptionswidget.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/axisdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/cartesianplotdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/cartesianplotlegenddock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/histogramdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/barchartplotdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/columndock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/custompointdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/livedatadock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/notedock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/matrixdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/projectdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/spreadsheetdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xycurvedock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xycurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xydatareductioncurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xydifferentiationcurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xyintegrationcurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xyinterpolationcurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xysmoothcurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xyfitcurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xyfourierfiltercurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xyfouriertransformcurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/xyequationcurvedockgeneraltab.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/worksheetdock.ui ${KDEFRONTEND_DIR}/ui/matrix/matrixfunctionwidget.ui ${KDEFRONTEND_DIR}/ui/spreadsheet/plotdatawidget.ui ${KDEFRONTEND_DIR}/ui/spreadsheet/equidistantvalueswidget.ui ${KDEFRONTEND_DIR}/ui/spreadsheet/exportspreadsheetwidget.ui ${KDEFRONTEND_DIR}/ui/spreadsheet/dropvalueswidget.ui ${KDEFRONTEND_DIR}/ui/spreadsheet/functionvalueswidget.ui ${KDEFRONTEND_DIR}/ui/spreadsheet/randomvalueswidget.ui ${KDEFRONTEND_DIR}/ui/spreadsheet/sortdialogwidget.ui ${KDEFRONTEND_DIR}/ui/worksheet/exportworksheetwidget.ui ${KDEFRONTEND_DIR}/ui/datapickerimagewidget.ui ${KDEFRONTEND_DIR}/ui/datapickercurvewidget.ui ${KDEFRONTEND_DIR}/ui/fitsheadereditwidget.ui ${KDEFRONTEND_DIR}/ui/fitsheadereditnewkeywordwidget.ui ${KDEFRONTEND_DIR}/ui/fitsheadereditaddunitwidget.ui ) set(BACKEND_SOURCES ${BACKEND_DIR}/core/Folder.cpp ${BACKEND_DIR}/core/AbstractAspect.cpp ${BACKEND_DIR}/core/AbstractColumn.cpp ${BACKEND_DIR}/core/AbstractColumnPrivate.cpp ${BACKEND_DIR}/core/abstractcolumncommands.cpp ${BACKEND_DIR}/core/AbstractFilter.cpp ${BACKEND_DIR}/core/AbstractSimpleFilter.cpp ${BACKEND_DIR}/core/column/Column.cpp ${BACKEND_DIR}/core/column/ColumnPrivate.cpp ${BACKEND_DIR}/core/column/ColumnStringIO.cpp ${BACKEND_DIR}/core/column/columncommands.cpp ${BACKEND_DIR}/core/AbstractScriptingEngine.cpp ${BACKEND_DIR}/core/AbstractScript.cpp ${BACKEND_DIR}/core/ScriptingEngineManager.cpp ${BACKEND_DIR}/core/Project.cpp ${BACKEND_DIR}/core/AbstractPart.cpp ${BACKEND_DIR}/core/Workbook.cpp ${BACKEND_DIR}/core/AspectTreeModel.cpp ${BACKEND_DIR}/core/datatypes/SimpleCopyThroughFilter.h ${BACKEND_DIR}/core/datatypes/Double2DateTimeFilter.h ${BACKEND_DIR}/core/datatypes/Double2DayOfWeekFilter.h ${BACKEND_DIR}/core/datatypes/Double2IntegerFilter.h ${BACKEND_DIR}/core/datatypes/Double2MonthFilter.h ${BACKEND_DIR}/core/datatypes/Double2StringFilter.cpp ${BACKEND_DIR}/core/datatypes/Integer2DateTimeFilter.h ${BACKEND_DIR}/core/datatypes/Integer2DayOfWeekFilter.h ${BACKEND_DIR}/core/datatypes/Integer2DoubleFilter.h ${BACKEND_DIR}/core/datatypes/Integer2MonthFilter.h ${BACKEND_DIR}/core/datatypes/Integer2StringFilter.h ${BACKEND_DIR}/core/datatypes/String2DayOfWeekFilter.h ${BACKEND_DIR}/core/datatypes/String2DoubleFilter.h ${BACKEND_DIR}/core/datatypes/String2IntegerFilter.h ${BACKEND_DIR}/core/datatypes/String2MonthFilter.h ${BACKEND_DIR}/core/datatypes/String2DateTimeFilter.cpp ${BACKEND_DIR}/core/datatypes/DateTime2DoubleFilter.h ${BACKEND_DIR}/core/datatypes/DateTime2IntegerFilter.h ${BACKEND_DIR}/core/datatypes/DateTime2StringFilter.cpp ${BACKEND_DIR}/core/datatypes/Month2DoubleFilter.h ${BACKEND_DIR}/core/datatypes/Month2IntegerFilter.h ${BACKEND_DIR}/core/datatypes/DayOfWeek2DoubleFilter.h ${BACKEND_DIR}/core/datatypes/DayOfWeek2IntegerFilter.h ${BACKEND_DIR}/core/plugin/PluginLoader.cpp ${BACKEND_DIR}/core/plugin/PluginManager.cpp ${BACKEND_DIR}/datasources/AbstractDataSource.cpp ${BACKEND_DIR}/datasources/LiveDataSource.cpp ${BACKEND_DIR}/datasources/filters/AbstractFileFilter.cpp ${BACKEND_DIR}/datasources/filters/AsciiFilter.cpp ${BACKEND_DIR}/datasources/filters/BinaryFilter.cpp ${BACKEND_DIR}/datasources/filters/HDF5Filter.cpp ${BACKEND_DIR}/datasources/filters/ImageFilter.cpp + ${BACKEND_DIR}/datasources/filters/JsonFilter.cpp ${BACKEND_DIR}/datasources/filters/NetCDFFilter.cpp ${BACKEND_DIR}/datasources/filters/NgspiceRawAsciiFilter.cpp ${BACKEND_DIR}/datasources/filters/FITSFilter.cpp + ${BACKEND_DIR}/datasources/filters/QJsonModel.cpp ${BACKEND_DIR}/datasources/filters/ROOTFilter.cpp ${BACKEND_DIR}/datasources/projects/ProjectParser.cpp ${BACKEND_DIR}/datasources/projects/LabPlotProjectParser.cpp ${BACKEND_DIR}/datasources/projects/OriginProjectParser.cpp ${BACKEND_DIR}/gsl/ExpressionParser.cpp ${BACKEND_DIR}/matrix/Matrix.cpp ${BACKEND_DIR}/matrix/matrixcommands.cpp ${BACKEND_DIR}/matrix/MatrixModel.cpp ${BACKEND_DIR}/spreadsheet/Spreadsheet.cpp ${BACKEND_DIR}/spreadsheet/SpreadsheetModel.cpp ${BACKEND_DIR}/lib/XmlStreamReader.cpp ${BACKEND_DIR}/note/Note.cpp ${BACKEND_DIR}/worksheet/WorksheetElement.cpp ${BACKEND_DIR}/worksheet/TextLabel.cpp ${BACKEND_DIR}/worksheet/Worksheet.cpp ${BACKEND_DIR}/worksheet/WorksheetElementContainer.cpp ${BACKEND_DIR}/worksheet/WorksheetElementGroup.cpp ${BACKEND_DIR}/worksheet/plots/AbstractPlot.cpp ${BACKEND_DIR}/worksheet/plots/AbstractCoordinateSystem.cpp ${BACKEND_DIR}/worksheet/plots/PlotArea.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/Axis.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/CartesianPlot.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/CartesianPlotLegend.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/Histogram.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/BarChartPlot.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/CustomPoint.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/Symbol.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYAnalysisCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYEquationCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYDataReductionCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYDifferentiationCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYIntegrationCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYInterpolationCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYSmoothCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYFitCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYFourierFilterCurve.cpp ${BACKEND_DIR}/worksheet/plots/cartesian/XYFourierTransformCurve.cpp ${BACKEND_DIR}/lib/SignallingUndoCommand.cpp ${BACKEND_DIR}/datapicker/DatapickerPoint.cpp ${BACKEND_DIR}/datapicker/DatapickerImage.cpp ${BACKEND_DIR}/datapicker/Datapicker.cpp ${BACKEND_DIR}/datapicker/Transform.cpp ${BACKEND_DIR}/datapicker/ImageEditor.cpp ${BACKEND_DIR}/datapicker/Segment.cpp ${BACKEND_DIR}/datapicker/Segments.cpp ${BACKEND_DIR}/datapicker/DatapickerCurve.cpp ) set(NSL_SOURCES ${BACKEND_DIR}/nsl/nsl_dft.c ${BACKEND_DIR}/nsl/nsl_diff.c ${BACKEND_DIR}/nsl/nsl_filter.c ${BACKEND_DIR}/nsl/nsl_fit.c ${BACKEND_DIR}/nsl/nsl_geom.c ${BACKEND_DIR}/nsl/nsl_geom_linesim.c ${BACKEND_DIR}/nsl/nsl_int.c ${BACKEND_DIR}/nsl/nsl_interp.c ${BACKEND_DIR}/nsl/nsl_math.c ${BACKEND_DIR}/nsl/nsl_sf_basic.c ${BACKEND_DIR}/nsl/nsl_sf_kernel.c ${BACKEND_DIR}/nsl/nsl_sf_poly.c ${BACKEND_DIR}/nsl/nsl_sf_stats.c ${BACKEND_DIR}/nsl/nsl_sf_window.c ${BACKEND_DIR}/nsl/nsl_smooth.c ${BACKEND_DIR}/nsl/nsl_sort.c ${BACKEND_DIR}/nsl/nsl_stats.c ) IF (NOT MSVC_FOUND) IF (NOT LIBCERF_FOUND) list(APPEND NSL_SOURCES ${BACKEND_DIR}/nsl/Faddeeva.c ) ENDIF () ENDIF () set(COMMONFRONTEND_SOURCES ${COMMONFRONTEND_DIR}/matrix/MatrixView.cpp ${COMMONFRONTEND_DIR}/note/NoteView.cpp ${COMMONFRONTEND_DIR}/spreadsheet/SpreadsheetCommentsHeaderModel.cpp ${COMMONFRONTEND_DIR}/spreadsheet/SpreadsheetHeaderView.cpp ${COMMONFRONTEND_DIR}/spreadsheet/SpreadsheetItemDelegate.cpp ${COMMONFRONTEND_DIR}/spreadsheet/SpreadsheetView.cpp ${COMMONFRONTEND_DIR}/workbook/WorkbookView.cpp ${COMMONFRONTEND_DIR}/worksheet/WorksheetView.cpp ${COMMONFRONTEND_DIR}/ProjectExplorer.cpp ${COMMONFRONTEND_DIR}/core/PartMdiView.cpp ${COMMONFRONTEND_DIR}/widgets/TreeViewComboBox.cpp ${COMMONFRONTEND_DIR}/widgets/qxtspanslider.cpp ${COMMONFRONTEND_DIR}/datapicker/DatapickerView.cpp ${COMMONFRONTEND_DIR}/datapicker/DatapickerImageView.cpp ) IF (${CANTOR_LIBS_FOUND}) set(CANTOR_SOURCES ${KDEFRONTEND_DIR}/dockwidgets/CantorWorksheetDock.cpp ${BACKEND_DIR}/cantorWorksheet/VariableParser.cpp ${BACKEND_DIR}/cantorWorksheet/CantorWorksheet.cpp ${COMMONFRONTEND_DIR}/cantorWorksheet/CantorWorksheetView.cpp ) set(CANTOR_UI_SOURCES ${KDEFRONTEND_DIR}/ui/dockwidgets/cantorworksheetdock.ui) set(UI_SOURCES ${UI_SOURCES} ${CANTOR_UI_SOURCES}) ELSE () set(CANTOR_SOURCES "") ENDIF () set(TOOLS_SOURCES ${TOOLS_DIR}/EquationHighlighter.cpp ${TOOLS_DIR}/ImageTools.cpp ${TOOLS_DIR}/TeXRenderer.cpp ) bison_target(GslParser ${BACKEND_DIR}/gsl/parser.y ${CMAKE_CURRENT_BINARY_DIR}/gsl_parser.c ) set(GENERATED_SOURCES ${BISON_GslParser_OUTPUTS} ) ############################################################################## INCLUDE_DIRECTORIES( . ${BACKEND_DIR}/gsl ${GSL_INCLUDE_DIR} ) set( LABPLOT_SRCS ${GUI_SOURCES} ) ki18n_wrap_ui( LABPLOT_SRCS ${UI_SOURCES} ) # TODO: build without: add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0) # static library add_library( labplot2lib STATIC ${LABPLOT_SRCS} ${BACKEND_SOURCES} ${NSL_SOURCES} ${CANTOR_SOURCES} ${DATASOURCES_SOURCES} ${COMMONFRONTEND_SOURCES} ${TOOLS_SOURCES} ${GENERATED_SOURCES} ${QTMOC_HDRS} ) # set_property(TARGET ${objlib} PROPERTY POSITION_INDEPENDENT_CODE 1) target_link_libraries( labplot2lib KF5::Archive KF5::Completion KF5::ConfigCore KF5::I18n KF5::IconThemes KF5::TextWidgets KF5::XmlGui Qt5::Svg Qt5::Core Qt5::Network Qt5::PrintSupport Qt5::Sql ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES} ) IF (Qt5SerialPort_FOUND) target_link_libraries( labplot2lib Qt5::SerialPort ) ENDIF () IF (KF5SyntaxHighlighting_FOUND) target_link_libraries( labplot2lib KF5::SyntaxHighlighting ) ENDIF () #TODO: KF5::NewStuff IF (CANTOR_LIBS_FOUND) target_link_libraries( labplot2lib ${CANTOR_LIBS} KF5::Service KF5::Parts) ENDIF () IF (HDF5_FOUND) target_link_libraries( labplot2lib ${HDF5_LIBRARIES} ) ENDIF () IF (FFTW_FOUND) target_link_libraries( labplot2lib ${FFTW_LIBRARIES} ) ENDIF () IF (NETCDF_FOUND) target_link_libraries( labplot2lib ${NETCDF_LIBRARY} ) ENDIF () IF (CFITSIO_FOUND) target_link_libraries( labplot2lib ${CFITSIO_LIBRARY} ) ENDIF () IF (LIBCERF_FOUND) target_link_libraries( labplot2lib ${LIBCERF_LIBRARY} ) ENDIF () IF (ZLIB_FOUND AND LZ4_FOUND) target_link_libraries( labplot2lib ${ZLIB_LIBRARY} ${LZ4_LIBRARY} ) ENDIF () IF (ENABLE_LIBORIGIN) target_link_libraries( labplot2lib liborigin-static ) ENDIF () # icons for the executable on Windows and Mac OS X set(LABPLOT_ICONS ${CMAKE_CURRENT_SOURCE_DIR}/../icons/16-apps-labplot2.png ${CMAKE_CURRENT_SOURCE_DIR}/../icons/32-apps-labplot2.png ${CMAKE_CURRENT_SOURCE_DIR}/../icons/48-apps-labplot2.png ${CMAKE_CURRENT_SOURCE_DIR}/../icons/64-apps-labplot2.png ${CMAKE_CURRENT_SOURCE_DIR}/../icons/128-apps-labplot2.png ${CMAKE_CURRENT_SOURCE_DIR}/../icons/256-apps-labplot2.png ${CMAKE_CURRENT_SOURCE_DIR}/../icons/512-apps-labplot2.png ) # main executable set(LABPLOT_SOURCE ${KDEFRONTEND_DIR}/LabPlot.cpp) ecm_add_app_icon(LABPLOT_SOURCE ICONS ${LABPLOT_ICONS}) add_executable( labplot2 ${LABPLOT_SOURCE} ) target_link_libraries( labplot2 labplot2lib ) ############## installation ################################ install( TARGETS labplot2 DESTINATION ${INSTALL_TARGETS_DEFAULT_ARGS} ) install( FILES ${KDEFRONTEND_DIR}/labplot2ui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/${PROJECT_NAME} ) install( FILES ${KDEFRONTEND_DIR}/splash.png ${KDEFRONTEND_DIR}/labplot2.ico DESTINATION ${DATA_INSTALL_DIR}/${PROJECT_NAME} ) install( PROGRAMS org.kde.labplot2.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES labplot2.xml DESTINATION ${XDG_MIME_INSTALL_DIR} ) install( FILES labplot2_themes.knsrc DESTINATION ${CONFIG_INSTALL_DIR} ) update_xdg_mimetypes( ${XDG_MIME_INSTALL_DIR} ) diff --git a/src/backend/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp index 364f14a1e..3427d3a50 100644 --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -1,933 +1,934 @@ /*************************************************************************** File : LiveDataSource.cpp Project : LabPlot Description : Represents live data source -------------------------------------------------------------------- Copyright : (C) 2009-2017 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/core/Project.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include #include #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(AbstractScriptingEngine* engine, const QString& name, bool loading) : Spreadsheet(engine, name, loading), m_fileType(AbstractFileFilter::Ascii), m_fileWatched(false), m_fileLinked(false), m_paused(false), m_prepared(false), m_sampleSize(1), m_keepNValues(0), m_updateInterval(1000), //TODO: m_port, m_baudRate ? m_bytesRead(0), m_filter(nullptr), m_updateTimer(new QTimer(this)), m_fileSystemWatcher(nullptr), m_file(nullptr), m_localSocket(nullptr), m_tcpSocket(nullptr), m_udpSocket(nullptr), m_serialPort(nullptr), m_device(nullptr) { initActions(); connect(m_updateTimer, &QTimer::timeout, this, &LiveDataSource::read); } LiveDataSource::~LiveDataSource() { //stop reading before deleting the objects pauseReading(); if (m_filter) delete m_filter; if (m_fileSystemWatcher) delete m_fileSystemWatcher; if (m_file) delete m_file; if (m_localSocket) delete m_localSocket; if (m_tcpSocket) delete m_tcpSocket; if (m_serialPort) 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_partView = new SpreadsheetView(const_cast(this), true); 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()); qDebug() << sp.description(); qDebug() << sp.manufacturer(); qDebug() << sp.portName(); qDebug() << sp.serialNumber(); qDebug() << sp.systemLocation(); } 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: 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) { 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; 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 connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); break; case TimeInterval: if (m_fileSystemWatcher) 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; } 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; } QIcon LiveDataSource::icon() const { QIcon icon; if (m_fileType == AbstractFileFilter::Ascii) icon = QIcon::fromTheme("text-plain"); else if (m_fileType == AbstractFileFilter::Binary) icon = QIcon::fromTheme("application-octet-stream"); else if (m_fileType == AbstractFileFilter::Image) icon = QIcon::fromTheme("image-x-generic"); // TODO: HDF5, NetCDF, FITS, etc. return icon; } QMenu* LiveDataSource::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); QAction* firstAction = 0; // 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: doesnt 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 == nullptr) return; //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; m_device = m_serialPort; m_serialPort->setBaudRate(m_baudRate); m_serialPort->setPortName(m_serialPortName); connect(m_serialPort, static_cast(&QSerialPort::error), this, &LiveDataSource::serialPortError); connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead); break; } m_prepared = true; } qint64 bytes = 0; switch (m_sourceType) { case FileOrPipe: DEBUG("Reading FileOrPipe. type = " << ENUM_TO_STRING(LiveDataSource,FileType,m_fileType)); switch (m_fileType) { case AbstractFileFilter::Ascii: if (m_readingType == LiveDataSource::ReadingType::WholeFile) { dynamic_cast(m_filter)->readFromLiveDevice(*m_file, this, 0); } else { bytes = dynamic_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: bytes = dynamic_cast(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); m_bytesRead += bytes; //TODO:? case AbstractFileFilter::Image: case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: + case AbstractFileFilter::Json: case AbstractFileFilter::ROOT: case AbstractFileFilter::NgspiceRawAscii: 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) dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; case LocalSocket: DEBUG("reading from local socket. state before abort = " << ENUM_TO_STRING(QLocalSocket, LocalSocketState, m_localSocket->state())); m_localSocket->abort(); m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); DEBUG("reading from local socket. state after reconnect = " << m_localSocket->state()); break; case SerialPort: //TODO: Test DEBUG("reading from the serial port"); m_serialPort->setBaudRate(m_baudRate); m_serialPort->setPortName(m_serialPortName); m_device = m_serialPort; break; } } /*! * Slot for the signal that is emitted once every time new data is available for reading from the device. * 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) dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //TODO: else if (m_fileType == 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 sequencial 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(0, i18n("Serial Port Error"), i18n("Failed to open the device.")); break; case QSerialPort::PermissionError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Failed to open the device. Please check your permissions on this device.")); break; case QSerialPort::OpenError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Device already opened.")); break; case QSerialPort::NotOpenError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("The device is not opened.")); break; case QSerialPort::ReadError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Failed to read data.")); break; case QSerialPort::ResourceError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Failed to read data. The device is removed.")); break; case QSerialPort::TimeoutError: QMessageBox::critical(0, 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(0, 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 void LiveDataSource::watch() { DEBUG("LiveDataSource::watch() file name = " << m_fileName.toStdString()); if (m_fileWatched) { if (!m_fileSystemWatcher) { m_fileSystemWatcher = new QFileSystemWatcher; connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); } if ( !m_fileSystemWatcher->files().contains(m_fileName) ) m_fileSystemWatcher->addPath(m_fileName); } else { if (m_fileSystemWatcher) m_fileSystemWatcher->removePath(m_fileName); } } void LiveDataSource::plotData() { PlotDataDialog* 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 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; default: break; } writer->writeEndElement(); //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("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 FileOrPipe: break; case LocalSocket: break; default: break; } } else if (reader->name() == "asciiFilter") { m_filter = new AsciiFilter(); if (!m_filter->load(reader)) return false; } else if(reader->name() == "column") { Column* column = new Column("", 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; } } //read the content of the file if it was only linked if (m_fileLinked) this->read(); return !reader->hasError(); } diff --git a/src/backend/datasources/filters/AbstractFileFilter.cpp b/src/backend/datasources/filters/AbstractFileFilter.cpp index 298c2e3f6..4078ed55c 100644 --- a/src/backend/datasources/filters/AbstractFileFilter.cpp +++ b/src/backend/datasources/filters/AbstractFileFilter.cpp @@ -1,150 +1,154 @@ /*************************************************************************** File : AbstractFileFilter.h Project : LabPlot Description : file I/O-filter related interface -------------------------------------------------------------------- Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/lib/macros.h" #include #include #ifndef HAVE_WINDOWS #include #endif #include #include bool AbstractFileFilter::isNan(QString s) { QStringList nanStrings; nanStrings << "NA" << "NAN" << "N/A" << "-NA" << "-NAN" << "NULL"; if (nanStrings.contains(s, Qt::CaseInsensitive)) return true; return false; } AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat, QLocale::Language lang) { QLocale locale(lang); if (valueString.size() == 0) // empty string treated as integer (meaning the non-empty strings will determine the data type) return AbstractColumn::Integer; if (isNan(valueString)) return AbstractColumn::Numeric; // check if integer first bool ok; locale.toInt(valueString, &ok); DEBUG("string " << valueString.toStdString() << ": toInt " << locale.toInt(valueString, &ok) << "?:" << ok); if (ok || isNan(valueString)) return AbstractColumn::Integer; //try to convert to a double AbstractColumn::ColumnMode mode = AbstractColumn::Numeric; locale.toDouble(valueString, &ok); DEBUG("string " << valueString.toStdString() << ": toDouble " << locale.toDouble(valueString, &ok) << "?:" << ok); //if not a number, check datetime. if that fails: string if (!ok) { QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); if (valueDateTime.isValid()) mode = AbstractColumn::DateTime; else mode = AbstractColumn::Text; } return mode; } /* returns the list of all supported locales for numeric data */ QStringList AbstractFileFilter::numberFormats() { QStringList formats; for (int l = 0; l < ENUM_COUNT(QLocale, Language); ++l) formats << QLocale::languageToString((QLocale::Language)l); return formats; } AbstractFileFilter::FileType AbstractFileFilter::fileType(const QString& fileName) { QString fileInfo; #ifndef HAVE_WINDOWS //check, if we can guess the file type by content QProcess* proc = new QProcess(); QStringList args; args << "-b" << fileName; proc->start("file", args); if (proc->waitForReadyRead(1000) == false) { QDEBUG("ERROR: reading file type of file" << fileName); return Binary; } fileInfo = proc->readLine(); #endif FileType fileType; QByteArray imageFormat = QImageReader::imageFormat(fileName); - if (fileInfo.contains(QLatin1String("compressed data")) || fileInfo.contains(QLatin1String("ASCII")) || + if (fileInfo.contains(QLatin1String("JSON")) || fileName.endsWith(QLatin1String("json"), Qt::CaseInsensitive)) { + //*.json files can be recognized as ASCII. so, do the check for the json-extension as first. + fileType = Json; + } else if (fileInfo.contains(QLatin1String("compressed data")) || fileInfo.contains(QLatin1String("ASCII")) || fileName.endsWith(QLatin1String("dat"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("txt"), Qt::CaseInsensitive)) { if (NgspiceRawAsciiFilter::isNgspiceAsciiFile(fileName)) fileType = NgspiceRawAscii; else //probably ascii data fileType = Ascii; } else if (fileInfo.contains(QLatin1String("Hierarchical Data Format")) || fileName.endsWith(QLatin1String("h5"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf5"), Qt::CaseInsensitive) ) fileType = HDF5; else if (fileInfo.contains(QLatin1String("NetCDF Data Format")) || fileName.endsWith(QLatin1String("nc"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("netcdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("cdf"), Qt::CaseInsensitive)) fileType = NETCDF; else if (fileInfo.contains(QLatin1String("FITS image data")) || fileName.endsWith(QLatin1String("fits"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fit"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fts"), Qt::CaseInsensitive)) fileType = FITS; else if (fileInfo.contains(QLatin1String("ROOT Data Format")) || fileName.endsWith(QLatin1String("root"), Qt::CaseInsensitive)) // TODO find out file description fileType = ROOT; else if (fileInfo.contains("image") || fileInfo.contains("bitmap") || !imageFormat.isEmpty()) fileType = Image; else fileType = Binary; return fileType; } /*! returns the list of all supported data file formats */ QStringList AbstractFileFilter::fileTypes() { return (QStringList() << i18n("ASCII data") << i18n("Binary data") << i18n("Image") << i18n("Hierarchical Data Format 5 (HDF5)") << i18n("Network Common Data Format (NetCDF)") << i18n("Flexible Image Transport System Data Format (FITS)") + << i18n("JSON data") << i18n("ROOT (CERN) Histograms") << "Ngspice RAW ASCII" ); } diff --git a/src/backend/datasources/filters/AbstractFileFilter.h b/src/backend/datasources/filters/AbstractFileFilter.h index e0a0be09f..a1c74c12c 100644 --- a/src/backend/datasources/filters/AbstractFileFilter.h +++ b/src/backend/datasources/filters/AbstractFileFilter.h @@ -1,72 +1,72 @@ /*************************************************************************** File : AbstractFileFilter.h Project : LabPlot Description : file I/O-filter related interface -------------------------------------------------------------------- Copyright : (C) 2009-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef ABSTRACTFILEFILTER_H #define ABSTRACTFILEFILTER_H #include "backend/core/AbstractColumn.h" #include #include #include // smart pointer class AbstractDataSource; class XmlStreamReader; class QXmlStreamWriter; class AbstractFileFilter : public QObject { Q_OBJECT Q_ENUMS(ImportMode) public: - enum FileType {Ascii, Binary, Image, HDF5, NETCDF, FITS, ROOT, NgspiceRawAscii}; + enum FileType {Ascii, Binary, Image, HDF5, NETCDF, FITS, Json, ROOT, NgspiceRawAscii}; enum ImportMode {Append, Prepend, Replace}; AbstractFileFilter() {} ~AbstractFileFilter() override {} static bool isNan(QString); static AbstractColumn::ColumnMode columnMode(const QString& valueString, const QString& dateTimeFormat, QLocale::Language); static QStringList numberFormats(); static AbstractFileFilter::FileType fileType(const QString&); static QStringList fileTypes(); virtual QVector readDataFromFile(const QString& fileName, AbstractDataSource* = nullptr, ImportMode = AbstractFileFilter::Replace, int lines = -1) = 0; virtual void write(const QString& fileName, AbstractDataSource*) = 0; virtual void loadFilterSettings(const QString& filterName) = 0; virtual void saveFilterSettings(const QString& filterName) const = 0; virtual void save(QXmlStreamWriter*) const = 0; virtual bool load(XmlStreamReader*) = 0; signals: void completed(int) const; //!< int ranging from 0 to 100 notifies about the status of a read/write process }; #endif diff --git a/src/backend/datasources/filters/JsonFilter.cpp b/src/backend/datasources/filters/JsonFilter.cpp new file mode 100644 index 000000000..737ebef95 --- /dev/null +++ b/src/backend/datasources/filters/JsonFilter.cpp @@ -0,0 +1,840 @@ +/*************************************************************************** + File : JsonFilter.cpp + Project : LabPlot + Description : JSON I/O-filter. + -------------------------------------------------------------------- + -------------------------------------------------------------------- + Copyright : (C) 2018 Andrey Cygankov (craftplace.ms@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 "backend/datasources/filters/JsonFilter.h" +#include "backend/datasources/filters/JsonFilterPrivate.h" +#include "backend/datasources/AbstractDataSource.h" +#include "backend/core/column/Column.h" + +#include +#include +#include +#include +#include +#include +#include + +/*! +\class JsonFilter +\brief Manages the import/export of data from/to a file formatted using JSON. + +\ingroup datasources +*/ +JsonFilter::JsonFilter() : AbstractFileFilter(), d(new JsonFilterPrivate(this)) {} + +JsonFilter::~JsonFilter() {} + +/*! +reads the content of the device \c device. +*/ +void JsonFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { + d->readDataFromDevice(device, dataSource, importMode, lines); +} + +/*! +reads the content of the file \c fileName. +*/ +QVector JsonFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { + d->readDataFromFile(fileName, dataSource, importMode, lines); + return QVector(); //TODO: remove this later once all read*-functions in the filter classes don't return any preview strings anymore +} + +QVector JsonFilter::preview(const QString& fileName) { + return d->preview(fileName); +} + +QVector JsonFilter::preview(QIODevice& device) { + return d->preview(device); +} + +QVector JsonFilter::preview(QJsonDocument& doc) { + return d->preview(doc); +} + +/*! +writes the content of the data source \c dataSource to the file \c fileName. +*/ +void JsonFilter::write(const QString& fileName, AbstractDataSource* dataSource) { + d->write(fileName, dataSource); +} + +/////////////////////////////////////////////////////////////////////// +/*! +loads the predefined filter settings for \c filterName +*/ +void JsonFilter::loadFilterSettings(const QString& filterName) { + Q_UNUSED(filterName); +} + +/*! +saves the current settings as a new filter with the name \c filterName +*/ +void JsonFilter::saveFilterSettings(const QString& filterName) const { + Q_UNUSED(filterName); +} + +/*! +returns the list of all predefined data types. +*/ +QStringList JsonFilter::dataTypes() { + const QMetaObject& mo = AbstractColumn::staticMetaObject; + const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode")); + QStringList list; + for (int i = 0; i <= 100; ++i) // me.keyCount() does not work because we have holes in enum + if (me.valueToKey(i)) + list << me.valueToKey(i); + return list; +} + +/*! +returns the list of all predefined data row types. +*/ +QStringList JsonFilter::dataRowTypes() { + return (QStringList() << "Array" << "Object"); +} + +void JsonFilter::setDataRowType(QJsonValue::Type type) { + d->rowType = type; +} +QJsonValue::Type JsonFilter::dataRowType() const { + return d->rowType; +} + +void JsonFilter::setModelRows(QVector rows){ + d->modelRows = rows; +} + +QVector JsonFilter::modelRows() const { + return d->modelRows; +} + +void JsonFilter::setDateTimeFormat(const QString &f) { + d->dateTimeFormat = f; +} +QString JsonFilter::dateTimeFormat() const { + return d->dateTimeFormat; +} + +void JsonFilter::setNumberFormat(QLocale::Language lang) { + d->numberFormat = lang; +} +QLocale::Language JsonFilter::numberFormat() const { + return d->numberFormat; +} + +void JsonFilter::setNaNValueToZero(bool b) { + if (b) + d->nanValue = 0; + else + d->nanValue = NAN; +} +bool JsonFilter::NaNValueToZeroEnabled() const { + if (d->nanValue == 0) + return true; + return false; +} + +void JsonFilter::setCreateIndexEnabled(bool b){ + d->createIndexEnabled = b; +} + +void JsonFilter::setParseRowsName(bool b) { + d->parseRowsName = b; +} + +void JsonFilter::setVectorNames(const QString& s) { + d->vectorNames.clear(); + if (!s.simplified().isEmpty()) + d->vectorNames = s.simplified().split(' '); +} +QStringList JsonFilter::vectorNames() const { + return d->vectorNames; +} + +QVector JsonFilter::columnModes() { + return d->columnModes; +} + +void JsonFilter::setStartRow(const int r) { + d->startRow = r; +} +int JsonFilter::startRow() const { + return d->startRow; +} + +void JsonFilter::setEndRow(const int r) { + d->endRow = r; +} +int JsonFilter::endRow() const { + return d->endRow; +} + +void JsonFilter::setStartColumn(const int c) { + d->startColumn = c; +} +int JsonFilter::startColumn() const { + return d->startColumn; +} + +void JsonFilter::setEndColumn(const int c) { + d->endColumn = c; +} +int JsonFilter::endColumn() const { + return d->endColumn; +} + +//##################################################################### +//################### Private implementation ########################## +//##################################################################### +JsonFilterPrivate::JsonFilterPrivate(JsonFilter* owner) : q(owner), + model(new QJsonModel()), + containerType(JsonFilter::Object), + rowType(QJsonValue::Object), + numberFormat(QLocale::C), + createIndexEnabled(false), + parseRowsName(false), + vectorNames(), + startRow(1), + endRow(-1), + startColumn(1), + endColumn(-1), + m_actualRows(0), + m_actualCols(0), + m_prepared(false), + m_columnOffset(0) { +} +//TODO: delete model from memory + +/*! +returns 1 if row is invalid and 0 otherwise. +*/ +int JsonFilterPrivate::checkRow(QJsonValueRef value, int& countCols) { + switch(rowType){ + //TODO: implement other value types + case QJsonValue::Array: { + QJsonArray row = value.toArray(); + if(row.isEmpty()) + return 1; + countCols = (countCols == -1 || countCols > row.count()) ? row.count() : countCols; + break; + } + case QJsonValue::Object: { + QJsonObject row = value.toObject(); + if(row.isEmpty()) + return 1; + countCols = (countCols == -1 || countCols > row.count()) ? row.count() : countCols; + break; + } + case QJsonValue::Double: + case QJsonValue::String: + case QJsonValue::Bool: + case QJsonValue::Null: + case QJsonValue::Undefined: + return 1; + } + return 0; +} + +/*! +returns -1 if a parse error has occurred, 1 if the current row type not supported and 0 otherwise. +*/ +int JsonFilterPrivate::parseColumnModes(QJsonValue row, QString rowName) { + columnModes.resize(m_actualCols); + + int colIndexInContainer = startColumn - 1; + for(int i = 0; i < m_actualCols; ++i){ + if((createIndexEnabled || parseRowsName) && i == 0){ + if(createIndexEnabled) + columnModes[i] = AbstractColumn::Integer; + if(parseRowsName) + columnModes[i + createIndexEnabled] = AbstractFileFilter::columnMode(rowName, dateTimeFormat, numberFormat); + i = i + createIndexEnabled + parseRowsName - 1; + continue; + } + + QJsonValue columnValue; + switch (rowType) { + //TODO: implement other value types + case QJsonValue::Array: { + QJsonArray arr = row.toArray(); + if(arr.count() < colIndexInContainer + 1) + return -1; + columnValue = *(row.toArray().begin() + colIndexInContainer); + break; + } + case QJsonValue::Object: { + QJsonObject obj = row.toObject(); + if(obj.count() < colIndexInContainer + 1) + return -1; + if(vectorNames.count() == 0) + vectorNames = row.toObject().keys(); + columnValue = *(row.toObject().begin() + colIndexInContainer); + break; + } + case QJsonValue::Double: + case QJsonValue::String: + case QJsonValue::Bool: + case QJsonValue::Null: + case QJsonValue::Undefined: + return 1; + } + + switch (columnValue.type()) { + case QJsonValue::Double: + columnModes[i] = AbstractColumn::Numeric; + break; + case QJsonValue::String: + columnModes[i] = AbstractFileFilter::columnMode(columnValue.toString(), dateTimeFormat, numberFormat); + break; + case QJsonValue::Array: + case QJsonValue::Object: + case QJsonValue::Bool: + case QJsonValue::Null: + case QJsonValue::Undefined: + return -1; + } + colIndexInContainer++; + } + + if(parseRowsName) + vectorNames.prepend("row name"); + if(createIndexEnabled) + vectorNames.prepend("index"); + + return 0; +} + +void JsonFilterPrivate::setEmptyValue(int column, int row){ + switch (columnModes[column]) { + case AbstractColumn::Numeric: + static_cast*>(m_dataContainer[column])->operator[](row) = nanValue; + break; + case AbstractColumn::Integer: + static_cast*>(m_dataContainer[column])->operator[](row) = 0; + break; + case AbstractColumn::DateTime: + static_cast*>(m_dataContainer[column])->operator[](row) = QDateTime(); + break; + case AbstractColumn::Text: + static_cast*>(m_dataContainer[column])->operator[](row) = ""; + break; + case AbstractColumn::Month: + case AbstractColumn::Day: + break; + } +} + +void JsonFilterPrivate::setValueFromString(int column, int row, QString valueString){ + QLocale locale(numberFormat); + switch (columnModes[column]) { + case AbstractColumn::Numeric: { + bool isNumber; + const double value = locale.toDouble(valueString, &isNumber); + static_cast*>(m_dataContainer[column])->operator[](row) = isNumber ? value : nanValue; + break; + } + case AbstractColumn::Integer: { + bool isNumber; + const int value = locale.toInt(valueString, &isNumber); + static_cast*>(m_dataContainer[column])->operator[](row) = isNumber ? value : 0; + break; + } + case AbstractColumn::DateTime: { + const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); + static_cast*>(m_dataContainer[column])->operator[](row) = + valueDateTime.isValid() ? valueDateTime : QDateTime(); + break; + } + case AbstractColumn::Text: + static_cast*>(m_dataContainer[column])->operator[](row) = valueString; + break; + case AbstractColumn::Month: + case AbstractColumn::Day: + break; + } +} + +/*! +returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end +*/ +int JsonFilterPrivate::prepareDeviceToRead(QIODevice& device) { + DEBUG("device is sequential = " << device.isSequential()); + + if (!device.open(QIODevice::ReadOnly)) + return -1; + + if (device.atEnd() && !device.isSequential()) // empty file + return 1; + + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(device.readAll(), &err); + + if(err.error != QJsonParseError::NoError || doc.isEmpty()) + return 1; + + if(prepareDocumentToRead(doc) != 0) + return 2; + // reset to start of file + if (!device.isSequential()) + device.seek(0); + + return 0; +} + +/*! +returns 2 if a parse error has occurred and 0 otherwise. +*/ +int JsonFilterPrivate::prepareDocumentToRead(const QJsonDocument& doc) { + model->loadJson(doc); + + if(modelRows.isEmpty()) + m_preparedDoc = doc; + else { + QModelIndex index; + for(auto it = modelRows.begin(); it != modelRows.end(); ++it){ + index = model->index(*it, 0, index); + } + m_preparedDoc = model->genJsonByIndex(index); + } + + if(!m_preparedDoc.isEmpty()){ + if(m_preparedDoc.isArray()) + containerType = JsonFilter::Array; + else if(m_preparedDoc.isObject()) + containerType = JsonFilter::Object; + else + return 2; + } + else + return 2; + + int countRows = 0; + int countCols = -1; + QJsonValue firstRow; + QString firstRowName = ""; + parseRowsName = parseRowsName && rowType == QJsonValue::Object; + + switch(containerType) { + case JsonFilter::Array: { + QJsonArray arr = m_preparedDoc.array(); + + if(arr.count() < startRow) + return 2; + + int endRowOffset = (endRow == -1 || endRow > arr.count()) ? arr.count() : endRow; + firstRow = *(arr.begin() + (startRow - 1)); + for(QJsonArray::iterator it = arr.begin() + (startRow - 1); it != arr.begin() + endRowOffset; ++it) { + if(checkRow(*it, countCols) != 0) + return 2; + countRows++; + } + break; + } + case JsonFilter::Object: { + QJsonObject obj = m_preparedDoc.object(); + + if(obj.count() < startRow) + return 2; + + int startRowOffset = startRow - 1; + int endRowOffset = (endRow == -1 || endRow > obj.count()) ? obj.count() : endRow; + firstRow = *(obj.begin() + startRowOffset); + firstRowName = (obj.begin() + startRowOffset).key(); + for(QJsonObject::iterator it = obj.begin() + startRowOffset; it != obj.begin() + endRowOffset; ++it) { + if(checkRow(*it, countCols) != 0) + return 2; + countRows++; + } + break; + } + } + + if(endColumn == -1 || endColumn > countCols) + endColumn = countCols; + + m_actualRows = countRows; + m_actualCols = endColumn - startColumn + 1 + createIndexEnabled + parseRowsName; + + if(parseColumnModes(firstRow, firstRowName) != 0) + return 2; + + DEBUG("start/end column: = " << startColumn << ' ' << endColumn); + DEBUG("start/end rows = " << startRow << ' ' << endRow); + DEBUG("actual cols/rows = " << m_actualCols << ' ' << m_actualRows); + + return 0; +} + +/*! +reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. +*/ +void JsonFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { + KFilterDev device(fileName); + readDataFromDevice(device, dataSource, importMode, lines); +} + +/*! +reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source. +*/ +void JsonFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { + if(!m_prepared) { + const int deviceError = prepareDeviceToRead(device); + if(deviceError != 0){ + DEBUG("Device error = " << deviceError); + return; + } + //TODO: support other modes and vector names + m_prepared = true; + } + importData(dataSource, importMode, lines); +} + +/*! +reads the content of document \c doc to the data source \c dataSource. Uses the settings defined in the data source. +*/ +void JsonFilterPrivate::readDataFromDocument(const QJsonDocument& doc, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { + if(!m_prepared) { + const int docError = prepareDocumentToRead(doc); + if(docError != 0){ + DEBUG("Document parse error = " << docError); + return; + } + //TODO: support other modes and vector names + m_prepared = true; + } + importData(dataSource, importMode, lines); +} + +/*! +import the content of document \c m_preparedDoc to the data source \c dataSource. Uses the settings defined in the data source. +*/ +void JsonFilterPrivate::importData(AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { + Q_UNUSED(lines) + + m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows, m_actualCols, vectorNames, columnModes); + int rowOffset = startRow - 1; + DEBUG("reading " << m_actualRows << " lines"); + for(int i = 0; i < m_actualRows; ++i) { + QString rowName; + QJsonValue row; + switch (containerType) { + case JsonFilter::Array: + row = *(m_preparedDoc.array().begin() + rowOffset + i); + break; + case JsonFilter::Object: + rowName = (m_preparedDoc.object().begin() + rowOffset + i).key(); + row = *(m_preparedDoc.object().begin() + rowOffset + i); + break; + } + + int colIndex = 0; + for(int n = 0; n < m_actualCols; ++n) { + if((createIndexEnabled || parseRowsName) && n == 0) { + if(createIndexEnabled) + static_cast*>(m_dataContainer[n])->operator[](i) = i + 1; + if(parseRowsName) + setValueFromString(n + createIndexEnabled, i, rowName); + n = n + createIndexEnabled + parseRowsName - 1; + continue; + } + QJsonValue value; + switch(rowType){ + //TODO: implement other value types + case QJsonValue::Array: { + value = *(row.toArray().begin() + colIndex); + break; + } + case QJsonValue::Object: { + value = *(row.toObject().begin() + colIndex); + break; + } + case QJsonValue::Double: + case QJsonValue::String: + case QJsonValue::Bool: + case QJsonValue::Null: + case QJsonValue::Undefined: + break; + } + + switch(value.type()) { + case QJsonValue::Double: + if(columnModes[n] == AbstractColumn::Numeric) + static_cast*>(m_dataContainer[n])->operator[](i) = value.toDouble(); + else + setEmptyValue(n, i + startRow - 1); + break; + case QJsonValue::String: + setValueFromString(n, i, value.toString()); + break; + case QJsonValue::Array: + case QJsonValue::Object: + case QJsonValue::Bool: + case QJsonValue::Null: + case QJsonValue::Undefined: + setEmptyValue(n, i + startRow - 1); + break; + } + colIndex++; + } + emit q->completed(100 * i/m_actualRows); + } + //TODO: fix (startColumn + m_actualCols - 1) + dataSource->finalizeImport(m_columnOffset, startColumn, startColumn + m_actualCols - 1, dateTimeFormat, importMode); +} + +/*! +generates the preview for the file \c fileName. +*/ +QVector JsonFilterPrivate::preview(const QString& fileName) { + KFilterDev device(fileName); + return preview(device); +} + +/*! +generates the preview for device \c device. +*/ +QVector JsonFilterPrivate::preview(QIODevice &device) { + const int deviceError = prepareDeviceToRead(device); + if (deviceError != 0) { + DEBUG("Device error = " << deviceError); + return QVector(); + } + + return preview(); +} + +/*! +generates the preview for document \c doc. +*/ +QVector JsonFilterPrivate::preview(QJsonDocument &doc) { + if(prepareDocumentToRead(doc) != 0) + return QVector(); + return preview(); +} + +/*! +generates the preview for document \c m_preparedDoc. +*/ +QVector JsonFilterPrivate::preview() { + QVector dataStrings; + int rowOffset = startRow - 1; + DEBUG("reading " << m_actualRows << " lines"); + for(int i = 0; i < m_actualRows; ++i) { + QString rowName; + QJsonValue row; + switch (containerType) { + case JsonFilter::Object: + rowName = (m_preparedDoc.object().begin() + rowOffset + i).key(); + row = *(m_preparedDoc.object().begin() + rowOffset + i); + break; + case JsonFilter::Array: + row = *(m_preparedDoc.array().begin() + rowOffset + i); + break; + } + + QStringList lineString; + int colIndex = 0; + for(int n = 0; n < m_actualCols; ++n) { + if((createIndexEnabled || parseRowsName) && n == 0) { + if(createIndexEnabled) + lineString += QString::number(i + 1); + if(parseRowsName) + lineString += rowName; + n = n + createIndexEnabled + parseRowsName - 1; + continue; + } + + QJsonValue value; + switch(rowType){ + case QJsonValue::Object: { + value = *(row.toObject().begin() + colIndex); + break; + } + case QJsonValue::Array: { + value = *(row.toArray().begin() + colIndex); + break; + } + //TODO: implement other value types + case QJsonValue::Double: + case QJsonValue::String: + case QJsonValue::Bool: + case QJsonValue::Null: + case QJsonValue::Undefined: + break; + } + switch(value.type()) { + case QJsonValue::Double: + if(columnModes[n] == AbstractColumn::Numeric) + lineString += QString::number(value.toDouble(), 'g', 16); + else + lineString += lineString += QLatin1String(""); + break; + case QJsonValue::String: { + //TODO: add parsing string before appending + lineString += value.toString(); + break; + } + case QJsonValue::Array: + case QJsonValue::Object: + case QJsonValue::Bool: + case QJsonValue::Null: + case QJsonValue::Undefined: + lineString += QLatin1String(""); + break; + } + colIndex++; + } + dataStrings << lineString; + emit q->completed(100 * i/m_actualRows); + } + return dataStrings; +} + +/*! +writes the content of \c dataSource to the file \c fileName. +*/ +void JsonFilterPrivate::write(const QString& fileName, AbstractDataSource* dataSource) { + Q_UNUSED(fileName); + Q_UNUSED(dataSource); + + //TODO: save data to json file +} + +//############################################################################## +//################## Serialization/Deserialization ########################### +//############################################################################## +/*! +Saves as XML. +*/ +void JsonFilter::save(QXmlStreamWriter* writer) const { + writer->writeStartElement("jsonFilter"); + writer->writeAttribute("rowType", QString::number(d->rowType)); + writer->writeAttribute("dateTimeFormat", d->dateTimeFormat); + writer->writeAttribute("numberFormat", QString::number(d->numberFormat)); + writer->writeAttribute("createIndex", QString::number(d->createIndexEnabled)); + writer->writeAttribute("parseRowsName", QString::number(d->parseRowsName)); + writer->writeAttribute("nanValue", QString::number(d->nanValue)); + writer->writeAttribute("startRow", QString::number(d->startRow)); + writer->writeAttribute("endRow", QString::number(d->endRow)); + writer->writeAttribute("startColumn", QString::number(d->startColumn)); + writer->writeAttribute("endColumn", QString::number(d->endColumn)); + + QStringList list; + for(auto it = modelRows().begin(); it != modelRows().end(); ++it){ + list.append(QString::number(*it)); + } + writer->writeAttribute("modelRows", list.join(';')); + + writer->writeEndElement(); + DEBUG("JsonFilter save params"); +} + +/*! +Loads from XML. +*/ +bool JsonFilter::load(XmlStreamReader* reader) { + if (!reader->isStartElement() || reader->name() != "jsonFilter") { + reader->raiseError(i18n("no json filter element found")); + return false; + } + QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); + QXmlStreamAttributes attribs = reader->attributes(); + + QString str = attribs.value("rowType").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'rowType'")); + else + d->rowType = static_cast(str.toInt()); + + str = attribs.value("dateTimeFormat").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'dateTimeFormat'")); + else + d->dateTimeFormat = str; + + str = attribs.value("numberFormat").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'numberFormat'")); + else + d->numberFormat = static_cast(str.toInt()); + + str = attribs.value("createIndex").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'createIndex'")); + else + d->createIndexEnabled = str.toInt(); + + str = attribs.value("parseRowsName").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'parseRowsName'")); + else + d->parseRowsName = str.toInt(); + + str = attribs.value("nanValue").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'nanValue'")); + else + d->nanValue = str.toDouble(); + + str = attribs.value("startRow").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'startRow'")); + else + d->startRow = str.toInt(); + + str = attribs.value("endRow").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'endRow'")); + else + d->endRow = str.toInt(); + + str = attribs.value("startColumn").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'startColumn'")); + else + d->startColumn = str.toInt(); + + str = attribs.value("endColumn").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'endColumn'")); + else + d->endColumn = str.toInt(); + + QStringList list = attribs.value("modelRows").toString().split(';'); + if (list.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'modelRows'")); + else{ + d->modelRows = QVector(); + for(auto it = list.begin(); it !=list.end(); ++it) + d->modelRows.append((*it).toInt()); + } + + DEBUG("JsonFilter load params"); + return true; +} diff --git a/src/backend/datasources/filters/JsonFilter.h b/src/backend/datasources/filters/JsonFilter.h new file mode 100644 index 000000000..322862858 --- /dev/null +++ b/src/backend/datasources/filters/JsonFilter.h @@ -0,0 +1,106 @@ +/*************************************************************************** + File : JsonFilter.h + Project : LabPlot + Description : JSON I/O-filter. + -------------------------------------------------------------------- + -------------------------------------------------------------------- + Copyright : (C) 2018 Andrey Cygankov (craftplace.ms@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 JSONFILTER_H +#define JSONFILTER_H + +#include "backend/datasources/filters/AbstractFileFilter.h" +#include "backend/core/AbstractColumn.h" + +#include + +class QStringList; +class QIODevice; +class QJsonDocument; +class QJsonModel; +class JsonFilterPrivate; + +class JsonFilter : public AbstractFileFilter { + Q_OBJECT + +public: + enum DataContainerType {Array, Object}; + + JsonFilter(); + ~JsonFilter() override; + + static QStringList dataTypes(); + static QStringList dataRowTypes(); + + // read data from any device + void readDataFromDevice(QIODevice& device, AbstractDataSource*, + AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); + // overloaded function to read from file + QVector readDataFromFile(const QString& fileName, AbstractDataSource* = nullptr, + AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1) override; + void write(const QString& fileName, AbstractDataSource*) override; + + QVector preview(const QString& fileName); + QVector preview(QIODevice& device); + QVector preview(QJsonDocument& doc); + + void loadFilterSettings(const QString&) override; + void saveFilterSettings(const QString&) const override; + + void setDataRowType(const QJsonValue::Type); + QJsonValue::Type dataRowType() const; + void setModelRows(const QVector); + QVector modelRows() const; + + void setDateTimeFormat(const QString&); + QString dateTimeFormat() const; + void setNumberFormat(QLocale::Language); + QLocale::Language numberFormat() const; + void setNaNValueToZero(const bool); + bool NaNValueToZeroEnabled() const; + void setCreateIndexEnabled(const bool); + void setParseRowsName(const bool); + + void setVectorNames(const QString&); + QStringList vectorNames() const; + QVector columnModes(); + + void setStartRow(const int); + int startRow() const; + void setEndRow(const int); + int endRow() const; + void setStartColumn(const int); + int startColumn() const; + void setEndColumn(const int); + int endColumn() const; + + void save(QXmlStreamWriter*) const override; + bool load(XmlStreamReader*) override; + +private: + std::unique_ptr const d; + friend class JsonFilterPrivate; +}; + +#endif diff --git a/src/backend/datasources/filters/JsonFilterPrivate.h b/src/backend/datasources/filters/JsonFilterPrivate.h new file mode 100644 index 000000000..fc48061d9 --- /dev/null +++ b/src/backend/datasources/filters/JsonFilterPrivate.h @@ -0,0 +1,97 @@ +/*************************************************************************** + File : JsonFilterPrivate.h + Project : LabPlot + Description : Private implementation class for JsonFilter. + -------------------------------------------------------------------- + -------------------------------------------------------------------- + Copyright : (C) 2018 Andrey Cygankov (craftplace.ms@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 JSONFILTERPRIVATE_H +#define JSONFILTERPRIVATE_H + +#include +#include "QJsonModel.h" +class KFilterDev; +class AbstractDataSource; +class AbstractColumn; + +class JsonFilterPrivate { + +public: + JsonFilterPrivate (JsonFilter* owner); + + int checkRow(QJsonValueRef value, int &countCols); + int parseColumnModes(QJsonValue row, QString rowName = ""); + void setEmptyValue(int column, int row); + void setValueFromString(int column, int row, QString value); + + int prepareDeviceToRead(QIODevice&); + int prepareDocumentToRead(const QJsonDocument&); + + void readDataFromDevice(QIODevice& device, AbstractDataSource* = nullptr, + AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); + void readDataFromFile(const QString& fileName, AbstractDataSource* = nullptr, + AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); + void readDataFromDocument(const QJsonDocument& doc, AbstractDataSource* = nullptr, + AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); + + void importData(AbstractDataSource* = nullptr, AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, + int lines = -1); + + void write(const QString& fileName, AbstractDataSource*); + QVector preview(const QString& fileName); + QVector preview(QIODevice& device); + QVector preview(QJsonDocument& doc); + QVector preview(); + + const JsonFilter* q; + QJsonModel* model; + + JsonFilter::DataContainerType containerType; + QJsonValue::Type rowType; + QVector modelRows; + + QString dateTimeFormat; + QLocale::Language numberFormat; + double nanValue; + bool createIndexEnabled; + bool parseRowsName; + QStringList vectorNames; + QVector columnModes; + + int startRow; // start row + int endRow; // end row + int startColumn; // start column + int endColumn; // end column + +private: + int m_actualRows; + int m_actualCols; + int m_prepared; + int m_columnOffset; // indexes the "start column" in the datasource. Data will be imported starting from this column. + QVector m_dataContainer; // pointers to the actual data containers (columns). + QJsonDocument m_preparedDoc; // parsed Json document +}; + +#endif diff --git a/src/backend/datasources/filters/QJsonModel.cpp b/src/backend/datasources/filters/QJsonModel.cpp new file mode 100644 index 000000000..aeb5ecc05 --- /dev/null +++ b/src/backend/datasources/filters/QJsonModel.cpp @@ -0,0 +1,338 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2011 SCHUTZ Sacha + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "QJsonModel.h" +#include +#include + +QJsonTreeItem::QJsonTreeItem(QJsonTreeItem* parent) : mParent(parent) { +} + +QJsonTreeItem::~QJsonTreeItem() { + qDeleteAll(mChilds); +} + +void QJsonTreeItem::appendChild(QJsonTreeItem* item) { + mChilds.append(item); +} + +QJsonTreeItem* QJsonTreeItem::child(int row) { + return mChilds.value(row); +} + +QJsonTreeItem* QJsonTreeItem::parent() { + return mParent; +} + +int QJsonTreeItem::childCount() const { + return mChilds.count(); +} + +int QJsonTreeItem::row() const { + if (mParent) + return mParent->mChilds.indexOf(const_cast(this)); + + return 0; +} + +void QJsonTreeItem::setKey(const QString& key) { + mKey = key; +} + +void QJsonTreeItem::setValue(const QString& value) { + mValue = value; +} + +void QJsonTreeItem::setType(const QJsonValue::Type type) { + mType = type; +} + +QString QJsonTreeItem::key() const { + return mKey; +} + +QString QJsonTreeItem::value() const { + return mValue; +} + +QJsonValue::Type QJsonTreeItem::type() const { + return mType; +} + +QJsonTreeItem* QJsonTreeItem::load(const QJsonValue& value, QJsonTreeItem* parent) { + QJsonTreeItem* rootItem = new QJsonTreeItem(parent); + rootItem->setKey("root"); + + if ( value.isObject()) { + + //Get all QJsonValue childs + for (QString key : value.toObject().keys()) { + QJsonValue v = value.toObject().value(key); + QJsonTreeItem* child = load(v,rootItem); + child->setKey(key); + child->setType(v.type()); + rootItem->appendChild(child); + } + } else if ( value.isArray()) { + //Get all QJsonValue childs + int index = 0; + for (QJsonValue v : value.toArray()) { + QJsonTreeItem* child = load(v,rootItem); + child->setKey(QString::number(index)); + child->setType(v.type()); + rootItem->appendChild(child); + ++index; + } + } else { + rootItem->setValue(value.toVariant().toString()); + rootItem->setType(value.type()); + } + + return rootItem; +} + +//========================================================================= + +QJsonModel::QJsonModel(QObject* parent) : QAbstractItemModel(parent), + mHeadItem(new QJsonTreeItem), + mRootItem(new QJsonTreeItem(mHeadItem)) { + + mHeadItem->appendChild(mRootItem); + mHeaders.append("key"); + mHeaders.append("value"); +} + +QJsonModel::~QJsonModel() { + //delete mRootItem; + delete mHeadItem; +} + +void QJsonModel::clear() { + beginResetModel(); + delete mHeadItem; + mHeadItem = new QJsonTreeItem; + mRootItem = new QJsonTreeItem(mHeadItem); + mHeadItem->appendChild(mRootItem); + endResetModel(); +} + +bool QJsonModel::load(const QString& fileName) { + QFile file(fileName); + bool success = false; + if (file.open(QIODevice::ReadOnly)) { + success = load(&file); + file.close(); + } else + success = false; + + return success; +} + +bool QJsonModel::load(QIODevice* device) { + return loadJson(device->readAll()); +} + +bool QJsonModel::loadJson(const QByteArray& json) { + auto const& jdoc = QJsonDocument::fromJson(json); + return loadJson(jdoc); +} + +bool QJsonModel::loadJson(const QJsonDocument& jdoc) { + if (!jdoc.isNull()) { + beginResetModel(); + delete mHeadItem; + + mHeadItem = new QJsonTreeItem; + + if (jdoc.isArray()) { + mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.array()), mHeadItem); + mRootItem->setType(QJsonValue::Array); + + } else { + mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.object()), mHeadItem); + mRootItem->setType(QJsonValue::Object); + } + + mHeadItem->appendChild(mRootItem); + + endResetModel(); + return true; + } + + qDebug()<(index.internalPointer()); + + if (role == Qt::DisplayRole) { + if (index.column() == 0) + return QString("%1").arg(item->key()); + + if (index.column() == 1) + return QString("%1").arg(item->value()); + } else if (Qt::EditRole == role) { + if (index.column() == 1) + return QString("%1").arg(item->value()); + } else if (role == Qt::DecorationRole) { + if (index.column() == 0) { + if (item->type() == QJsonValue::Array) + return QIcon::fromTheme("labplot-json-array"); + else if (item->type() == QJsonValue::Object) + return QIcon::fromTheme("labplot-json-object"); + } + return QIcon(); + } + + return QVariant(); +} + +bool QJsonModel::setData(const QModelIndex& index, const QVariant& value, int role) { + if (Qt::EditRole == role) { + if (index.column() == 1) { + QJsonTreeItem* item = static_cast(index.internalPointer()); + item->setValue(value.toString()); + emit dataChanged(index, index, {Qt::EditRole}); + return true; + } + } + + return false; +} + +QVariant QJsonModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + return mHeaders.value(section); + else + return QVariant(); +} + +QModelIndex QJsonModel::index(int row, int column, const QModelIndex& parent) const { + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + QJsonTreeItem* parentItem; + + if (!parent.isValid()) + parentItem = mHeadItem; + else + parentItem = static_cast(parent.internalPointer()); + + QJsonTreeItem* childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex QJsonModel::parent(const QModelIndex& index) const { + if (!index.isValid()) + return QModelIndex(); + + QJsonTreeItem* childItem = static_cast(index.internalPointer()); + QJsonTreeItem* parentItem = childItem->parent(); + + if (parentItem == mHeadItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +int QJsonModel::rowCount(const QModelIndex& parent) const { + QJsonTreeItem* parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = mHeadItem; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->childCount(); +} + +int QJsonModel::columnCount(const QModelIndex& parent) const { + Q_UNUSED(parent) + return 2; +} + +Qt::ItemFlags QJsonModel::flags(const QModelIndex& index) const { + if (index.column() == 1) + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); + else + return QAbstractItemModel::flags(index); +} + +QJsonDocument QJsonModel::json() const { + auto v = genJson(mRootItem); + QJsonDocument doc; + + if (v.isObject()) + doc = QJsonDocument(v.toObject()); + else + doc = QJsonDocument(v.toArray()); + + return doc; +} + +QJsonValue QJsonModel::genJson(QJsonTreeItem* item) const { + auto type = item->type(); + const int nchild = item->childCount(); + + if (QJsonValue::Object == type) { + QJsonObject jo; + for (int i = 0; i < nchild; ++i) { + auto ch = item->child(i); + auto key = ch->key(); + jo.insert(key, genJson(ch)); + } + return jo; + } else if (QJsonValue::Array == type) { + QJsonArray arr; + for (int i = 0; i < nchild; ++i) { + auto ch = item->child(i); + arr.append(genJson(ch)); + } + return arr; + } else { + QJsonValue va(item->value()); + return va; + } + +} + +QJsonDocument QJsonModel::genJsonByIndex(const QModelIndex& index) const { + if (!index.isValid()) + return QJsonDocument(); + + QJsonTreeItem* item = static_cast(index.internalPointer()); + return QJsonDocument::fromVariant(genJson(item).toVariant()); +} diff --git a/src/backend/datasources/filters/QJsonModel.h b/src/backend/datasources/filters/QJsonModel.h new file mode 100644 index 000000000..111cc9bf9 --- /dev/null +++ b/src/backend/datasources/filters/QJsonModel.h @@ -0,0 +1,95 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2011 SCHUTZ Sacha + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef QJSONMODEL_H +#define QJSONMODEL_H + +#include +#include +#include +#include +#include +#include + +class QJsonModel; +class QJsonItem; + +class QJsonTreeItem { +public: + QJsonTreeItem(QJsonTreeItem* parent = nullptr); + ~QJsonTreeItem(); + void appendChild(QJsonTreeItem*); + QJsonTreeItem* child(int row); + QJsonTreeItem* parent(); + int childCount() const; + int row() const; + void setKey(const QString& key); + void setValue(const QString& value); + void setType(const QJsonValue::Type type); + QString key() const; + QString value() const; + QJsonValue::Type type() const; + + static QJsonTreeItem* load(const QJsonValue& value, QJsonTreeItem* parent = nullptr); + +private: + QString mKey; + QString mValue; + QJsonValue::Type mType; + QList mChilds; + QJsonTreeItem* mParent; +}; + + +class QJsonModel : public QAbstractItemModel { + Q_OBJECT + +public: + explicit QJsonModel(QObject* parent = nullptr); + ~QJsonModel(); + void clear(); + bool load(const QString& fileName); + bool load(QIODevice*); + bool loadJson(const QByteArray& json); + bool loadJson(const QJsonDocument& jdoc); + QVariant data(const QModelIndex& index, int role) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex&) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex&) const override; + QJsonDocument json() const; + QJsonDocument genJsonByIndex(const QModelIndex&) const; + +private: + QJsonValue genJson(QJsonTreeItem*) const; + + QJsonTreeItem* mHeadItem; + QJsonTreeItem* mRootItem; + QStringList mHeaders; +}; + +#endif // QJSONMODEL_H diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp index 15d45bb0c..dfcd1179c 100644 --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -1,467 +1,469 @@ /*************************************************************************** File : ImportDialog.cc Project : LabPlot Description : import file data dialog -------------------------------------------------------------------- Copyright : (C) 2008-2018 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/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/core/Workbook.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/MainWin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportFileDialog \brief Dialog for importing data from a file. Embeds \c ImportFileWidget and provides the standard buttons. \ingroup kdefrontend */ ImportFileDialog::ImportFileDialog(MainWin* parent, bool liveDataSource, const QString& fileName) : ImportDialog(parent), m_importFileWidget(new ImportFileWidget(this, fileName)), m_showOptions(false) { vLayout->addWidget(m_importFileWidget); //dialog buttons QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Reset |QDialogButtonBox::Cancel); okButton = buttonBox->button(QDialogButtonBox::Ok); m_optionsButton = buttonBox->button(QDialogButtonBox::Reset); //we highjack the default "Reset" button and use if for showing/hiding the options okButton->setEnabled(false); //ok is only available if a valid container was selected vLayout->addWidget(buttonBox); //hide the data-source related widgets if (!liveDataSource) { setModel(); //TODO: disable for file data sources m_importFileWidget->hideDataSource(); } else m_importFileWidget->initializeAndFillPortsAndBaudRates(); //Signals/Slots connect(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")); m_importFileWidget->hideDataSource(); } else setWindowTitle(i18nc("@title:window", "Add New Live Data Source")); setWindowIcon(QIcon::fromTheme("document-import-database")); QTimer::singleShot(0, this, &ImportFileDialog::loadSettings); } void ImportFileDialog::loadSettings() { //restore saved settings QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); m_showOptions = conf.readEntry("ShowOptions", false); m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); m_importFileWidget->showOptions(m_showOptions); m_importFileWidget->loadSettings(); //do the signal-slot connections after all settings where loaded in import file widget and check the OK button after this connect(m_importFileWidget, SIGNAL(checkedFitsTableToMatrix(bool)), this, SLOT(checkOnFitsTableToMatrix(bool))); connect(m_importFileWidget, SIGNAL(fileNameChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(sourceTypeChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(hostChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(portChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(previewRefreshed()), this, SLOT(checkOkButton())); connect(m_optionsButton, SIGNAL(clicked()), this, SLOT(toggleOptions())); checkOkButton(); KWindowConfig::restoreWindowSize(windowHandle(), conf); } ImportFileDialog::~ImportFileDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); conf.writeEntry("ShowOptions", m_showOptions); if (cbPosition) conf.writeEntry("Position", cbPosition->currentIndex()); KWindowConfig::saveWindowSize(windowHandle(), conf); } /*! triggers data import to the live data source \c source */ void ImportFileDialog::importToLiveDataSource(LiveDataSource* source, QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importToLiveDataSource()"); m_importFileWidget->saveSettings(source); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(source->filter(), SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QTime timer; timer.start(); DEBUG(" Inital read()"); source->read(); statusBar->showMessage( i18n("Live data source created in %1 seconds.", (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); source->ready(); } /*! triggers data import to the currently selected data container */ void ImportFileDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importTo()"); QDEBUG(" cbAddTo->currentModelIndex() =" << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR in importTo(): No aspect available"); DEBUG(" cbAddTo->currentModelIndex().isValid() = " << cbAddTo->currentModelIndex().isValid()); DEBUG(" cbAddTo->currentModelIndex() row/column = " << cbAddTo->currentModelIndex().row() << ' ' << cbAddTo->currentModelIndex().column()); return; } if (m_importFileWidget->isFileEmpty()) { KMessageBox::information(0, i18n("No data to import."), i18n("No Data")); return; } QString fileName = m_importFileWidget->fileName(); AbstractFileFilter* filter = m_importFileWidget->currentFileFilter(); AbstractFileFilter::ImportMode mode = AbstractFileFilter::ImportMode(cbPosition->currentIndex()); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(filter, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 100); QTime timer; timer.start(); if (aspect->inherits("Matrix")) { DEBUG(" to Matrix"); Matrix* matrix = qobject_cast(aspect); filter->readDataFromFile(fileName, matrix, mode); } else if (aspect->inherits("Spreadsheet")) { DEBUG(" to Spreadsheet"); Spreadsheet* spreadsheet = qobject_cast(aspect); DEBUG(" Calling readDataFromFile()"); filter->readDataFromFile(fileName, spreadsheet, mode); } else if (aspect->inherits("Workbook")) { DEBUG(" to Workbook"); Workbook* 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; switch (fileType) { case AbstractFileFilter::HDF5: names = m_importFileWidget->selectedHDF5Names(); break; case AbstractFileFilter::NETCDF: names = m_importFileWidget->selectedNetCDFNames(); break; case AbstractFileFilter::ROOT: names = m_importFileWidget->selectedROOTNames(); break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::FITS: + case AbstractFileFilter::Json: case AbstractFileFilter::NgspiceRawAscii: break; // never reached, omit warning } int nrNames = names.size(), offset = sheets.size(); int start=0; if (mode == AbstractFileFilter::Replace) start=offset; // add additional sheets for (int i = start; i < nrNames; ++i) { Spreadsheet *spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); if (mode == AbstractFileFilter::Prepend) workbook->insertChildBefore(spreadsheet,sheets[0]); else workbook->addChild(spreadsheet); } if (mode != AbstractFileFilter::Append) offset = 0; // import to sheets sheets = workbook->children(); for (int i = 0; i < nrNames; ++i) { switch (fileType) { case AbstractFileFilter::HDF5: ((HDF5Filter*) filter)->setCurrentDataSetName(names[i]); break; case AbstractFileFilter::NETCDF: ((NetCDFFilter*) filter)->setCurrentVarName(names[i]); break; case AbstractFileFilter::ROOT: ((ROOTFilter*) filter)->setCurrentHistogram(names[i]); break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::FITS: + case AbstractFileFilter::Json: case AbstractFileFilter::NgspiceRawAscii: break; // never reached, omit warning } if (sheets[i+offset]->inherits("Matrix")) filter->readDataFromFile(fileName, qobject_cast(sheets[i+offset])); else if (sheets[i+offset]->inherits("Spreadsheet")) filter->readDataFromFile(fileName, qobject_cast(sheets[i+offset])); } } else { // single import file types // use active spreadsheet/matrix if present, else new spreadsheet Spreadsheet* spreadsheet = workbook->currentSpreadsheet(); Matrix* matrix = workbook->currentMatrix(); if (spreadsheet) filter->readDataFromFile(fileName, spreadsheet, mode); else if (matrix) filter->readDataFromFile(fileName, matrix, mode); else { spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); workbook->addChild(spreadsheet); filter->readDataFromFile(fileName, spreadsheet, mode); } } } statusBar->showMessage( i18n("File %1 imported in %2 seconds.", fileName, (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); delete filter; } void ImportFileDialog::toggleOptions() { m_importFileWidget->showOptions(!m_showOptions); m_showOptions = !m_showOptions; m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); //resize the dialog layout()->activate(); resize( QSize(this->width(), 0).expandedTo(minimumSize()) ); } void ImportFileDialog::checkOnFitsTableToMatrix(const bool enable) { if (cbAddTo) { QDEBUG("cbAddTo->currentModelIndex() = " << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR: no aspect available."); return; } if(aspect->inherits("Matrix")) { okButton->setEnabled(enable); 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 Matrix* matrix = dynamic_cast(aspect); m_importFileWidget->showAsciiHeaderOptions(matrix == NULL); } } 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 bool enable = !m_importFileWidget->serialPort().isEmpty(); if (enable) { QSerialPort* serialPort = new QSerialPort(this); serialPort->setBaudRate(m_importFileWidget->baudRate()); serialPort->setPortName(m_importFileWidget->serialPort()); bool serialPortOpened = serialPort->open(QIODevice::ReadOnly); okButton->setEnabled(serialPortOpened); if (serialPortOpened) okButton->setToolTip(i18n("Close the dialog and import the data.")); else okButton->setToolTip(i18n("Could not connect to the provided serial port.")); } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Serial port number is missing.")); } } } } QString ImportFileDialog::selectedObject() const { return m_importFileWidget->selectedObject(); } diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index 032887d3d..19c92302b 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,1220 +1,1275 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ImageFilter.h" #include "backend/datasources/filters/FITSFilter.h" +#include "backend/datasources/filters/JsonFilter.h" +#include "backend/datasources/filters/QJsonModel.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/ROOTFilter.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 /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_fileEmpty(false), m_liveDataSource(true), m_suppressRefresh(false) { ui.setupUi(this); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leFileName->setCompleter(completer); ui.cbFileType->addItems(AbstractFileFilter::fileTypes()); QStringList filterItems; filterItems << i18n("Automatic") << i18n("Custom"); ui.cbFilter->addItems(filterItems); // file type specific option widgets QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); ui.swOptions->insertWidget(AbstractFileFilter::Ascii, asciiw); QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->insertWidget(AbstractFileFilter::Binary, binaryw); QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->insertWidget(AbstractFileFilter::Image, imagew); QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->insertWidget(AbstractFileFilter::HDF5, hdf5w); QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->insertWidget(AbstractFileFilter::FITS, fitsw); QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->insertWidget(AbstractFileFilter::ROOT, rootw); + QWidget* jsonw = new QWidget(); + m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); + ui.swOptions->insertWidget(AbstractFileFilter::Json, jsonw); + + ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui.tvJson->setAlternatingRowColors(true); + ui.tvJson->setModel(m_jsonOptionsWidget->model()); + showJsonModel(false); + // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); QHBoxLayout* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // default filter ui.swOptions->setCurrentIndex(AbstractFileFilter::Ascii); #if !defined(HAVE_HDF5) || !defined(HAVE_NETCDF) || !defined(HAVE_FITS) || !defined(HAVE_ZIP) const QStandardItemModel* model = qobject_cast(ui.cbFileType->model()); #endif #ifndef HAVE_HDF5 // disable HDF5 item QStandardItem* item = model->item(AbstractFileFilter::HDF5); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_NETCDF // disable NETCDF item QStandardItem* item2 = model->item(AbstractFileFilter::NETCDF); item2->setFlags(item2->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_FITS // disable FITS item QStandardItem* item3 = model->item(AbstractFileFilter::FITS); item3->setFlags(item3->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_ZIP // disable ROOT item QStandardItem* item4 = model->item(AbstractFileFilter::ROOT); item4->setFlags(item4->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.bFileInfo->setIcon( QIcon::fromTheme("help-about") ); ui.bManageFilters->setIcon( QIcon::fromTheme("configure") ); ui.bSaveFilter->setIcon( QIcon::fromTheme("document-save") ); ui.bRefreshPreview->setIcon( QIcon::fromTheme("view-refresh") ); connect( ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString)) ); connect( ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile()) ); connect( ui.bFileInfo, SIGNAL(clicked()), this, SLOT (fileInfoDialog()) ); connect( ui.bSaveFilter, SIGNAL(clicked()), this, SLOT (saveFilter()) ); connect( ui.bManageFilters, SIGNAL(clicked()), this, SLOT (manageFilters()) ); connect( ui.cbFileType, SIGNAL(currentIndexChanged(int)), SLOT(fileTypeChanged(int)) ); connect( ui.cbUpdateType, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypeChanged(int))); connect( ui.cbReadingType, SIGNAL(currentIndexChanged(int)), this, SLOT(readingTypeChanged(int))); connect( ui.cbFilter, SIGNAL(activated(int)), SLOT(filterChanged(int)) ); connect( ui.bRefreshPreview, SIGNAL(clicked()), SLOT(refreshPreview()) ); connect(ui.leHost, SIGNAL(textChanged(QString)), this, SIGNAL(hostChanged())); connect(ui.lePort, SIGNAL(textChanged(QString)), this, SIGNAL(portChanged())); connect( ui.cbSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceTypeChanged(int))); + connect( ui.tvJson, SIGNAL(clicked(const QModelIndex&)), this, SLOT(refreshPreview())); + //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); //defer the loading of settings a bit in order to show the dialog prior to blocking the GUI in refreshPreview() QTimer::singleShot( 100, this, SLOT(loadSettings()) ); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //settings for data type specific widgets m_asciiOptionsWidget->loadSettings(); m_binaryOptionsWidget->loadSettings(); m_imageOptionsWidget->loadSettings(); + m_jsonOptionsWidget->loadSettings(); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings ui.cbFileType->setCurrentIndex(conf.readEntry("Type", 0)); ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed if (m_fileName.isEmpty()) ui.leFileName->setText(conf.readEntry("LastImportedFile", "")); else ui.leFileName->setText(m_fileName); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate").toInt()); ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType").toInt()); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType").toInt()); 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()); m_suppressRefresh = false; refreshPreview(); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", ui.cbFileType->currentIndex()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", ui.leFileName->text()); //live data related settings conf.writeEntry("SourceType", ui.cbSourceType->currentIndex()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("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()); // data type specific settings m_asciiOptionsWidget->saveSettings(); m_binaryOptionsWidget->saveSettings(); m_imageOptionsWidget->saveSettings(); } void ImportFileWidget::hideDataSource() { m_liveDataSource = false; ui.gbUpdateOptions->hide(); ui.chbLinkFile->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.cbUpdateType->hide(); ui.lUpdateType->hide(); ui.sbUpdateInterval->hide(); ui.lUpdateInterval->hide(); } void ImportFileWidget::showAsciiHeaderOptions(bool b) { m_asciiOptionsWidget->showAsciiHeaderOptions(b); } +void ImportFileWidget::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 ui.leFileName->text(); } QString ImportFileWidget::selectedObject() const { const QString& path = ui.leFileName->text(); //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-dimensinal formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedHDF5Names(); 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->selectedNetCDFNames(); 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->selectedROOTNames(); 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 = static_cast(ui.cbFileType->currentIndex()); LiveDataSource::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = static_cast(ui.cbSourceType->currentIndex()); LiveDataSource::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( ui.leFileName->text() ); source->setFileType(fileType); source->setFilter(this->currentFileFilter()); source->setSourceType(sourceType); source->setReadingType(readingType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); else source->setFileWatched(true); 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( ui.leFileName->text() ); source->setFileLinked( ui.chbLinkFile->isChecked() ); break; case LiveDataSource::SourceType::LocalSocket: source->setLocalSocketName(ui.leFileName->text()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; default: break; } } /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentIndex()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = static_cast(ui.cbFileType->currentIndex()); switch (fileType) { case AbstractFileFilter::Ascii: { DEBUG(" ASCII"); //TODO std::unique_ptr filter(new AsciiFilter()); AsciiFilter* filter = new AsciiFilter(); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings( ui.cbFilter->currentText() ); //save the data portion to import filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case AbstractFileFilter::Binary: { BinaryFilter* filter = new BinaryFilter(); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if ( ui.cbFilter->currentIndex() == 1 ) { //"custom" filter->setAutoModeEnabled(false); m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } case AbstractFileFilter::Image: { ImageFilter* filter = new ImageFilter(); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::HDF5: { HDF5Filter* filter = new HDF5Filter(); QStringList names = selectedHDF5Names(); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::NETCDF: { NetCDFFilter* filter = new NetCDFFilter(); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::FITS: { FITSFilter* filter = new FITSFilter(); filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } + case AbstractFileFilter::Json: { + JsonFilter* filter = new JsonFilter(); + 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()); + return filter; + } case AbstractFileFilter::ROOT: { ROOTFilter* filter = new ROOTFilter(); QStringList names = selectedROOTNames(); if (!names.isEmpty()) filter->setCurrentHistogram(names.first()); filter->setStartBin( m_rootOptionsWidget->startBin() ); filter->setEndBin( m_rootOptionsWidget->endBin() ); filter->setColumns( m_rootOptionsWidget->columns() ); return filter; } case AbstractFileFilter::NgspiceRawAscii: { NgspiceRawAsciiFilter* filter = new NgspiceRawAsciiFilter(); // filter->setStartRow( ui.sbStartRow->value() ); // filter->setEndRow( ui.sbEndRow->value() ); return filter; } } return 0; } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileWidget"); QString dir = conf.readEntry("LastDir", ""); QString path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); if (path.isEmpty()) //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("LastDir", newDir); } ui.leFileName->setText(path); //TODO: decide whether the selection of several files should be possible // QStringList filelist = QFileDialog::getOpenFileNames(this,i18n("Select one or more files to open")); // if (! filelist.isEmpty() ) // ui.leFileName->setText(filelist.join(";")); } /************** SLOTS **************************************************************/ /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { QString fileName = name; #ifndef HAVE_WINDOWS // make relative path if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif bool fileExists = QFile::exists(fileName); if (fileExists) ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.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(); m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_fitsOptionsWidget->clear(); + m_jsonOptionsWidget->clearModel(); m_rootOptionsWidget->clear(); emit fileNameChanged(); return; } if (currentSourceType() == LiveDataSource::FileOrPipe) { const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); switch(fileType) { case AbstractFileFilter::Ascii: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Ascii); break; case AbstractFileFilter::Binary: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Binary); break; case AbstractFileFilter::Image: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Image); break; case AbstractFileFilter::HDF5: ui.cbFileType->setCurrentIndex(AbstractFileFilter::HDF5); m_hdf5OptionsWidget->updateContent((HDF5Filter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::NETCDF: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NETCDF); m_netcdfOptionsWidget->updateContent((NetCDFFilter*)this->currentFileFilter(), fileName); break; #ifdef HAVE_FITS case AbstractFileFilter::FITS: ui.cbFileType->setCurrentIndex(AbstractFileFilter::FITS); m_fitsOptionsWidget->updateContent((FITSFilter*)this->currentFileFilter(), fileName); break; #endif + case AbstractFileFilter::Json: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::Json); + m_jsonOptionsWidget->loadDocument(fileName); + break; case AbstractFileFilter::ROOT: ui.cbFileType->setCurrentIndex(AbstractFileFilter::ROOT); m_rootOptionsWidget->updateContent((ROOTFilter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::NgspiceRawAscii: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawAscii); break; default: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Binary); } } refreshPreview(); emit fileNameChanged(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined fllter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int fileType) { ui.swOptions->setCurrentIndex(fileType); //default ui.lFilter->show(); ui.cbFilter->show(); //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->count(); ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); 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.lPreviewLines->hide(); ui.sbPreviewLines->hide(); ui.lFilter->hide(); ui.cbFilter->hide(); break; case AbstractFileFilter::NgspiceRawAscii: 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"); } m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_rootOptionsWidget->clear(); int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); refreshPreview(); } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedHDF5Names(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNetCDFNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedFITSExtensions(); } const QStringList ImportFileWidget::selectedROOTNames() const { return m_rootOptionsWidget->selectedROOTNames(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = ui.leFileName->text().split(';'); FileInfoDialog* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats if (ui.cbFileType->currentIndex() == AbstractFileFilter::HDF5 || ui.cbFileType->currentIndex() == AbstractFileFilter::NETCDF || ui.cbFileType->currentIndex() == AbstractFileFilter::Image || ui.cbFileType->currentIndex() == AbstractFileFilter::FITS || + ui.cbFileType->currentIndex() == AbstractFileFilter::Json || ui.cbFileType->currentIndex() == AbstractFileFilter::ROOT) { 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; WAIT_CURSOR; QString fileName = ui.leFileName->text(); #ifndef HAVE_WINDOWS if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif DEBUG("refreshPreview(): file name = " << fileName.toStdString()); QVector importedStrings; AbstractFileFilter::FileType fileType = (AbstractFileFilter::FileType)ui.cbFileType->currentIndex(); // generic table widget - if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary) + if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary || fileType == AbstractFileFilter::Json) m_twPreview->show(); else m_twPreview->hide(); int lines = ui.sbPreviewLines->value(); bool ok = true; QTableWidget* tmpTableWidget{nullptr}; QStringList vectorNameList; QVector columnModes; DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); switch (fileType) { case AbstractFileFilter::Ascii: { ui.tePreview->clear(); AsciiFilter* filter = static_cast(this->currentFileFilter()); DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, currentSourceType())); switch (currentSourceType()) { 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}; sPort.setBaudRate(baudRate()); sPort.setPortName(serialPort()); if (sPort.open(QIODevice::ReadOnly)) { bool canread = sPort.waitForReadyRead(500); if (canread) importedStrings = filter->preview(sPort); sPort.close(); } break; } } tmpTableWidget = m_twPreview; vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::Binary: { ui.tePreview->clear(); BinaryFilter *filter = (BinaryFilter *)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; break; } case AbstractFileFilter::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case AbstractFileFilter::HDF5: { HDF5Filter *filter = (HDF5Filter *)this->currentFileFilter(); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, NULL, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case AbstractFileFilter::NETCDF: { NetCDFFilter *filter = (NetCDFFilter *)this->currentFileFilter(); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, NULL, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case AbstractFileFilter::FITS: { FITSFilter* filter = (FITSFilter*)this->currentFileFilter(); lines = m_fitsOptionsWidget->lines(); // update file name (may be any file type) m_fitsOptionsWidget->updateContent(filter, fileName); 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(); + m_jsonOptionsWidget->loadDocument(fileName); + JsonFilter *filter = (JsonFilter*)this->currentFileFilter(); + m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); + importedStrings = filter->preview(fileName); + tmpTableWidget = m_twPreview; + columnModes = filter->columnModes(); + break; + } case AbstractFileFilter::ROOT: { ROOTFilter *filter = (ROOTFilter *)this->currentFileFilter(); lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNBins(filter->binsInCurrentHistogram(fileName)); importedStrings = filter->previewCurrentHistogram( fileName, m_rootOptionsWidget->startBin(), qMin(m_rootOptionsWidget->startBin() + m_rootOptionsWidget->lines() - 1, m_rootOptionsWidget->endBin()) ); 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(); NgspiceRawAsciiFilter* filter = (NgspiceRawAsciiFilter*)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if( !importedStrings.isEmpty() ) { //QDEBUG("importedStrings =" << importedStrings); if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); QTableWidgetItem* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { QDEBUG(importedStrings[i]); int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); // new if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { QTableWidgetItem* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else { m_fileEmpty = true; } emit previewRefreshed(); RESET_CURSOR; } void ImportFileWidget::updateTypeChanged(int idx) { LiveDataSource::UpdateType type = static_cast(idx); switch (type) { 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) { LiveDataSource::ReadingType type = static_cast(idx); if (type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } if (type == LiveDataSource::ReadingType::WholeFile) { ui.lKeepLastValues->hide(); ui.sbKeepNValues->hide(); } else { ui.lKeepLastValues->show(); ui.sbKeepNValues->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { LiveDataSource::SourceType type = static_cast(idx); switch (type) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); ui.leFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); ui.chbLinkFile->show(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); fileNameChanged(ui.leFileName->text()); break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); ui.leFileName->show(); ui.bOpen->show(); ui.bFileInfo->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); break; } // "whole file" item const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); if (type == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); //"update options" groupbox can be deactived for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. if (type != LiveDataSource::SourceType::FileOrPipe) ui.gbUpdateOptions->setEnabled(true); emit sourceTypeChanged(); refreshPreview(); } void ImportFileWidget::initializeAndFillPortsAndBaudRates() { for (int i = 2; i < ui.swOptions->count(); ++i) ui.swOptions->removeWidget(ui.swOptions->widget(i)); const int size = ui.cbFileType->count(); for (int i = 2; i < size; ++i) ui.cbFileType->removeItem(2); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); } diff --git a/src/kdefrontend/datasources/ImportFileWidget.h b/src/kdefrontend/datasources/ImportFileWidget.h index 0e92772b9..c59aaa982 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.h +++ b/src/kdefrontend/datasources/ImportFileWidget.h @@ -1,122 +1,127 @@ /*************************************************************************** File : ImportFileWidget.h Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2017 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) Copyright : (C) 2009-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef IMPORTFILEWIDGET_H #define IMPORTFILEWIDGET_H #include "ui_importfilewidget.h" #include "backend/datasources/LiveDataSource.h" #include class AbstractFileFilter; class AsciiOptionsWidget; class BinaryOptionsWidget; class HDF5OptionsWidget; class ImageOptionsWidget; class NetCDFOptionsWidget; class FITSOptionsWidget; +class JsonOptionsWidget; class ROOTOptionsWidget; class QTableWidget; class ImportFileWidget : public QWidget { Q_OBJECT public: explicit ImportFileWidget(QWidget*, const QString& fileName = QString()); ~ImportFileWidget(); void showOptions(bool); void saveSettings(LiveDataSource*) const; AbstractFileFilter::FileType currentFileType() const; LiveDataSource::SourceType currentSourceType() const; AbstractFileFilter* currentFileFilter() const; QString fileName() const; QString selectedObject() const; bool isFileEmpty() const; const QStringList selectedHDF5Names() const; const QStringList selectedNetCDFNames() const; const QStringList selectedFITSExtensions() const; const QStringList selectedROOTNames() const; void hideDataSource(); void showAsciiHeaderOptions(bool); + void showJsonModel(bool); QString host() const; QString port() const; QString serialPort() const; int baudRate() const; void initializeAndFillPortsAndBaudRates(); private: Ui::ImportFileWidget ui; std::unique_ptr m_asciiOptionsWidget; std::unique_ptr m_binaryOptionsWidget; std::unique_ptr m_hdf5OptionsWidget; std::unique_ptr m_imageOptionsWidget; std::unique_ptr m_netcdfOptionsWidget; std::unique_ptr m_fitsOptionsWidget; + std::unique_ptr m_jsonOptionsWidget; std::unique_ptr m_rootOptionsWidget; + QTableWidget* m_twPreview; const QString& m_fileName; bool m_fileEmpty; bool m_liveDataSource; bool m_suppressRefresh; public slots: void loadSettings(); private slots: void fileNameChanged(const QString&); void fileTypeChanged(int); void updateTypeChanged(int); void sourceTypeChanged(int); void readingTypeChanged(int); void saveFilter(); void manageFilters(); void filterChanged(int); void selectFile(); void fileInfoDialog(); void refreshPreview(); signals: void fileNameChanged(); void sourceTypeChanged(); void hostChanged(); void portChanged(); void previewRefreshed(); void checkedFitsTableToMatrix(const bool enable); friend class HDF5OptionsWidget; // to access refreshPreview() friend class NetCDFOptionsWidget; // to access refreshPreview() and others friend class FITSOptionsWidget; + friend class JsonOptionsWidget; friend class ROOTOptionsWidget; // to access refreshPreview() and others }; #endif diff --git a/src/kdefrontend/datasources/JsonOptionsWidget.cpp b/src/kdefrontend/datasources/JsonOptionsWidget.cpp new file mode 100644 index 000000000..33bdd3850 --- /dev/null +++ b/src/kdefrontend/datasources/JsonOptionsWidget.cpp @@ -0,0 +1,195 @@ +/*************************************************************************** + File : JsonOptionsWidget.cpp + Project : LabPlot + Description : Widget providing options for the import of json data. + -------------------------------------------------------------------- + -------------------------------------------------------------------- + Copyright : (C) 2018 Andrey Cygankov (craftplace.ms@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 "JsonOptionsWidget.h" +#include "backend/datasources/filters/QJsonModel.h" +#include "backend/datasources/filters/AbstractFileFilter.h" +#include "backend/datasources/filters/JsonFilter.h" + +#include +#include +#include +#include + +/*! +\class JsonOptionsWidget +\brief Widget providing options for the import of json data + +\ingroup kdefrontend +*/ +JsonOptionsWidget::JsonOptionsWidget(QWidget* parent, ImportFileWidget* fileWidget) : QWidget(parent), + m_fileWidget(fileWidget), + m_model(new QJsonModel()) { + + ui.setupUi(parent); + + ui.cbNumberFormat->addItems(AbstractFileFilter::numberFormats()); + ui.cbDateTimeFormat->addItems(AbstractColumn::dateTimeFormats()); + + setTooltips(); +} + +void JsonOptionsWidget::applyFilterSettings(JsonFilter* filter, const QModelIndex& index) const { + Q_ASSERT(filter); + + filter->setModelRows(getIndexRows(index)); + filter->setNumberFormat( QLocale::Language(ui.cbNumberFormat->currentIndex())); + filter->setDateTimeFormat(ui.cbDateTimeFormat->currentText()); + filter->setCreateIndexEnabled(ui.chbCreateIndex->isChecked()); + filter->setNaNValueToZero(ui.chbConvertNaNToZero->isChecked()); + filter->setParseRowsName(ui.chbParseRowsName->isChecked()); + + //TODO: change this after implementation other row types + filter->setDataRowType(QJsonValue::Array); + if(!index.isValid()) return; + QJsonTreeItem *item = static_cast(index.internalPointer()); + if(item->childCount() < 1) return; + filter->setDataRowType(item->child(0)->type()); +} + +void JsonOptionsWidget::clearModel() { + m_model->clear(); +} + +void JsonOptionsWidget::loadSettings() const { + KConfigGroup conf(KSharedConfig::openConfig(), "ImportJson"); + + ui.cbNumberFormat->setCurrentIndex(conf.readEntry("NumberFormat", (int)QLocale::AnyLanguage)); + ui.cbDateTimeFormat->setCurrentItem(conf.readEntry("DateTimeFormat", "yyyy-MM-dd hh:mm:ss.zzz")); + ui.chbCreateIndex->setChecked(conf.readEntry("CreateIndex", false)); + ui.chbConvertNaNToZero->setChecked(conf.readEntry("ConvertNaNToZero", false)); + ui.chbParseRowsName->setChecked(conf.readEntry("ParseRowsName", false)); +} + +void JsonOptionsWidget::saveSettings() { + KConfigGroup conf(KSharedConfig::openConfig(), "ImportJson"); + + conf.writeEntry("NumberFormat", ui.cbNumberFormat->currentIndex()); + conf.writeEntry("DateTimeFormat", ui.cbDateTimeFormat->currentText()); + conf.writeEntry("CreateIndex", ui.chbCreateIndex->isChecked()); + conf.writeEntry("ConvertNaNToZero", ui.chbConvertNaNToZero->isChecked()); + conf.writeEntry("ParseRowsName", ui.chbParseRowsName->isChecked()); +} + +void JsonOptionsWidget::loadDocument(QString filename) { + if(m_filename == filename) return; + else + m_filename = filename; + + KFilterDev device(m_filename); + if (!device.open(QIODevice::ReadOnly)) + return; + + if (device.atEnd() && !device.isSequential()) // empty file + return; + if(!m_model->loadJson(device.readAll())) + m_model->clear(); +} + +QJsonModel* JsonOptionsWidget::model() { + return m_model; +} + +void JsonOptionsWidget::setTooltips() { + const QString textNumberFormatShort = i18n("This option determines how the imported strings have to be converted to numbers."); + const QString textNumberFormat = textNumberFormatShort + "

" + i18n( + "For 'C Format', a period is used for the decimal point character and comma is used for the thousands group separator. " + "Valid number representations are:" + "
    " + "
  • 1234.56
  • " + "
  • 1,234.56
  • " + "
  • etc.
  • " + "
" + "When using 'System locale', the system settings will be used. " + "E.g., for the German local the valid number representations are:" + "
    " + "
  • 1234,56
  • " + "
  • 1.234,56
  • " + "
  • etc.
  • " + "
" + ); + + ui.lNumberFormat->setToolTip(textNumberFormatShort); + ui.lNumberFormat->setWhatsThis(textNumberFormat); + ui.cbNumberFormat->setToolTip(textNumberFormatShort); + ui.cbNumberFormat->setWhatsThis(textNumberFormat); + + const QString textDateTimeFormatShort = i18n("This option determines how the imported strings have to be converted to calendar date, i.e. year, month, and day numbers in the Gregorian calendar and to time."); + const QString textDateTimeFormat = textDateTimeFormatShort + "

" + i18n( + "Expressions that may be used for the date part of format string:" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
dthe day as number without a leading zero (1 to 31).
ddthe day as number with a leading zero (01 to 31).
dddthe abbreviated localized day name (e.g. 'Mon' to 'Sun'). Uses the system locale to localize the name.
ddddthe long localized day name (e.g. 'Monday' to 'Sunday'). Uses the system locale to localize the name.
Mthe month as number without a leading zero (1 to 12).
MMthe month as number with a leading zero (01 to 12).
MMMthe abbreviated localized month name (e.g. 'Jan' to 'Dec'). Uses the system locale to localize the name.
MMMMthe long localized month name (e.g. 'January' to 'December'). Uses the system locale to localize the name.
yythe year as two digit number (00 to 99).
yyyythe year as four digit number. If the year is negative, a minus sign is prepended in addition.


" + "Expressions that may be used for the time part of the format string:" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
hthe hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display)
hhthe hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display)
Hthe hour without a leading zero (0 to 23, even with AM/PM display)
HHthe hour with a leading zero (00 to 23, even with AM/PM display)
mthe minute without a leading zero (0 to 59)
mmthe minute with a leading zero (00 to 59)
sthe second without a leading zero (0 to 59)
ssthe second with a leading zero (00 to 59)
zthe milliseconds without leading zeroes (0 to 999)
zzzthe milliseconds with leading zeroes (000 to 999)
AP or Ainterpret as an AM/PM time. AP must be either 'AM' or 'PM'.
ap or aInterpret as an AM/PM time. ap must be either 'am' or 'pm'.


" + "Examples are:" + "" + "" + "" + "" + "
dd.MM.yyyy20.07.1969
ddd MMMM d yySun July 20 69
'The day is' ddddThe day is Sunday
"); + + ui.lDateTimeFormat->setToolTip(textDateTimeFormatShort); + ui.lDateTimeFormat->setWhatsThis(textDateTimeFormat); + ui.cbDateTimeFormat->setToolTip(textDateTimeFormatShort); + ui.cbDateTimeFormat->setWhatsThis(textDateTimeFormat); +} + +QVector JsonOptionsWidget::getIndexRows(const QModelIndex &index) const { + QVector rows; + QModelIndex current = index; + while(current.isValid()){ + rows.prepend(current.row()); + current = current.parent(); + } + return rows; +} diff --git a/src/kdefrontend/datasources/JsonOptionsWidget.h b/src/kdefrontend/datasources/JsonOptionsWidget.h new file mode 100644 index 000000000..a7366e63b --- /dev/null +++ b/src/kdefrontend/datasources/JsonOptionsWidget.h @@ -0,0 +1,62 @@ +/*************************************************************************** + File : JsonOptionsWidget.h + Project : LabPlot + Description : Widget providing options for the import of json data. + -------------------------------------------------------------------- + -------------------------------------------------------------------- + Copyright : (C) 2018 Andrey Cygankov (craftplace.ms@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 JSONOPTIONSWIDGET_H +#define JSONOPTIONSWIDGET_H + +#include "ui_jsonoptionswidget.h" + +class ImportFileWidget; +class JsonFilter; +class QJsonModel; +class QJsonTreeItem; + +class JsonOptionsWidget : public QWidget { + Q_OBJECT + +public: + explicit JsonOptionsWidget(QWidget*, ImportFileWidget*); + void applyFilterSettings(JsonFilter*, const QModelIndex&) const; + void clearModel(); + void loadSettings() const; + void saveSettings(); + void loadDocument(QString filename); + QJsonModel* model(); + +private: + void setTooltips(); + QVector getIndexRows(const QModelIndex&) const; + + QString m_filename; + Ui::JsonOptionsWidget ui; + ImportFileWidget* m_fileWidget; + QJsonModel* m_model; +}; + +#endif diff --git a/src/kdefrontend/ui/datasources/importfilewidget.ui b/src/kdefrontend/ui/datasources/importfilewidget.ui index 17f04e62f..4bd2e756e 100644 --- a/src/kdefrontend/ui/datasources/importfilewidget.ui +++ b/src/kdefrontend/ui/datasources/importfilewidget.ui @@ -1,725 +1,766 @@ ImportFileWidget 0 0 679 - 837 + 1045 0 0 - + + 0 + + + 0 + + + 0 + + 0 0 0 Data Source - - + + + + + File or named pipe + + + + + Network TCP socket + + + + + Network UDP socket + + + + + Local socket + + + + + Serial port + + + + + + + + Source: + + + + + false + + Save the current filter settings + - - + + + + false + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + Manage filters + - Filter: + + + + + + + + Host: + + + + + + + false + + + Type: - - - - Qt::Vertical + + + + + 0 + 0 + - - QSizePolicy::Fixed + + Select the file to import - - - 20 - 13 - + + - + - - + + - Baud rate: + Name: - - - - - - - - + + Port: - - - - false + + + + Specify the name of the file to import. - - - 0 - 0 - + + true - - - - - - - - + + - Host: + Filter: Qt::Vertical QSizePolicy::Fixed 20 10 + + + + + + false 0 0 Show file info - - - - - 0 - 0 - + + + + Qt::Vertical - - Select the file to import + + QSizePolicy::Fixed - - + + + 20 + 13 + - + - - - - Specify the name of the file to import. - - - true - - + + - - + + - Name: + Port - - - - - File or Named Pipe - - - - - Network TCP Socket - - - - - Network UDP Socket - - - - - Local Socket - - - - - Serial Port - - - - - - + + - Port: + Baud rate: - - - - false + + + + QAbstractItemView::NoEditTriggers - - Save the current filter settings - - - - - - + false - - - 0 - 0 - - - - Manage filters - - - + + + 16 + 16 + - - + + - Source: + Field: false 0 0 Format Options - + + 0 + + + 0 + + + 0 + + 0 0 0 0 Data format 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 Update interval: 0 0 ms 5 60000 1000 1 10000 1 Sample rate: Read: 0 0 Periodically On New Data If this option is checked, only the link to the file is stored in the project file but not it's content. Link the file true Keep last values: Update: Continuously Fixed From End Till the End All 999999 KComboBox QComboBox
kcombobox.h
diff --git a/src/kdefrontend/ui/datasources/jsonoptionswidget.ui b/src/kdefrontend/ui/datasources/jsonoptionswidget.ui new file mode 100644 index 000000000..8fabb6ec8 --- /dev/null +++ b/src/kdefrontend/ui/datasources/jsonoptionswidget.ui @@ -0,0 +1,101 @@ + + + JsonOptionsWidget + + + + 0 + 0 + 469 + 499 + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + true + + + + + + + Create index column + + + + + + + + + + Number format + + + + + + + Convert NaN to 0 + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 44 + + + + + + + + DateTime format + + + + + + + Parse rows name + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+
+ + +
diff --git a/tests/import_export/CMakeLists.txt b/tests/import_export/CMakeLists.txt index 242cc894a..82fafb58b 100644 --- a/tests/import_export/CMakeLists.txt +++ b/tests/import_export/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(ASCII) +add_subdirectory(JSON) add_subdirectory(project) diff --git a/tests/import_export/JSON/CMakeLists.txt b/tests/import_export/JSON/CMakeLists.txt new file mode 100644 index 000000000..fad09fc09 --- /dev/null +++ b/tests/import_export/JSON/CMakeLists.txt @@ -0,0 +1,35 @@ +add_executable (jsonfiltertest JsonFilterTest.cpp) + +target_link_libraries(jsonfiltertest Qt5::Test) +target_link_libraries(jsonfiltertest KF5::Archive KF5::XmlGui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) + +IF (Qt5SerialPort_FOUND) + target_link_libraries(jsonfiltertest Qt5::SerialPort ) +ENDIF () +IF (KF5SyntaxHighlighting_FOUND) + target_link_libraries(jsonfiltertest KF5::SyntaxHighlighting ) +ENDIF () +#TODO: KF5::NewStuff + +IF (CANTOR_LIBS_FOUND) + target_link_libraries(jsonfiltertest ${CANTOR_LIBS} ) +ENDIF () +IF (HDF5_FOUND) + target_link_libraries(jsonfiltertest ${HDF5_C_LIBRARIES} ) +ENDIF () +IF (FFTW_FOUND) + target_link_libraries(jsonfiltertest ${FFTW_LIBRARIES} ) +ENDIF () +IF (NETCDF_FOUND) + target_link_libraries(jsonfiltertest ${NETCDF_LIBRARY} ) +ENDIF () +IF (CFITSIO_FOUND) + target_link_libraries(jsonfiltertest ${CFITSIO_LIBRARY} ) +ENDIF () +IF (USE_LIBORIGIN) + target_link_libraries(jsonfiltertest liborigin-static ) +ENDIF () + +target_link_libraries(jsonfiltertest labplot2lib) + +add_test(NAME jsonfiltertest COMMAND jsonfiltertest) diff --git a/tests/import_export/JSON/JsonFilterTest.cpp b/tests/import_export/JSON/JsonFilterTest.cpp new file mode 100644 index 000000000..6794fb3f3 --- /dev/null +++ b/tests/import_export/JSON/JsonFilterTest.cpp @@ -0,0 +1,86 @@ +#include "JsonFilterTest.h" +#include "backend/datasources/filters/JsonFilter.h" +#include "backend/spreadsheet/Spreadsheet.h" + +void JsonFilterTest::initTestCase() { + const QString currentDir = __FILE__; + m_dataDir = currentDir.left(currentDir.lastIndexOf(QDir::separator())) + QDir::separator() + QLatin1String("data") + QDir::separator(); + + // needed in order to have the signals triggered by SignallingUndoCommand, see LabPlot.cpp + //TODO: redesign/remove this + qRegisterMetaType("const AbstractAspect*"); + qRegisterMetaType("const AbstractColumn*"); +} + +void JsonFilterTest::testArrayImport() { + Spreadsheet spreadsheet(0, "test", false); + JsonFilter filter; + + const QString fileName = m_dataDir + "array.json"; + AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; + filter.setModelRows(QVector()); + filter.setCreateIndexEnabled(true); + filter.setDataRowType(QJsonValue::Array); + filter.readDataFromFile(fileName, &spreadsheet, mode); + + QCOMPARE(spreadsheet.columnCount(), 3); + QCOMPARE(spreadsheet.rowCount(), 3); + QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); + QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Text); + QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); + + QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); + QCOMPARE(spreadsheet.column(0)->integerAt(1), 2); + QCOMPARE(spreadsheet.column(0)->integerAt(2), 3); + + QCOMPARE(spreadsheet.column(1)->textAt(0), "2018-06-01"); + QCOMPARE(spreadsheet.column(1)->textAt(1), "2018-06-02"); + QCOMPARE(spreadsheet.column(1)->textAt(2), "2018-06-03"); + + QCOMPARE(spreadsheet.column(2)->valueAt(0), 0.01); + QCOMPARE(spreadsheet.column(2)->valueAt(1), 0.02); + QCOMPARE(spreadsheet.column(2)->valueAt(2), 0.03); + +} + +void JsonFilterTest::testObjectImport() { + Spreadsheet spreadsheet(0, "test", false); + JsonFilter filter; + + const QString fileName = m_dataDir + "object.json"; + AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; + filter.setModelRows(QVector()); + filter.setCreateIndexEnabled(true); + filter.setDataRowType(QJsonValue::Object); + filter.readDataFromFile(fileName, &spreadsheet, mode); + + QCOMPARE(spreadsheet.columnCount(), 5); + QCOMPARE(spreadsheet.rowCount(), 3); + QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); + QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); + QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); + QCOMPARE(spreadsheet.column(3)->columnMode(), AbstractColumn::Numeric); + QCOMPARE(spreadsheet.column(4)->columnMode(), AbstractColumn::Text); + + QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); + QCOMPARE(spreadsheet.column(0)->integerAt(1), 2); + QCOMPARE(spreadsheet.column(0)->integerAt(2), 3); + + QCOMPARE(spreadsheet.column(1)->valueAt(0), 1.234); + QCOMPARE(spreadsheet.column(1)->valueAt(1), 111.); + QCOMPARE(spreadsheet.column(1)->valueAt(2), 0.001); + + QCOMPARE(spreadsheet.column(2)->valueAt(0), 2.345); + QCOMPARE(spreadsheet.column(2)->valueAt(1), 222); + QCOMPARE(spreadsheet.column(2)->valueAt(2), 0.002); + + QCOMPARE(spreadsheet.column(3)->valueAt(0), 3.456); + QCOMPARE(spreadsheet.column(3)->valueAt(1), 333); + QCOMPARE(spreadsheet.column(3)->valueAt(2), 0.003); + + QCOMPARE(spreadsheet.column(4)->textAt(0), "field1"); + QCOMPARE(spreadsheet.column(4)->textAt(1), "field2"); + QCOMPARE(spreadsheet.column(4)->textAt(2), "field3"); +} + +QTEST_MAIN(JsonFilterTest) \ No newline at end of file diff --git a/tests/import_export/JSON/JsonFilterTest.h b/tests/import_export/JSON/JsonFilterTest.h new file mode 100644 index 000000000..a93241ed3 --- /dev/null +++ b/tests/import_export/JSON/JsonFilterTest.h @@ -0,0 +1,19 @@ +#ifndef JSONFILTERTEST_H +#define JSONFILTERTEST_H + +#include + +class JsonFilterTest : public QObject { + Q_OBJECT + +private slots: + void initTestCase(); + + void testArrayImport(); + void testObjectImport(); +private: + QString m_dataDir; +}; + + +#endif diff --git a/tests/import_export/JSON/data/array.json b/tests/import_export/JSON/data/array.json new file mode 100644 index 000000000..5a15d0d45 --- /dev/null +++ b/tests/import_export/JSON/data/array.json @@ -0,0 +1,14 @@ +[ + [ + "2018-06-01", + 0.01 + ], + [ + "2018-06-02", + 0.02 + ], + [ + "2018-06-03", + 0.03 + ] +] \ No newline at end of file diff --git a/tests/import_export/JSON/data/object.json b/tests/import_export/JSON/data/object.json new file mode 100644 index 000000000..d71fe8ec6 --- /dev/null +++ b/tests/import_export/JSON/data/object.json @@ -0,0 +1,20 @@ +{ + "field1": { + "1": "1.234", + "2": "2.345", + "3": "3.456", + "4": "field1" + }, + "field2": { + "1": "111", + "2": "222", + "3": "333", + "4": "field2" + }, + "field3": { + "1": "0.001", + "2": "0.002", + "3": "0.003", + "4": "field3" + } +} \ No newline at end of file