diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,6 @@ set(QT_MINIMUM_VERSION 5.6.0) set(KF5_MIN_VERSION "5.16.0") -set(APPLE_SUPPRESS_X11_WARNING ON) - find_package(ECM 1.3.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) # build type: "release", "debug", "debugfull" @@ -206,13 +204,6 @@ ) IF (CANTOR_LIBS AND CANTOR_INCLUDE_DIR AND KF5Service_FOUND AND KF5Parts_FOUND) SET (CANTOR_LIBS_FOUND TRUE) - FIND_PATH (CANTOR_VERSION_FILE cantor/cantorlibs_version.h - /usr/include - /usr/local/include - ) - IF (NOT CANTOR_VERSION_FILE) - add_definitions (-DOLD_CANTORLIBS_VERSION) - ENDIF() ELSE () SET (CANTOR_LIBS_FOUND FALSE) SET (CANTOR_LIBS "") @@ -262,7 +253,6 @@ add_definitions (-DHAVE_AT_LEAST_HDF5_1_10_0) ENDIF () IF (HDF5_VERSION VERSION_GREATER "1.10.0.1") - add_definitions (-DHAVE_AT_LEAST_HDF5_1_10_0) add_definitions (-DHAVE_AT_LEAST_HDF5_1_10_1) ENDIF () include_directories (${HDF5_INCLUDE_DIRS}) diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,28 +1,21 @@ -----2.6----- New features: - * Histogram - * Import from MQTT sources * Import of ROOT (CERN) TH1 histograms * Import of Ngspice raw files (ASCII and binary) * Import of data in JSON format (JSON arrays and objects) - * Improved import of NetCDF files - * Added file type specific summary and content for special file types in info box of import dialog * Convolution/Deconvolution of data sets (sampling interval, linear/circular, normalization, wrap, standard kernel) * Cross-/Autocorrelation of data sets (sampling interval, linear/circular, normalization) + * Allow to rotate plot legends * Allow to specify the number format when exporting spreadsheet and matrix * Improved user interface for data fitting (add fit function preview, show parameters directly, make options foldable) + * Added file type specific summary and content for special file types in info box of import dialog * [spreadsheet] when filling a float column with row numbers, automatically convert its type to integer * [spreadsheet] when filling an integer column with function values, automatically convert its type to float * [spreadsheet] data manipulation: add/subtract/multiply/divide for column values * [spreadsheet] export to SQLite - * [matrix] data manipulation: add/subtract/multiply/divide for matrix values - * [worksheet] Allow to specify different border shapes for labels (rectangle, eclipse, etc.) - * [worksheet] Allow to rotate plot legends - * [worksheet] Better positioning of rotated axis tick labels - * [worksheet] Allow to make plots not-interactive (ignore mouse drag and wheel events) to avoid unwanted occasional panning and zooming + * [worksheet] Allow to specify different border shapes for labels (rectangle, elipse, etc.) * Allow to connect to SQL databases via ODBC - * Show the amount of consumed memory in the status bar (optional) - * Allow to change the settings for different computer algebra systems (Maxima, etc.) directly in LabPlot (embedd Cantor's settings widgets) + * Improved import of NetCDF files Bug fixes: * Fixed several problems in live data support diff --git a/INSTALL b/INSTALL --- a/INSTALL +++ b/INSTALL @@ -8,9 +8,8 @@ optional * LaTeX to enable LaTeX typesetting in LabPlot, requires also 'convert' from ImageMagick and 'dvips'. - * Required LaTeX packages: 'color', 'preview' and, in case xelatex or lualatex are used, 'xltxtra'. - * On Windows you need a LaTeX installation (like MiKTeX) and ghostscript for LaTeX support. - * On macOS you need a LaTeX installation (like MacTeX) and ImageMagick for LaTeX support. + On Windows you need a LaTeX installation (like MiKTeX) and ghostscript for LaTeX support. + On macOS you need a LaTeX installation (like MacTeX) and ImageMagick for LaTeX support. * FFTW version 3.3 or higher (including the devel-package, fftw3-devel or similar) * HDF5 version 1.8 or higher (including the devel-package, hdf5-devel or similar) * NetCDF version 3 or higher (including the devel-package, netcdf-devel or similar) diff --git a/admin/SLOC.txt b/admin/SLOC.txt --- a/admin/SLOC.txt +++ b/admin/SLOC.txt @@ -1,43 +1,5 @@ Statistics generated using David A. Wheeler's 'SLOCCount'." -SLOCCount, Copyright (C) 2001-2004 David A. Wheeler -SLOCCount is Open Source Software/Free Software, licensed under the GNU GPL. -SLOCCount comes with ABSOLUTELY NO WARRANTY, and you are welcome to -redistribute it under certain conditions as specified by the GNU GPL license; -see the documentation for details. - - - ------2.6----- -SLOC Directory SLOC-by-Language (Sorted) -57268 backend cpp=52330,ansic=4440,yacc=481,python=17 -33542 kdefrontend cpp=33542 -8993 commonfrontend cpp=8993 -539 tools cpp=539 -152 cantor cpp=152 -20 top_dir sh=20 -0 doc (none) - - -Totals grouped by language (dominant language first): -cpp: 95556 (95.07%) -ansic: 4440 (4.42%) -yacc: 481 (0.48%) -sh: 20 (0.02%) -python: 17 (0.02%) - - -* Total Physical Source Lines of Code (SLOC) = 100,514 -* Development Effort Estimate, Person-Years (Person-Months) = 25.31 (303.77) - (Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05)) -* Schedule Estimate, Years (Months) = 1.83 (21.94) - (Basic COCOMO model, Months = 2.5 * (person-months**0.38)) -* Estimated Average Number of Developers (Effort/Schedule) = 13.84 -* Total Estimated Cost to Develop = $ 3,419,633 - (average salary = $56,286/year, overhead = 2.40). - - - -----2.5----- SLOC Directory SLOC-by-Language (Sorted) 50012 backend cpp=45264,ansic=4251,yacc=480,python=17 diff --git a/admin/labplot-64bit-setup.iss b/admin/labplot-64bit-setup.iss --- a/admin/labplot-64bit-setup.iss +++ b/admin/labplot-64bit-setup.iss @@ -47,7 +47,7 @@ Source: "{#CraftRoot}\bin\libintl-8.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\iconv.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libeay32.dll"; DestDir: "{app}";Flags: ignoreversion -;Source: "{#CraftRoot}\bin\ssleay32.dll"; DestDir: "{app}";Flags: ignoreversion +Source: "{#CraftRoot}\bin\ssleay32.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libssl-1_1-x64.dll"; DestDir: "{app}";Flags: ignoreversion Source: "{#CraftRoot}\bin\libcrypto-1_1-x64.dll"; DestDir: "{app}";Flags: ignoreversion ;Source: "{#CraftRoot}\bin\libdbus-1-3.dll"; DestDir: "{app}";Flags: ignoreversion @@ -101,8 +101,8 @@ Source: "{#CraftRoot}\bin\libcantor_pythonbackend.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\plugins\libcantorpart.dll"; DestDir: "{app}\plugins"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\plugins\cantor\*"; DestDir: "{app}\plugins\cantor"; Flags: recursesubdirs ignoreversion -Source: "{#CraftRoot}\bin\data\kxmlgui5\cantor\*"; DestDir: "{app}\data"; Flags: recursesubdirs ignoreversion -Source: "{#CraftRoot}\bin\data\cantor\*"; DestDir: "{app}\data"; Flags: recursesubdirs ignoreversion +Source: "{#CraftRoot}\bin\data\kxmlgui5\cantor\*"; DestDir: "{app}\cantor"; Flags: recursesubdirs ignoreversion +Source: "{#CraftRoot}\bin\data\cantor\*"; DestDir: "{app}\cantor"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\doc\HTML\en\cantor\*"; DestDir: "{app}\doc\HTML\en\cantor";Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\etc\xdg\cantor*"; DestDir: "{app}\etc\xdg"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\metainfo\org.kde.cantor.appdata.xml"; DestDir: "{app}\data\metainfo"; Flags: ignoreversion @@ -110,7 +110,7 @@ Source: "{#CraftRoot}\bin\data\config.kcfg\*"; DestDir: "{app}\data\config.kcfg"; Flags: recursesubdirs ignoreversion Source: "{#CraftRoot}\bin\data\icons\hicolor\48x48\apps\*"; DestDir: "{app}\data\icons\hicolor\48x48\apps\"; Flags: recursesubdirs ignoreversion ; misc -Source: "{#CraftRoot}\bin\libfftw3.dll"; DestDir: "{app}"; DestName: "libfftw3-3.dll"; Flags: ignoreversion +Source: "{#CraftRoot}\bin\libfftw3.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\netcdf.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\hdf5_hl.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "{#CraftRoot}\bin\hdf5.dll"; DestDir: "{app}"; Flags: ignoreversion diff --git a/liborigin/OriginAnyParser.cpp b/liborigin/OriginAnyParser.cpp --- a/liborigin/OriginAnyParser.cpp +++ b/liborigin/OriginAnyParser.cpp @@ -539,7 +539,7 @@ if (angroup_size > 0) { LOG_PRINT(logfile, " ... group end at %" PRId64 " [0x%" PRIx64 "]. Annotations: %d\n", curpos, curpos, angroup_size) } - andt2_data = string(); + andt2_data = string(""); } else { andt2_data = readObjectAsString(ane_data_2_size); // TODO: get known info @@ -2166,7 +2166,7 @@ GET_SHORT(stmp, w) column = findDataByIndex(w-1); if (column.first.size() > 0) { - curve.xDataName = (curve.dataName != column.first) ? column.first : string(); + curve.xDataName = (curve.dataName != column.first) ? column.first : ""; if (glayer.is3D() || (curve.type == GraphCurve::XYZContour)) { curve.yColumnName = column.second; } else if (glayer.isXYY3D){ diff --git a/liborigin/OriginObj.h b/liborigin/OriginObj.h --- a/liborigin/OriginObj.h +++ b/liborigin/OriginObj.h @@ -163,7 +163,7 @@ Color windowBackgroundColorBase; Color windowBackgroundColorEnd; - Window(const string& _name = string(), const string& _label = string(), bool _hidden = false) + Window(const string& _name= "", const string& _label = "", bool _hidden = false) : name(_name) , label(_label) , objectID(-1) @@ -265,7 +265,7 @@ unsigned int endRow; vector data; - SpreadColumn(const string& _name = string(), unsigned int _index = 0) + SpreadColumn(const string& _name = "", unsigned int _index = 0) : name(_name) , type(ColumnType::Y) , valueType(Numeric) @@ -273,6 +273,8 @@ , significantDigits(6) , decimalPlaces(6) , numericDisplayType(DefaultDecimalDigits) + , command("") + , comment("") , width(8) , index(_index) , colIndex(0) @@ -290,7 +292,7 @@ unsigned int sheets; vector columns; - SpreadSheet(const string& _name = string()) + SpreadSheet(const string& _name = "") : Window(_name) , maxRows(30) , loose(true) @@ -304,7 +306,7 @@ bool loose; vector sheets; - Excel(const string& _name = string(), const string& _label = string(), int _maxRows = 0, bool _hidden = false, bool _loose = true) + Excel(const string& _name = "", const string& _label = "", int _maxRows = 0, bool _hidden = false, bool _loose = true) : Window(_name, _label, _hidden) , maxRows(_maxRows) , loose(_loose) @@ -331,7 +333,7 @@ vector data; vector coordinates; - MatrixSheet(const string& _name = string(), unsigned int _index = 0) + MatrixSheet(const string& _name = "", unsigned int _index = 0) : name(_name) , rowCount(8) , columnCount(8) @@ -339,6 +341,7 @@ , significantDigits(6) , decimalPlaces(6) , numericDisplayType(DefaultDecimalDigits) + , command("") , width(8) , index(_index) , view(DataView) @@ -353,7 +356,7 @@ HeaderViewType header; vector sheets; - Matrix(const string& _name = string()) + Matrix(const string& _name = "") : Window(_name) , activeSheet(0) , header(ColumnRow) @@ -372,9 +375,10 @@ int totalPoints; unsigned int index; - Function(const string& _name = string(), unsigned int _index = 0) + Function(const string& _name = "", unsigned int _index = 0) : name(_name) , type(Normal) + , formula("") , begin(0.0) , end(0.0) , totalPoints(0) @@ -394,7 +398,7 @@ BorderType borderType; Attach attach; - TextBox(const string& _text = string()) + TextBox(const string& _text = "") : text(_text) , color({Color::Regular, {Color::Black}}) , fontSize(20) @@ -790,7 +794,7 @@ BorderType borderType; unsigned char* data; - Bitmap(const string& _name = string()) + Bitmap(const string& _name = "") : attach(Frame) , size(0) , windowName(_name) @@ -960,20 +964,21 @@ bool connectMissingData; string templateName; - Graph(const string& _name = string()) + Graph(const string& _name = "") : Window(_name) , width(400) , height(300) , is3D(false) , isLayout(false) , connectMissingData(false) + , templateName("") {}; }; struct Note : public Window { string text; - Note(const string& _name = string()) + Note(const string& _name = "") : Window(_name) {}; }; @@ -988,7 +993,7 @@ time_t modificationDate; bool active; - ProjectNode(const string& _name = string(), NodeType _type = Folder, const time_t _creationDate = time(nullptr), const time_t _modificationDate = time(nullptr), bool _active = false) + ProjectNode(const string& _name = "", NodeType _type = Folder, const time_t _creationDate = time(nullptr), const time_t _modificationDate = time(nullptr), bool _active = false) : type(_type) , name(_name) , creationDate(_creationDate) diff --git a/org.kde.labplot2.appdata.xml b/org.kde.labplot2.appdata.xml --- a/org.kde.labplot2.appdata.xml +++ b/org.kde.labplot2.appdata.xml @@ -29,7 +29,6 @@ LabPlot xxLabPlotxx LabPlot - LabPlot LabPlot is a KDE application for interactive graphing and analysis of scientific data El LabPlot és una aplicació KDE per a la representació interactiva de grafs i anàlisi de dades científiques El LabPlot és una aplicació KDE per a la representació interactiva de grafs i anàlisi de dades científiques @@ -119,7 +118,6 @@ Contoh pas Adatta esempio Voorbeeld aanpassing - Przykład dopasowania Exemplo de ajuste Exemplo de ajuste Anpassningsexempel @@ -139,7 +137,6 @@ Fungsi matematika Funzioni matematica Wiskundige functie - Funkcja matematyczna Função matemática Função matemática Matematisk funktion @@ -158,7 +155,6 @@ Lembar kerja CAS Foglio di lavoro CAS CAS werkblad - Arkusz roboczy CAS Folha de cálculo CAS Folha de trabalho CAS CAS-arbetsblad diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,7 +39,6 @@ ${KDEFRONTEND_DIR}/datasources/ROOTOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/FITSOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/MQTTErrorWidget.cpp - ${KDEFRONTEND_DIR}/datasources/MQTTSubscriptionWidget.cpp ${KDEFRONTEND_DIR}/datasources/JsonOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/MQTTConnectionManagerWidget.cpp ${KDEFRONTEND_DIR}/datasources/MQTTConnectionManagerDialog.cpp @@ -122,7 +121,6 @@ ${KDEFRONTEND_DIR}/ui/datasources/rootoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/fitsoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/mqtterrorwidget.ui - ${KDEFRONTEND_DIR}/ui/datasources/mqttsubscriptionwidget.ui ${KDEFRONTEND_DIR}/ui/datasources/jsonoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/mqttconnectionmanagerwidget.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/axisdock.ui @@ -322,7 +320,6 @@ ${COMMONFRONTEND_DIR}/widgets/TreeViewComboBox.cpp ${COMMONFRONTEND_DIR}/widgets/qxtspanslider.cpp ${COMMONFRONTEND_DIR}/widgets/MemoryWidget.cpp - ${COMMONFRONTEND_DIR}/widgets/DateTimeSpinBox.cpp ${COMMONFRONTEND_DIR}/datapicker/DatapickerView.cpp ${COMMONFRONTEND_DIR}/datapicker/DatapickerImageView.cpp ) diff --git a/src/backend/cantorWorksheet/CantorWorksheet.h b/src/backend/cantorWorksheet/CantorWorksheet.h --- a/src/backend/cantorWorksheet/CantorWorksheet.h +++ b/src/backend/cantorWorksheet/CantorWorksheet.h @@ -78,7 +78,6 @@ bool init(QByteArray* content = nullptr); private slots: - void dataChanged(const QModelIndex&); void rowsInserted(const QModelIndex & parent, int first, int last); void rowsAboutToBeRemoved(const QModelIndex & parent, int first, int last); void modelReset(); diff --git a/src/backend/cantorWorksheet/CantorWorksheet.cpp b/src/backend/cantorWorksheet/CantorWorksheet.cpp --- a/src/backend/cantorWorksheet/CantorWorksheet.cpp +++ b/src/backend/cantorWorksheet/CantorWorksheet.cpp @@ -77,12 +77,7 @@ connect(m_session, SIGNAL(statusChanged(Cantor::Session::Status)), this, SIGNAL(statusChanged(Cantor::Session::Status))); //variable model -#ifndef OLD_CANTORLIBS_VERSION - m_variableModel = m_session->variableDataModel(); -#else m_variableModel = m_session->variableModel(); -#endif - connect(m_variableModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(dataChanged(QModelIndex))); connect(m_variableModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int))); connect(m_variableModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); connect(m_variableModel, SIGNAL(modelReset()), this, SLOT(modelReset())); @@ -105,30 +100,11 @@ } //SLots -void CantorWorksheet::dataChanged(const QModelIndex& index) { - const QString& name = m_variableModel->data(m_variableModel->index(index.row(), 0)).toString(); - Column* col = child(name); - if (col) { - // Cantor::DefaultVariableModel::DataRole == 257 - QVariant dataValue = m_variableModel->data(m_variableModel->index(index.row(), 1), 257); - if (dataValue.isNull()) - dataValue = m_variableModel->data(m_variableModel->index(index.row(), 1)); - const QString& value = dataValue.toString(); - auto* parser = new VariableParser(m_backendName, value); - if (parser->isParsed()) - col->replaceValues(0, parser->values()); - } - -} - void CantorWorksheet::rowsInserted(const QModelIndex& parent, int first, int last) { Q_UNUSED(parent) for (int i = first; i <= last; ++i) { - const QString& name = m_variableModel->data(m_variableModel->index(i, 0)).toString(); - QVariant dataValue = m_variableModel->data(m_variableModel->index(i, 1), 257); - if (dataValue.isNull()) - dataValue = m_variableModel->data(m_variableModel->index(i, 1)); - const QString& value = dataValue.toString(); + const QString name = m_variableModel->data(m_variableModel->index(first, 0)).toString(); + const QString value = m_variableModel->data(m_variableModel->index(first, 1)).toString(); auto* parser = new VariableParser(m_backendName, value); if (parser->isParsed()) { Column* col = child(name); @@ -168,20 +144,20 @@ void CantorWorksheet::rowsAboutToBeRemoved(const QModelIndex & parent, int first, int last) { Q_UNUSED(parent); -#ifndef OLD_CANTORLIBS_VERSION - for (int i = first; i <= last; ++i) { - const QString& name = m_variableModel->data(m_variableModel->index(first, 0)).toString(); - Column* column = child(name); - if (column) - column->remove(); - } -#else Q_UNUSED(first); Q_UNUSED(last); - //TODO: Old Cantor removes rows from the model even when the variable was changed only. + //TODO: Cantor removes rows from the model even when the variable was changed only. //We don't want this behaviour since this removes the columns from the datasource in the curve. + //We need to fix/change this in Cantor. return; -#endif + +// Q_UNUSED(parent) +// for (int i = first; i <= last; ++i) { +// const QString name = m_variableModel->data(m_variableModel->index(first, 0)).toString(); +// Column* column = child(name); +// if (column) +// column->remove(); +// } } QList CantorWorksheet::getPlugins() { @@ -309,7 +285,7 @@ return false; } } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString()); + Column* column = new Column(""); column->setUndoAware(false); if (!column->load(reader, preview)) { delete column; diff --git a/src/backend/cantorWorksheet/VariableParser.h b/src/backend/cantorWorksheet/VariableParser.h --- a/src/backend/cantorWorksheet/VariableParser.h +++ b/src/backend/cantorWorksheet/VariableParser.h @@ -38,17 +38,16 @@ VariableParser(QString name, QString value); QVector values(); bool isParsed(); - + private: QString m_backendName; QString m_string; QVector m_values; bool m_parsed{false}; - + void parseMaximaValues(); void parsePythonValues(); void parseRValues(); - void parseOctaveValues(); void parseValues(const QStringList&); }; diff --git a/src/backend/cantorWorksheet/VariableParser.cpp b/src/backend/cantorWorksheet/VariableParser.cpp --- a/src/backend/cantorWorksheet/VariableParser.cpp +++ b/src/backend/cantorWorksheet/VariableParser.cpp @@ -48,14 +48,12 @@ parseRValues(); else if (m_backendName.compare(QStringLiteral("Julia"), Qt::CaseInsensitive) == 0) parsePythonValues(); - else if (m_backendName.compare(QStringLiteral("Octave"), Qt::CaseInsensitive) == 0) - parseOctaveValues(); } void VariableParser::parseMaximaValues() { if (m_string.count(QStringLiteral("[")) < 2) { - m_string = m_string.replace(QStringLiteral("["), QString()); - m_string = m_string.replace(QStringLiteral("]"), QString()); + m_string = m_string.replace(QStringLiteral("["), QStringLiteral("")); + m_string = m_string.replace(QStringLiteral("]"), QStringLiteral("")); m_string = m_string.trimmed(); const QStringList valueStringList = m_string.split(QStringLiteral(",")); @@ -68,16 +66,16 @@ m_string = m_string.trimmed(); if (m_string.startsWith(QStringLiteral("array"))) { //parse numpy arrays, string representation like array([1,2,3,4,5]) - m_string = m_string.replace(QStringLiteral("array(["), QString()); - m_string = m_string.replace(QStringLiteral("])"), QString()); + m_string = m_string.replace(QStringLiteral("array(["), QStringLiteral("")); + m_string = m_string.replace(QStringLiteral("])"), QStringLiteral("")); } else if (m_string.startsWith(QStringLiteral("["))) { //parse python's lists - m_string = m_string.replace(QStringLiteral("["), QString()); - m_string = m_string.replace(QStringLiteral("]"), QString()); + m_string = m_string.replace(QStringLiteral("["), QStringLiteral("")); + m_string = m_string.replace(QStringLiteral("]"), QStringLiteral("")); } else if (m_string.startsWith(QStringLiteral("("))) { //parse python's tuples - m_string = m_string.replace(QStringLiteral("("), QString()); - m_string = m_string.replace(QStringLiteral(")"), QString()); + m_string = m_string.replace(QStringLiteral("("), QStringLiteral("")); + m_string = m_string.replace(QStringLiteral(")"), QStringLiteral("")); } else { return; } @@ -96,22 +94,6 @@ parseValues(valueStringList); } -void VariableParser::parseOctaveValues() { - m_string = m_string.trimmed(); - - QStringList valueStringList; - const QStringList tempStringList = m_string.split(QLatin1Char('\n')); - for (const QString& values : tempStringList) { - //TODO: in newer version of Cantor the rows with "Columns..." were removed already. - //we can stop looking for this substring in some point in time later. - if (!values.isEmpty() && !values.trimmed().startsWith(QStringLiteral("Columns"))) - valueStringList << values.split(QLatin1Char(' ')); - } - - valueStringList.removeAll(QStringLiteral("")); - parseValues(valueStringList); -} - bool VariableParser::isParsed() { return m_parsed; } diff --git a/src/backend/core/AbstractAspect.cpp b/src/backend/core/AbstractAspect.cpp --- a/src/backend/core/AbstractAspect.cpp +++ b/src/backend/core/AbstractAspect.cpp @@ -36,10 +36,6 @@ #include "backend/lib/XmlStreamReader.h" #include "backend/lib/SignallingUndoCommand.h" #include "backend/lib/PropertyChangeCommand.h" -#ifdef HAVE_MQTT -#include "backend/datasources/MQTTSubscription.h" -#include "backend/datasources/MQTTTopic.h" -#endif #include @@ -298,26 +294,11 @@ // menu->addAction(KStandardAction::copy(this)); // menu->addAction(KStandardAction::paste(this)); // menu->addSeparator(); + menu->addAction(QIcon::fromTheme(QLatin1String("edit-rename")), i18n("Rename"), this, SIGNAL(renameRequested())); - //don't allow to rename and delete - //1. data spreadsheets of datapicker curves - //2. columns in data spreadsheets of datapicker curves - //1. Mqtt subscriptions - //2. Mqtt topics - //3. Columns in Mqtt topics - bool enabled = !(dynamic_cast(this) && dynamic_cast(this->parentAspect())) - && !(dynamic_cast(this) && this->parentAspect()->parentAspect() && dynamic_cast(this->parentAspect()->parentAspect())) -#ifdef HAVE_MQTT - && !dynamic_cast(this) - && !dynamic_cast(this) - && !(dynamic_cast(this) && dynamic_cast(this->parentAspect())) -#endif - ; - - if(enabled) { - menu->addAction(QIcon::fromTheme(QLatin1String("edit-rename")), i18n("Rename"), this, SIGNAL(renameRequested())); + //don't allow to delete data spreadsheets in the datapicker curves + if ( !(dynamic_cast(this) && dynamic_cast(this->parentAspect())) ) menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete"), this, SLOT(remove())); - } return menu; } @@ -372,7 +353,7 @@ * \brief Return the path that leads from the top-most Aspect (usually a Project) to me. */ QString AbstractAspect::path() const { - return parentAspect() ? parentAspect()->path() + QLatin1Char('/') + name() : QString(); + return parentAspect() ? parentAspect()->path() + QLatin1Char('/') + name() : QLatin1String(""); } /** diff --git a/src/backend/core/AbstractColumn.h b/src/backend/core/AbstractColumn.h --- a/src/backend/core/AbstractColumn.h +++ b/src/backend/core/AbstractColumn.h @@ -91,7 +91,7 @@ // QMatrix // etc. }; - enum Properties { + enum Properties { No = 0x00, Constant = 0x01, MonotonicIncreasing = 0x02, // prev_value >= value for all values in column @@ -194,7 +194,7 @@ virtual int integerAt(int row) const; virtual void setIntegerAt(int row, int new_value); virtual void replaceInteger(int first, const QVector& new_values); - virtual Properties properties() const; + virtual int properties() const; signals: void plotDesignationAboutToChange(const AbstractColumn* source); diff --git a/src/backend/core/AbstractColumn.cpp b/src/backend/core/AbstractColumn.cpp --- a/src/backend/core/AbstractColumn.cpp +++ b/src/backend/core/AbstractColumn.cpp @@ -398,7 +398,7 @@ */ QString AbstractColumn::textAt(int row) const { Q_UNUSED(row); - return QString(); + return ""; } /** @@ -542,11 +542,13 @@ } /** + * @brief AbstractColumn::properties * Returns the properties hold by this column (no, monotonic increasing, monotonic decreasing,...) - * Is used in XYCurve to improve the search velocity for the y value for a specific x value + * Will be used in XYCurve to improve the search velocity for the y value for a specific x value + * @return */ -AbstractColumn::Properties AbstractColumn::properties() const { - return AbstractColumn::Properties::No; +int AbstractColumn::properties() const { + return AbstractColumn::Properties::No; } /**********************************************************************/ diff --git a/src/backend/core/AbstractPart.cpp b/src/backend/core/AbstractPart.cpp --- a/src/backend/core/AbstractPart.cpp +++ b/src/backend/core/AbstractPart.cpp @@ -34,9 +34,6 @@ #include "backend/matrix/Matrix.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/datapicker/DatapickerCurve.h" -#ifdef HAVE_MQTT -#include "backend/datasources/MQTTTopic.h" -#endif #include #include @@ -111,11 +108,7 @@ menu->addSeparator(); if (m_mdiWindow) { - if ( (dynamic_cast(this) || dynamic_cast(this)) -#ifdef HAVE_MQTT - && !dynamic_cast(this) -#endif - ) { + if (dynamic_cast(this) || dynamic_cast(this)) { QMenu* subMenu = new QMenu(i18n("Import Data"), menu); subMenu->addAction(QIcon::fromTheme("document-import"), i18n("From File"), this, SIGNAL(importFromFileRequested())); subMenu->addAction(QIcon::fromTheme("document-import"), i18n("From SQL Database"), this, SIGNAL(importFromSQLDatabaseRequested())); diff --git a/src/backend/core/Folder.cpp b/src/backend/core/Folder.cpp --- a/src/backend/core/Folder.cpp +++ b/src/backend/core/Folder.cpp @@ -42,7 +42,6 @@ #endif #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" -#include "backend/datasources/MQTTSubscription.h" #endif #include "backend/worksheet/Worksheet.h" @@ -54,17 +53,7 @@ * \brief Folder in a project */ -Folder::Folder(const QString &name) : AbstractAspect(name) { - - //when the child being removed is a LiveDataSource, stop reading from the source - connect(this, &AbstractAspect::aspectAboutToBeRemoved, this, [=](const AbstractAspect* aspect) { - const LiveDataSource* lds = dynamic_cast(aspect); - if (lds) - const_cast(lds)->pauseReading(); - } ); - - -} +Folder::Folder(const QString &name) : AbstractAspect(name) {} QIcon Folder::icon() const { return QIcon::fromTheme("folder"); @@ -76,11 +65,7 @@ * The caller takes ownership of the menu. */ QMenu* Folder::createContextMenu() { - if (project() -#ifdef HAVE_MQTT - && !dynamic_cast(this) -#endif - ) + if (project()) return project()->createFolderContextMenu(this); return nullptr; } @@ -170,7 +155,7 @@ QString element_name = reader->name().toString(); if (element_name == QLatin1String("folder")) { - Folder* folder = new Folder(QString()); + Folder* folder = new Folder(""); if (!m_pathesToLoad.isEmpty()) { //a child folder to be read -> provide the list of aspects to be loaded to the child folder, too. @@ -204,28 +189,28 @@ } addChildFast(folder); } else if (element_name == QLatin1String("workbook")) { - Workbook* workbook = new Workbook(QString()); + Workbook* workbook = new Workbook(""); if (!workbook->load(reader, preview)) { delete workbook; return false; } addChildFast(workbook); } else if (element_name == QLatin1String("spreadsheet")) { - Spreadsheet* spreadsheet = new Spreadsheet(QString(), true); + Spreadsheet* spreadsheet = new Spreadsheet("", true); if (!spreadsheet->load(reader, preview)) { delete spreadsheet; return false; } addChildFast(spreadsheet); } else if (element_name == QLatin1String("matrix")) { - Matrix* matrix = new Matrix(QString(), true); + Matrix* matrix = new Matrix("", true); if (!matrix->load(reader, preview)) { delete matrix; return false; } addChildFast(matrix); } else if (element_name == QLatin1String("worksheet")) { - Worksheet* worksheet = new Worksheet(QString()); + Worksheet* worksheet = new Worksheet(""); worksheet->setIsLoading(true); if (!worksheet->load(reader, preview)) { delete worksheet; @@ -245,7 +230,7 @@ #ifdef HAVE_MQTT } else if (element_name == QLatin1String("MQTTClient")) { qDebug()<<"Load MQTTClient"; - MQTTClient* client = new MQTTClient(QString()); + MQTTClient* client = new MQTTClient(""); if (!client->load(reader, preview)) { delete client; return false; @@ -253,21 +238,21 @@ addChildFast(client); #endif } else if (element_name == QLatin1String("LiveDataSource")) { - LiveDataSource* liveDataSource = new LiveDataSource(QString(), true); + LiveDataSource* liveDataSource = new LiveDataSource("", true); if (!liveDataSource->load(reader, preview)) { delete liveDataSource; return false; } addChildFast(liveDataSource); } else if (element_name == QLatin1String("datapicker")) { - Datapicker* datapicker = new Datapicker(QString(), true); + Datapicker* datapicker = new Datapicker("", true); if (!datapicker->load(reader, preview)) { delete datapicker; return false; } addChildFast(datapicker); } else if (element_name == QLatin1String("note")) { - Note* note = new Note(QString()); + Note* note = new Note(""); if (!note->load(reader, preview)) { delete note; return false; diff --git a/src/backend/core/column/Column.h b/src/backend/core/column/Column.h --- a/src/backend/core/column/Column.h +++ b/src/backend/core/column/Column.h @@ -104,7 +104,7 @@ int integerAt(int) const override; void setIntegerAt(int, int) override; void replaceInteger(int, const QVector&) override; - Properties properties() const override; + int properties() const override; double maximum(int count = 0) const override; double minimum(int count = 0) const override; diff --git a/src/backend/core/column/Column.cpp b/src/backend/core/column/Column.cpp --- a/src/backend/core/column/Column.cpp +++ b/src/backend/core/column/Column.cpp @@ -96,20 +96,10 @@ QMenu* Column::createContextMenu() { QMenu* menu = AbstractAspect::createContextMenu(); - QAction* firstAction{nullptr}; - - //insert after "rename" and "delete" actions, if available. - //MQTTTopic columns don't have these actions - if (menu->actions().size() > 1) - firstAction = menu->actions().at(1); + QAction* firstAction = menu->actions().at(1); //add actions available in SpreadsheetView - //TODO: we don't need to add anything from the view for MQTTTopic columns. - //at the moment it's ok to check to the null pointer for firstAction here. - //later, once we have some actions in the menu also for MQTT topics we'll - //need to explicitely to dynamic_cast for MQTTTopic - if (firstAction) - emit requestProjectContextMenu(menu); + emit requestProjectContextMenu(menu); //"Used in" menu containing all curves where the column is used QMenu* usedInMenu = new QMenu(i18n("Used in")); @@ -141,9 +131,7 @@ } } - if (firstAction) - menu->insertSeparator(firstAction); - + menu->insertSeparator(firstAction); menu->insertMenu(firstAction, usedInMenu); menu->insertSeparator(firstAction); @@ -255,7 +243,7 @@ d->statisticsAvailable = false; d->hasValuesAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; } /** @@ -269,7 +257,7 @@ d->statisticsAvailable = false; d->hasValuesAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; } /** @@ -369,7 +357,7 @@ void Column::setTextAt(int row, const QString& new_value) { DEBUG("Column::setTextAt()"); d->statisticsAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnSetTextCmd(d, row, new_value)); } @@ -382,7 +370,7 @@ DEBUG("Column::replaceTexts()"); if (!new_values.isEmpty()) { //TODO: do we really need this check? d->statisticsAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnReplaceTextsCmd(d, first, new_values)); } } @@ -394,7 +382,7 @@ */ void Column::setDateAt(int row, QDate new_value) { d->statisticsAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } @@ -405,7 +393,7 @@ */ void Column::setTimeAt(int row, QTime new_value) { d->statisticsAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } @@ -416,7 +404,7 @@ */ void Column::setDateTimeAt(int row, const QDateTime& new_value) { d->statisticsAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnSetDateTimeCmd(d, row, new_value)); } @@ -428,7 +416,7 @@ void Column::replaceDateTimes(int first, const QVector& new_values) { if (!new_values.isEmpty()) { d->statisticsAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnReplaceDateTimesCmd(d, first, new_values)); } } @@ -442,7 +430,7 @@ // DEBUG("Column::setValueAt()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnSetValueCmd(d, row, new_value)); } @@ -456,7 +444,7 @@ if (!new_values.isEmpty()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnReplaceValuesCmd(d, first, new_values)); } } @@ -470,7 +458,7 @@ DEBUG("Column::setIntegerAt()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnSetIntegerCmd(d, row, new_value)); } @@ -484,20 +472,21 @@ if (!new_values.isEmpty()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; - d->propertiesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnReplaceIntegersCmd(d, first, new_values)); } } /*! * \brief Column::properties * Returns the column properties of this curve (monoton increasing, monoton decreasing, ... ) - * \see AbstractColumn::properties + * See AbstractColumn::properties + * \return */ -AbstractColumn::Properties Column::properties() const{ - if (!d->propertiesAvailable) - d->updateProperties(); +int Column::properties() const{ + if (!d->propertiesAvailable) + d->updateProperties(); - return d->properties; + return d->properties; } const Column::ColumnStatistics& Column::statistics() const { diff --git a/src/backend/core/column/ColumnPrivate.h b/src/backend/core/column/ColumnPrivate.h --- a/src/backend/core/column/ColumnPrivate.h +++ b/src/backend/core/column/ColumnPrivate.h @@ -105,7 +105,7 @@ void setIntegerAt(int row, int new_value); void replaceInteger(int first, const QVector&); - void updateProperties(); + void updateProperties(); mutable AbstractColumn::ColumnStatistics statistics; bool statisticsAvailable{false}; //is 'statistics' already available or needs to be (re-)calculated? @@ -114,7 +114,7 @@ bool hasValuesAvailable{false}; //is 'hasValues' already available or needs to be (re-)calculated? mutable bool propertiesAvailable{false}; //is 'properties' already available (true) or needs to be (re-)calculated (false)? - mutable AbstractColumn::Properties properties{AbstractColumn::Properties::No}; // declares the properties of the curve (monotonic increasing/decreasing ...). Speed up algorithms + mutable int properties{AbstractColumn::Properties::No}; // declares the properties of the curve (monotonic increasing/decreasing ...). Speed up algorithms private: AbstractColumn::ColumnMode m_column_mode; // type of column data void* m_data; //pointer to the data container (QVector) diff --git a/src/backend/core/column/ColumnPrivate.cpp b/src/backend/core/column/ColumnPrivate.cpp --- a/src/backend/core/column/ColumnPrivate.cpp +++ b/src/backend/core/column/ColumnPrivate.cpp @@ -1213,115 +1213,114 @@ } /*! - * Updates the properties. Will be called, when data in the column changed. + * \brief ColumnPrivate::updateProperties + * updates the properties. Will be called, when data in the column changed. * The properies will be used to speed up some algorithms. * See where variable properties will be used. */ void ColumnPrivate::updateProperties() { - // TODO: for double Properties::Constant will never be used. Use an epsilon (difference smaller than epsilon is zero) - if (rowCount() == 0) { - properties = AbstractColumn::Properties::No; - propertiesAvailable = true; - return; - } + // TODO: for double Properties::Constant will never be used. Use an epsilon (difference smaller than epsilon is zero) + if (rowCount() == 0) { + properties = AbstractColumn::Properties::No; + propertiesAvailable = true; + return; + } - double prevValue = NAN; - int prevValueInt = 0; - qint64 prevValueDatetime = 0; - - if (m_column_mode == AbstractColumn::Integer) - prevValueInt = integerAt(0); - else if (m_column_mode == AbstractColumn::Numeric) - prevValue = valueAt(0); - else if (m_column_mode == AbstractColumn::DateTime || - m_column_mode == AbstractColumn::Month || - m_column_mode == AbstractColumn::Day) - prevValueDatetime = dateTimeAt(0).toMSecsSinceEpoch(); - else { - properties = AbstractColumn::Properties::No; - propertiesAvailable = true; - return; - } + double prevValue = NAN; + int prevValueInt = 0; + qint64 prevValueDatetime; + + if (m_column_mode == AbstractColumn::Integer) + prevValueInt = integerAt(0); + else if (m_column_mode == AbstractColumn::Numeric) + prevValue = valueAt(0); + else if (m_column_mode == AbstractColumn::DateTime || + m_column_mode == AbstractColumn::Month || + m_column_mode == AbstractColumn::Day) + prevValueDatetime = dateTimeAt(0).toMSecsSinceEpoch(); int monotonic_decreasing = -1; int monotonic_increasing = -1; - double value; - int valueInt; - qint64 valueDateTime; + double value; + int valueInt; + qint64 valueDateTime; - for (int row = 1; row < rowCount(); row++) { + for (int row=1; row< rowCount(); row++) { - if (m_column_mode == AbstractColumn::Integer) { - valueInt = integerAt(row); + if (m_column_mode == AbstractColumn::Integer) { + valueInt = integerAt(row); // check monotonic increasing if (valueInt >= prevValueInt && monotonic_increasing < 0) monotonic_increasing = 1; - else if (valueInt < prevValueInt && monotonic_increasing >= 0) + else if (valueInt < prevValueInt && monotonic_increasing >=0) monotonic_increasing = 0; - // else: nothing + // else: nothing // check monotonic decreasing if (valueInt <= prevValueInt && monotonic_decreasing < 0) monotonic_decreasing = 1; - else if (valueInt > prevValueInt && monotonic_decreasing >= 0) + else if (valueInt > prevValueInt && monotonic_decreasing >=0) monotonic_decreasing = 0; - prevValueInt = valueInt; + prevValueInt = valueInt; - } else if (m_column_mode == AbstractColumn::Numeric) { - value = valueAt(row); + } else if (m_column_mode == AbstractColumn::Numeric) { + value = valueAt(row); - // check monotonic increasing - if (value >= prevValue && monotonic_increasing < 0) - monotonic_increasing = 1; - else if (value < prevValue) - monotonic_increasing = 0; - // else: nothing + // check monotonic increasing + if (value >= prevValue && monotonic_increasing < 0) + monotonic_increasing = 1; + else if (value < prevValue) + monotonic_increasing = 0; + // else: nothing - // check monotonic decreasing - if (value <= prevValue && monotonic_decreasing < 0) - monotonic_decreasing = 1; - else if (value > prevValue) - monotonic_decreasing = 0; + // check monotonic decreasing + if (value <= prevValue && monotonic_decreasing < 0) + monotonic_decreasing = 1; + else if (value > prevValue) + monotonic_decreasing = 0; - prevValue = value; + prevValue = value; - } else if (m_column_mode == AbstractColumn::DateTime || - m_column_mode == AbstractColumn::Month || - m_column_mode == AbstractColumn::Day) { + } else if (m_column_mode == AbstractColumn::DateTime || + m_column_mode == AbstractColumn::Month || + m_column_mode == AbstractColumn::Day) { - valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); + valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); - // check monotonic increasing - if (valueDateTime >= prevValueDatetime && monotonic_increasing < 0) - monotonic_increasing = 1; - else if (valueDateTime < prevValueDatetime) - monotonic_increasing = 0; - // else: nothing + // check monotonic increasing + if (valueDateTime >= prevValueDatetime && monotonic_increasing < 0) + monotonic_increasing = 1; + else if (valueDateTime < prevValueDatetime) + monotonic_increasing = 0; + // else: nothing - // check monotonic decreasing - if (valueDateTime <= prevValueDatetime && monotonic_decreasing < 0) - monotonic_decreasing = 1; - else if (valueDateTime > prevValueDatetime) - monotonic_decreasing = 0; + // check monotonic decreasing + if (valueDateTime <= prevValueDatetime && monotonic_decreasing < 0) + monotonic_decreasing = 1; + else if (valueDateTime > prevValueDatetime) + monotonic_decreasing = 0; - prevValueDatetime = valueDateTime; - } + prevValueDatetime = valueDateTime; + }else { + properties = AbstractColumn::Properties::No; + return; + } } - properties = AbstractColumn::Properties::No; + properties = AbstractColumn::Properties::No; if (monotonic_increasing > 0 && monotonic_decreasing > 0) - properties = AbstractColumn::Properties::Constant; + properties += AbstractColumn::Properties::Constant; else if (monotonic_decreasing > 0) - properties = AbstractColumn::Properties::MonotonicDecreasing; + properties += AbstractColumn::Properties::MonotonicDecreasing; else if (monotonic_increasing > 0) - properties = AbstractColumn::Properties::MonotonicIncreasing; + properties += AbstractColumn::Properties::MonotonicIncreasing; - propertiesAvailable = true; + propertiesAvailable = true; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/backend/core/column/ColumnStringIO.cpp b/src/backend/core/column/ColumnStringIO.cpp --- a/src/backend/core/column/ColumnStringIO.cpp +++ b/src/backend/core/column/ColumnStringIO.cpp @@ -35,7 +35,7 @@ * \class ColumnStringIO * \brief String-IO interface of Column. */ -ColumnStringIO::ColumnStringIO(Column* owner) : AbstractColumn(QString()), m_owner(owner) { +ColumnStringIO::ColumnStringIO(Column* owner) : AbstractColumn(""), m_owner(owner) { } AbstractColumn::ColumnMode ColumnStringIO::columnMode() const { diff --git a/src/backend/datapicker/Datapicker.cpp b/src/backend/datapicker/Datapicker.cpp --- a/src/backend/datapicker/Datapicker.cpp +++ b/src/backend/datapicker/Datapicker.cpp @@ -273,7 +273,7 @@ if (curve == m_activeCurve) { m_activeCurve = nullptr; - emit statusInfo(QString()); + emit statusInfo(""); } } else handleChildAspectAboutToBeRemoved(aspect); @@ -367,7 +367,7 @@ m_image = plot; } } else if (reader->name() == "datapickerCurve") { - DatapickerCurve* curve = new DatapickerCurve(QString()); + DatapickerCurve* curve = new DatapickerCurve(""); if (!curve->load(reader, preview)) { delete curve; return false; diff --git a/src/backend/datapicker/DatapickerCurve.cpp b/src/backend/datapicker/DatapickerCurve.cpp --- a/src/backend/datapicker/DatapickerCurve.cpp +++ b/src/backend/datapicker/DatapickerCurve.cpp @@ -595,7 +595,7 @@ READ_QBRUSH(d->pointErrorBarBrush); READ_QPEN(d->pointErrorBarPen); } else if (reader->name() == "datapickerPoint") { - DatapickerPoint* curvePoint = new DatapickerPoint(QString()); + DatapickerPoint* curvePoint = new DatapickerPoint(""); curvePoint->setHidden(true); if (!curvePoint->load(reader, preview)) { delete curvePoint; diff --git a/src/backend/datapicker/DatapickerImage.cpp b/src/backend/datapicker/DatapickerImage.cpp --- a/src/backend/datapicker/DatapickerImage.cpp +++ b/src/backend/datapicker/DatapickerImage.cpp @@ -796,7 +796,7 @@ READ_QBRUSH(d->pointBrush); READ_QPEN(d->pointPen); } else if (reader->name() == "datapickerPoint") { - DatapickerPoint* datapickerPoint = new DatapickerPoint(QString()); + DatapickerPoint* datapickerPoint = new DatapickerPoint(""); datapickerPoint->setHidden(true); if (!datapickerPoint->load(reader, preview)) { delete datapickerPoint; diff --git a/src/backend/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -724,13 +724,7 @@ DEBUG(" file is watched"); if (!m_fileSystemWatcher) { m_fileSystemWatcher = new QFileSystemWatcher; - - //connect to file changes to read the new data connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); - - //connect to file changes to re-add the file path again - need to cope with deletion of files in text editors which - //on save create a new file in the temp folder first and then swap with the original one. - connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [=]() {m_fileSystemWatcher->addPath(m_fileName);}); } if (!m_fileSystemWatcher->files().contains(m_fileName)) @@ -941,7 +935,7 @@ if (!m_filter->load(reader)) return false; } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Text); + Column* column = new Column("", AbstractColumn::Text); if (!column->load(reader, preview)) { delete column; setColumnCount(0); diff --git a/src/backend/datasources/MQTTClient.h b/src/backend/datasources/MQTTClient.h --- a/src/backend/datasources/MQTTClient.h +++ b/src/backend/datasources/MQTTClient.h @@ -253,7 +253,7 @@ void MQTTSubscribed(); void MQTTTopicsChanged(); void readFromTopics(); - void clientAboutToBeDeleted(const QString&, quint16); + void clientAboutToBeDeleted(const QString&); #endif //HAVE_MQTT }; diff --git a/src/backend/datasources/MQTTClient.cpp b/src/backend/datasources/MQTTClient.cpp --- a/src/backend/datasources/MQTTClient.cpp +++ b/src/backend/datasources/MQTTClient.cpp @@ -68,6 +68,11 @@ m_client(new QMqttClient(this)), m_willTimer(new QTimer(this)) { + qDebug() << "MQTTClient constructor: " << m_client->hostname(); + m_MQTTWill.enabled = false; + m_MQTTWill.willRetain = false; + m_MQTTWill.willStatistics.fill(false, 15); + connect(m_updateTimer, &QTimer::timeout, this, &MQTTClient::read); connect(m_client, &QMqttClient::connected, this, &MQTTClient::onMQTTConnect); connect(m_willTimer, &QTimer::timeout, this, &MQTTClient::updateWillMessage); @@ -75,10 +80,10 @@ } MQTTClient::~MQTTClient() { - emit clientAboutToBeDeleted(m_client->hostname(), m_client->port()); + emit clientAboutToBeDeleted(m_client->hostname()); //stop reading before deleting the objects pauseReading(); - qDebug() << "Delete MQTTClient: " << m_client->hostname() << m_client->port(); + qDebug()<<"Delete MQTTClient: " << m_client->hostname(); delete m_filter; delete m_updateTimer; @@ -236,7 +241,7 @@ * \brief Returns the MQTTClient's icon */ QIcon MQTTClient::icon() const { - return QIcon::fromTheme("network-server-database"); + return QIcon::fromTheme("labplot-MQTT"); } /*! @@ -400,7 +405,7 @@ MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); - addChildFast(newSubscription); + addChild(newSubscription); m_MQTTSubscriptions.push_back(newSubscription); //Search for inferior subscriptions, that the new subscription contains @@ -408,7 +413,7 @@ QVector inferiorSubscriptions; for (auto* subscription : m_MQTTSubscriptions) { if (checkTopicContains(topicName, subscription->subscriptionName()) - && topicName != subscription->subscriptionName()) { + && topicName != subscription->subscriptionName()) { found = true; inferiorSubscriptions.push_back(subscription); } @@ -431,7 +436,7 @@ for (int j = 0; j < m_MQTTSubscriptions.size(); ++j) { if (m_MQTTSubscriptions[j]->subscriptionName() == - inferiorSubscription->subscriptionName()) { + inferiorSubscription->subscriptionName()) { m_MQTTSubscriptions.remove(j); } } @@ -488,6 +493,8 @@ break; } } + + //Signal that there was a change among the topics emit MQTTTopicsChanged(); } } @@ -514,7 +521,7 @@ MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); - addChildFast(newSubscription); + addChild(newSubscription); m_MQTTSubscriptions.push_back(newSubscription); //Search for the subscription the topic belonged to @@ -522,7 +529,7 @@ MQTTSubscription* superiorSubscription = nullptr; for (auto* subscription : m_MQTTSubscriptions) { if (checkTopicContains(subscription->subscriptionName(), topicName) - && topicName != subscription->subscriptionName()) { + && topicName != subscription->subscriptionName()) { found = true; superiorSubscription = subscription; break; @@ -612,7 +619,7 @@ for (int i = 0; i < superiorList.size(); ++i) { if (superiorList.at(i) != inferiorList.at(i)) { if ((superiorList.at(i) != '+') && - !(superiorList.at(i) == '#' && i == superiorList.size() - 1)) { + !(superiorList.at(i) == '#' && i == superiorList.size() - 1)) { //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) //then superior can't contain inferior ok = false; @@ -637,12 +644,12 @@ * * \param first the name of a topic * \param second the name of a topic - * \return The name of the common topic, if it exists, otherwise an empty string + * \return The name of the common topic, if it exists, otherwise "" */ QString MQTTClient::checkCommonLevel(const QString& first, const QString& second) { QStringList firstList = first.split('/', QString::SkipEmptyParts); QStringList secondtList = second.split('/', QString::SkipEmptyParts); - QString commonTopic; + QString commonTopic = ""; if (!firstList.isEmpty()) { //the two topics have to be the same size and can't be identic @@ -693,7 +700,7 @@ m_MQTTWill = settings; } -MQTTClient::MQTTWill MQTTClient::willSettings() const { +MQTTClient::MQTTWill MQTTClient::willSettings() const{ return m_MQTTWill; } @@ -711,7 +718,7 @@ /*! * \brief Returns whether the user wants to use will message or not */ -bool MQTTClient::MQTTWillUse() const { +bool MQTTClient::MQTTWillUse() const{ return m_MQTTWill.enabled; } @@ -728,7 +735,7 @@ /*! * \brief Returns the will topic of the client */ -QString MQTTClient::willTopic() const { +QString MQTTClient::willTopic() const{ return m_MQTTWill.willTopic; } @@ -848,12 +855,12 @@ if (asciiFilter != nullptr) { //Statistics is only possible if the data stored in the MQTTTopic is of type integer or numeric if ((asciiFilter->MQTTColumnMode() == AbstractColumn::ColumnMode::Integer) || - (asciiFilter->MQTTColumnMode() == AbstractColumn::ColumnMode::Numeric)) { + (asciiFilter->MQTTColumnMode() == AbstractColumn::ColumnMode::Numeric)) { m_client->setWillMessage(asciiFilter->MQTTColumnStatistics(willTopic).toUtf8()); } //Otherwise set empty message else { - m_client->setWillMessage(QByteArray()); + m_client->setWillMessage(QString("").toUtf8()); } qDebug() << "Will statistics message: "<< QString(m_client->willMessage()); } @@ -877,7 +884,7 @@ /*! * \brief Returns the MQTTClient's will update type */ -MQTTClient::WillUpdateType MQTTClient::willUpdateType() const { +MQTTClient::WillUpdateType MQTTClient::willUpdateType() const{ return m_MQTTWill.willUpdateType; } @@ -893,7 +900,7 @@ /*! * \brief Returns the time interval of updating the MQTTClient's will message */ -int MQTTClient::willTimeInterval() const { +int MQTTClient::willTimeInterval() const{ return m_MQTTWill.willTimeInterval; } @@ -938,14 +945,14 @@ * \brief Returns a bool vector, meaning which statistic types are included in the will message * If the corresponding value is true, the statistic type is included, otherwise it isn't */ -QVector MQTTClient::willStatistics() const { +QVector MQTTClient::willStatistics() const{ return m_MQTTWill.willStatistics; } /*! * \brief Starts the will timer, which will update the will message */ -void MQTTClient::startWillTimer() const { +void MQTTClient::startWillTimer() const{ if (m_MQTTWill.willUpdateType == WillUpdateType::TimePeriod) m_willTimer->start(m_MQTTWill.willTimeInterval); } @@ -953,7 +960,7 @@ /*! * \brief Stops the will timer */ -void MQTTClient::stopWillTimer() const { +void MQTTClient::stopWillTimer() const{ m_willTimer->stop(); } @@ -976,7 +983,7 @@ } if ((m_client->state() == QMqttClient::ClientState::Connected) && m_MQTTFirstConnectEstablished) { -// qDebug()<<"Read"; + qDebug()<<"Read"; //Signal for every MQTTTopic that they can read emit readFromTopics(); } @@ -1005,7 +1012,7 @@ MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); - addChildFast(newSubscription); + addChild(newSubscription); m_MQTTSubscriptions.push_back(newSubscription); } @@ -1043,8 +1050,11 @@ //Decide to interpret retain message or not if (!msg.retain() || m_MQTTRetain) { //If this is the first message from the topic, save its name - if (!m_topicNames.contains(msg.topic().name())) + if (!m_topicNames.contains(msg.topic().name())) { m_topicNames.push_back(msg.topic().name()); + //Signal that a new topic is found + emit MQTTTopicsChanged(); + } //Pass the message and the topic name to the MQTTSubscription which contains the topic for (auto* subscription : m_MQTTSubscriptions) { @@ -1055,11 +1065,8 @@ } //if the message was received by the will topic, update the last message received by it - if (msg.topic().name() == m_MQTTWill.willTopic) { + if (msg.topic().name() == m_MQTTWill.willTopic) m_MQTTWill.willLastMessage = QString(msg.payload()); - - emit MQTTTopicsChanged(); - } } } @@ -1333,12 +1340,13 @@ m_MQTTWill.willStatistics[i] = str.toInt(); } } + } else if (reader->name() == "asciiFilter") { setFilter(new AsciiFilter); if (!m_filter->load(reader)) return false; } else if (reader->name() == "MQTTSubscription") { - MQTTSubscription* subscription = new MQTTSubscription(QString()); + MQTTSubscription* subscription = new MQTTSubscription(""); subscription->setMQTTClient(this); m_MQTTSubscriptions.push_back(subscription); connect(subscription, &MQTTSubscription::loaded, this, &MQTTClient::subscriptionLoaded); diff --git a/src/backend/datasources/MQTTSubscription.h b/src/backend/datasources/MQTTSubscription.h --- a/src/backend/datasources/MQTTSubscription.h +++ b/src/backend/datasources/MQTTSubscription.h @@ -47,6 +47,7 @@ void setMQTTClient(MQTTClient *client); QString subscriptionName() const; + void addTopic(const QString&); const QVector topics() const; MQTTClient* mqttClient() const; void messageArrived(const QString&, const QString&); diff --git a/src/backend/datasources/MQTTSubscription.cpp b/src/backend/datasources/MQTTSubscription.cpp --- a/src/backend/datasources/MQTTSubscription.cpp +++ b/src/backend/datasources/MQTTSubscription.cpp @@ -50,6 +50,15 @@ qDebug() << "Delete MQTTSubscription: " << m_subscriptionName; } +/*! + *\brief Adds an MQTTTopic as a child + * + * \param topicName the name of the topic, which will be added to the tree widget + */ +void MQTTSubscription::addTopic(const QString& topicName) { + addChild(new MQTTTopic(topicName, this, false)); +} + /*! *\brief Returns the object's MQTTTopic children * @@ -87,7 +96,7 @@ //read the message if needed if ((m_MQTTClient->updateType() == MQTTClient::UpdateType::NewData) && - !m_MQTTClient->isPaused()) + !m_MQTTClient->isPaused()) topic->read(); found = true; @@ -97,11 +106,20 @@ //if the topic can't be found, we add it as a new MQTTTopic, and read from it if needed if (!found) { - MQTTTopic* newTopic = new MQTTTopic(topicName, this, false); - addChildFast(newTopic); //no need for undo/redo here - newTopic->newMessage(message); - if ((m_MQTTClient->updateType() == MQTTClient::UpdateType::NewData) && !m_MQTTClient->isPaused()) - newTopic->read(); + addTopic(topicName); + topics = children(); + MQTTTopic* newTopic = nullptr; + for (auto* topic: topics) { + if (topicName == topic->topicName()) { + newTopic = topic; + break; + } + } + if (newTopic != nullptr) { + newTopic->newMessage(message); + if ((m_MQTTClient->updateType() == MQTTClient::UpdateType::NewData) && !m_MQTTClient->isPaused()) + newTopic->read(); + } } } @@ -127,7 +145,7 @@ *\brief Returns the icon of MQTTSubscription */ QIcon MQTTSubscription::icon() const { - return QIcon::fromTheme("mail-signed-full"); + return QIcon::fromTheme("labplot-MQTT"); } //############################################################################## @@ -177,9 +195,17 @@ return false; } else if (reader->name() == "general") { attribs = reader->attributes(); - m_subscriptionName = attribs.value("subscriptionName").toString(); - } else if(reader->name() == QLatin1String("MQTTTopic")) { - MQTTTopic* topic = new MQTTTopic(QString(), this, false); + + str = attribs.value("subscriptionName").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'subscriptionName'")); + else { + m_subscriptionName = str; + setName(str); + } + + } else if ( reader->name() == QLatin1String("MQTTTopic")) { + MQTTTopic* topic = new MQTTTopic("", this, false); if (!topic->load(reader, preview)) { delete topic; return false; diff --git a/src/backend/datasources/MQTTTopic.h b/src/backend/datasources/MQTTTopic.h --- a/src/backend/datasources/MQTTTopic.h +++ b/src/backend/datasources/MQTTTopic.h @@ -56,6 +56,11 @@ QString topicName() const; MQTTClient* mqttClient() const; void newMessage(const QString&); + int readingType() const; + int sampleSize() const; + bool isPaused() const; + int updateInterval() const; + int keepNValues() const; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; diff --git a/src/backend/datasources/MQTTTopic.cpp b/src/backend/datasources/MQTTTopic.cpp --- a/src/backend/datasources/MQTTTopic.cpp +++ b/src/backend/datasources/MQTTTopic.cpp @@ -29,13 +29,19 @@ #ifdef HAVE_MQTT #include "backend/datasources/MQTTSubscription.h" + #include "backend/datasources/MQTTClient.h" +#include "backend/core/Project.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "backend/datasources/filters/AsciiFilter.h" +#include +#include +#include #include #include +#include #include #include #include @@ -43,7 +49,8 @@ /*! \class MQTTTopic - \brief Represents a topic of a subscription made in MQTTClient. + \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters. + Represents a topic of a subscription made in MQTTClient \ingroup datasources */ @@ -140,6 +147,41 @@ return m_partView; } +/*! + *\brief Returns the reading type of the MQTTClient to which the MQTTTopic belongs + */ +int MQTTTopic::readingType() const { + return static_cast (m_MQTTClient->readingType()); +} + +/*! + *\brief Returns sampleSize of the MQTTClient to which the MQTTTopic belongs + */ +int MQTTTopic::sampleSize() const { + return m_MQTTClient->sampleSize(); +} + +/*! + *\brief Returns whether reading is paused or not in the MQTTClient to which the MQTTTopic belongs + */ +bool MQTTTopic::isPaused() const { + return m_MQTTClient->isPaused(); +} + +/*! + *\brief Returns update interval of the MQTTClient to which the MQTTTopic belongs + */ +int MQTTTopic::updateInterval() const { + return m_MQTTClient->updateInterval(); +} + +/*! + *\brief Returns the keepNValues (how many values we should keep) of the MQTTClient to which the MQTTTopic belongs + */ +int MQTTTopic::keepNValues() const { + return m_MQTTClient->keepNValues(); +} + /*! *\brief Adds a message received by the topic to the message puffer */ @@ -150,7 +192,7 @@ /*! *\brief Returns the name of the MQTTTopic */ -QString MQTTTopic::topicName() const { +QString MQTTTopic::topicName() const{ return m_topicName; } @@ -165,7 +207,7 @@ /*! *\brief Returns the MQTTClient the topic belongs to */ -MQTTClient *MQTTTopic::mqttClient() const { +MQTTClient *MQTTTopic::mqttClient() const{ return m_MQTTClient; } @@ -188,7 +230,7 @@ while (!m_messagePuffer.isEmpty()) { qDebug() << "Reading from topic " << m_topicName; const QString tempMessage = m_messagePuffer.takeFirst(); - m_filter->readMQTTTopic(tempMessage, this); + m_filter->readMQTTTopic(tempMessage, m_topicName, this); } } @@ -232,7 +274,7 @@ return false; bool isFilterPrepared = false; - QString separator; + QString separator = ""; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; @@ -291,7 +333,7 @@ if (!m_filter->load(reader)) return false; } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Text); + Column* column = new Column("", AbstractColumn::Text); if (!column->load(reader, preview)) { delete column; setColumnCount(0); diff --git a/src/backend/datasources/filters/AbstractFileFilter.cpp b/src/backend/datasources/filters/AbstractFileFilter.cpp --- a/src/backend/datasources/filters/AbstractFileFilter.cpp +++ b/src/backend/datasources/filters/AbstractFileFilter.cpp @@ -94,52 +94,39 @@ QString fileInfo; #ifndef HAVE_WINDOWS //check, if we can guess the file type by content - QProcess proc; - proc.start("file", QStringList() << "-b" << "-z" << fileName); - if (!proc.waitForFinished(1000)) { - proc.kill(); + auto* proc = new QProcess(); + QStringList args; + args << "-b" << fileName; + proc->start("file", args); + if (!proc->waitForReadyRead(1000)) { DEBUG("ERROR: reading file type of file" << fileName.toStdString()); return Binary; } - fileInfo = proc.readLine(); + fileInfo = proc->readLine(); #endif FileType fileType; QByteArray imageFormat = QImageReader::imageFormat(fileName); - if (fileInfo.contains(QLatin1String("JSON")) || fileName.endsWith(QLatin1String("json"), Qt::CaseInsensitive) - //json file can be compressed. add all formats supported by KFilterDev, \sa KCompressionDevice::CompressionType - || fileName.endsWith(QLatin1String("json.gz"), Qt::CaseInsensitive) - || fileName.endsWith(QLatin1String("json.bz2"), Qt::CaseInsensitive) - || fileName.endsWith(QLatin1String("json.lzma"), Qt::CaseInsensitive) - || fileName.endsWith(QLatin1String("json.xz"), Qt::CaseInsensitive) ) { + 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("ASCII")) - || fileName.endsWith(QLatin1String("txt"), Qt::CaseInsensitive) - || fileName.endsWith(QLatin1String("csv"), Qt::CaseInsensitive) - || fileName.endsWith(QLatin1String("dat"), Qt::CaseInsensitive) - || fileInfo.contains(QLatin1String("compressed data"))/* for gzipped ascii data */ ) { + } else if (fileInfo.contains(QLatin1String("compressed data")) || fileInfo.contains(QLatin1String("ASCII")) || + fileName.endsWith(QLatin1String("txt"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("csv"), Qt::CaseInsensitive) || + fileName.endsWith(QLatin1String("dat"), 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) ) + } 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)) + 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)) + 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")) //can be "ROOT Data Format" or "ROOT file Version ??? (Compression: 1)" - || fileName.endsWith(QLatin1String("root"), Qt::CaseInsensitive)) // TODO find out file description + 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; diff --git a/src/backend/datasources/filters/AsciiFilter.h b/src/backend/datasources/filters/AsciiFilter.h --- a/src/backend/datasources/filters/AsciiFilter.h +++ b/src/backend/datasources/filters/AsciiFilter.h @@ -3,7 +3,7 @@ Project : LabPlot Description : ASCII I/O-filter -------------------------------------------------------------------- -Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) +Copyright : (C) 2009-2013 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -54,16 +54,16 @@ static QString fileInfoString(const QString&); static int columnNumber(const QString& fileName, const QString& separator = QString()); static size_t lineNumber(const QString& fileName); - size_t lineNumber(QIODevice&) const; // calculate number of lines if device supports it + static size_t lineNumber(QIODevice&); // calculate number of lines if device supports it // read data from any device void readDataFromDevice(QIODevice& device, AbstractDataSource*, - AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); + AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); void readFromLiveDeviceNotFile(QIODevice& device, AbstractDataSource*dataSource); qint64 readFromLiveDevice(QIODevice& device, AbstractDataSource*, qint64 from = -1); // overloaded function to read from file void readDataFromFile(const QString& fileName, AbstractDataSource* = nullptr, - AbstractFileFilter::ImportMode = AbstractFileFilter::Replace) override; + AbstractFileFilter::ImportMode = AbstractFileFilter::Replace) override; void write(const QString& fileName, AbstractDataSource*) override; QVector preview(const QString& fileName, int lines); @@ -73,11 +73,11 @@ void saveFilterSettings(const QString&) const override; #ifdef HAVE_MQTT - QVector preview(const QString& message); - QString MQTTColumnStatistics(const MQTTTopic*) const; + void MQTTPreview(QVector&, const QString&, const QString&); + QString MQTTColumnStatistics(const MQTTTopic * topic) const; AbstractColumn::ColumnMode MQTTColumnMode() const; - void readMQTTTopic(const QString& message, AbstractDataSource*); - void setPreparedForMQTT(bool, MQTTTopic*, const QString&); + void readMQTTTopic(const QString&, const QString&, AbstractDataSource*); + void setPreparedForMQTT(bool, MQTTTopic* , const QString&); #endif QString separator() const; @@ -104,8 +104,6 @@ bool removeQuotesEnabled() const; void setCreateIndexEnabled(const bool); bool createIndexEnabled() const; - void setCreateTimestampEnabled(const bool); - bool createTimestampEnabled() const; void setVectorNames(const QString&); QStringList vectorNames() const; @@ -123,7 +121,7 @@ void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*) override; - int isPrepared(); + int isPrepared(); private: std::unique_ptr const d; diff --git a/src/backend/datasources/filters/AsciiFilter.cpp b/src/backend/datasources/filters/AsciiFilter.cpp --- a/src/backend/datasources/filters/AsciiFilter.cpp +++ b/src/backend/datasources/filters/AsciiFilter.cpp @@ -4,7 +4,7 @@ Description : ASCII I/O-filter -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) -Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) +Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -41,12 +41,13 @@ #include "backend/datasources/MQTTTopic.h" #endif +#include #include #include +#include #include -#if defined(Q_OS_LINUX) || defined(Q_OS_BSD4) -#include +#ifdef Q_OS_LINUX #include #endif @@ -76,28 +77,31 @@ } #ifdef HAVE_MQTT -QVector AsciiFilter::preview(const QString& message) { - return d->preview(message); +/*! + reads the content of a message received by the topic. +*/ +void AsciiFilter::readMQTTTopic(const QString& message, const QString& topic, AbstractDataSource* dataSource) { + d->readMQTTTopic(message, topic, dataSource); } /*! - reads the content of a message received by the topic. + provides a preview of the data received by the topic. */ -void AsciiFilter::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) { - d->readMQTTTopic(message, dataSource); +void AsciiFilter::MQTTPreview(QVector& list, const QString& message, const QString& topic) { + d->MQTTPreview(list, message, topic); } /*! Returns the statistical data, that the MQTTTopic needs for the will message. */ -QString AsciiFilter::MQTTColumnStatistics(const MQTTTopic* topic) const { +QString AsciiFilter::MQTTColumnStatistics(const MQTTTopic* topic) const{ return d->MQTTColumnStatistics(topic); } /*! Returns the column mode of the last column (the value column of the MQTTTopic). */ -AbstractColumn::ColumnMode AsciiFilter::MQTTColumnMode() const { +AbstractColumn::ColumnMode AsciiFilter::MQTTColumnMode() const{ return d->MQTTColumnMode(); } @@ -134,7 +138,7 @@ return d->preview(fileName, lines); } -QVector AsciiFilter::preview(QIODevice& device) { +QVector AsciiFilter::preview(QIODevice &device) { return d->preview(device); } @@ -215,7 +219,7 @@ returns the number of columns in the file \c fileName. */ int AsciiFilter::columnNumber(const QString& fileName, const QString& separator) { - KFilterDev device(fileName); + KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << fileName.toStdString() << " for determining number of columns"); return -1; @@ -246,14 +250,15 @@ // return -1; size_t lineCount = 0; -#if defined(Q_OS_LINUX) || defined(Q_OS_BSD4) - //on linux and BSD use wc, if available, which is much faster than counting lines in the file - if (device.compressionType() == KCompressionDevice::None && !QStandardPaths::findExecutable(QLatin1String("wc")).isEmpty()) { +#ifdef Q_OS_LINUX + //on linux use wc, if available, which is much faster than counting lines in the file + if (!QStandardPaths::findExecutable(QLatin1String("wc")).isEmpty()) { QProcess wc; wc.start(QLatin1String("wc"), QStringList() << QLatin1String("-l") << fileName); size_t lineCount = 0; while (wc.waitForReadyRead()) lineCount = wc.readLine().split(' ')[0].toInt(); + lineCount++; // last line not counted return lineCount; } #endif @@ -270,7 +275,7 @@ returns the number of lines in the device \c device and 0 if sequential. resets the position to 0! */ -size_t AsciiFilter::lineNumber(QIODevice& device) const { +size_t AsciiFilter::lineNumber(QIODevice &device) { if (device.isSequential()) return 0; // if (!device.canReadLine()) @@ -278,13 +283,9 @@ size_t lineCount = 0; device.seek(0); - if (d->readingFile) - lineCount = lineNumber(d->readingFileName); - else { - while (!device.atEnd()) { - device.readLine(); - lineCount++; - } + while (!device.atEnd()) { + device.readLine(); + lineCount++; } device.seek(0); @@ -344,18 +345,10 @@ d->createIndexEnabled = b; } -bool AsciiFilter::createIndexEnabled() const { +bool AsciiFilter::createIndexEnabled() const{ return d->createIndexEnabled; } -void AsciiFilter::setCreateTimestampEnabled(bool b) { - d->createTimestampEnabled = b; -} - -bool AsciiFilter::createTimestampEnabled() const { - return d->createTimestampEnabled; -} - void AsciiFilter::setSimplifyWhitespacesEnabled(bool b) { d->simplifyWhitespacesEnabled = b; } @@ -370,7 +363,9 @@ d->nanValue = NAN; } bool AsciiFilter::NaNValueToZeroEnabled() const { - return (d->nanValue == 0); + if (d->nanValue == 0) + return true; + return false; } void AsciiFilter::setRemoveQuotesEnabled(bool b) { @@ -424,7 +419,27 @@ //##################################################################### //################### Private implementation ########################## //##################################################################### -AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner) : q(owner) { +AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner) : q(owner), + commentCharacter("#"), + separatingCharacter("auto"), + numberFormat(QLocale::C), + autoModeEnabled(true), + headerEnabled(true), + skipEmptyParts(false), + simplifyWhitespacesEnabled(true), + nanValue(NAN), + removeQuotesEnabled(false), + createIndexEnabled(false), + startRow(1), + endRow(-1), + startColumn(1), + endColumn(-1), + mqttPreviewFirstEmptyColCount (0), + m_actualStartRow(1), + m_actualRows(0), + m_actualCols(0), + m_prepared(false), + m_columnOffset(0) { } /*! @@ -456,7 +471,7 @@ int AsciiFilterPrivate::prepareDeviceToRead(QIODevice& device) { DEBUG("AsciiFilterPrivate::prepareDeviceToRead(): is sequential = " << device.isSequential() << ", can readLine = " << device.canReadLine()); - if (!device.open(QIODevice::ReadOnly)) + if (!device.open(QIODevice::ReadOnly)) return -1; if (device.atEnd() && !device.isSequential()) // empty file @@ -500,12 +515,8 @@ DEBUG(" device position after first line and comments = " << device.pos()); firstLine.remove(QRegExp("[\\n\\r]")); // remove any newline - if (removeQuotesEnabled) - firstLine = firstLine.remove(QLatin1Char('"')); - - //TODO: this doesn't work, the split below introduces whitespaces again -// if (simplifyWhitespacesEnabled) -// firstLine = firstLine.simplified(); + if (simplifyWhitespacesEnabled) + firstLine = firstLine.simplified(); DEBUG("First line: \'" << firstLine.toStdString() << '\''); // determine separator and split first line @@ -542,21 +553,17 @@ DEBUG("headerEnabled: " << headerEnabled); //optionally, remove potential spaces in the first line - //TODO: this part should be obsolete actually if we do firstLine = firstLine.simplified(); above... if (simplifyWhitespacesEnabled) { for (int i = 0; i < firstLineStringList.size(); ++i) firstLineStringList[i] = firstLineStringList[i].simplified(); } - //in GUI in AsciiOptionsWidget we start counting from 1, subtract 1 here to start from zero - m_actualStartRow = startRow - 1; - if (headerEnabled) { // use first line to name vectors vectorNames = firstLineStringList; QDEBUG("vector names =" << vectorNames); - ++m_actualStartRow; - } - + m_actualStartRow = startRow + 1; + } else + m_actualStartRow = startRow; // set range to read if (endColumn == -1) { @@ -566,16 +573,11 @@ //number of vector names provided in the import dialog (not more than the maximal number of columns in the file) endColumn = qMin(vectorNames.size(), firstLineStringList.size()); } - - if (endColumn < startColumn) - m_actualCols = 0; - else - m_actualCols = endColumn - startColumn + 1; - if (createIndexEnabled) { vectorNames.prepend(i18n("Index")); - m_actualCols++; + endColumn++; } + m_actualCols = endColumn - startColumn + 1; //TEST: readline-seek-readline fails /* qint64 testpos = device.pos(); @@ -587,8 +589,7 @@ ///////////////////////////////////////////////////////////////// // parse first data line to determine data type for each column - // if the first line was already parsed as the header, read the next line - if (headerEnabled && !device.isSequential()) + if (!device.isSequential()) firstLineStringList = getLineString(device); columnModes.resize(m_actualCols); @@ -602,7 +603,7 @@ if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); + valueString.remove(QRegExp("[\"\']")); if (col == m_actualCols) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); @@ -620,7 +621,7 @@ if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); + valueString.remove(QRegExp("[\"\']")); if (col == m_actualCols) break; AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); @@ -638,13 +639,10 @@ QDEBUG("column modes = " << columnModes); // ATTENTION: This resets the position in the device to 0 - m_actualRows = (int)q->lineNumber(device); + m_actualRows = (int)AsciiFilter::lineNumber(device); const int actualEndRow = (endRow == -1 || endRow > m_actualRows) ? m_actualRows : endRow; - if (actualEndRow > m_actualStartRow) - m_actualRows = actualEndRow - m_actualStartRow + 1; - else - m_actualRows = 0; + m_actualRows = actualEndRow - m_actualStartRow + 1; DEBUG("start/end column: " << startColumn << ' ' << endColumn); DEBUG("start/end row: " << m_actualStartRow << ' ' << actualEndRow); @@ -663,14 +661,8 @@ DEBUG("AsciiFilterPrivate::readDataFromFile(): fileName = \'" << fileName.toStdString() << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode)); - //dirty hack: set readingFile and readingFileName in order to know in lineNumber(QIODevice) - //that we're reading from a file and to benefit from much faster wc on linux - //TODO: redesign the APIs and remove this later - readingFile = true; - readingFileName = fileName; KFilterDev device(fileName); readDataFromDevice(device, dataSource, importMode); - readingFile = false; } qint64 AsciiFilterPrivate::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { @@ -873,14 +865,13 @@ } QDEBUG(" data read: " << newData); } - //now we reset the readingType if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd) readingType = spreadsheet->readingType(); //we had less new lines than the sample size specified if (readingType != LiveDataSource::ReadingType::TillEnd) - QDEBUG(" Removed empty lines: " << newData.removeAll(QString())); + QDEBUG(" Removed empty lines: " << newData.removeAll("")); //back to the last read position before counting when reading from files if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) @@ -1114,7 +1105,6 @@ } } - QLocale locale(numberFormat); for (; row < linesToRead; ++row) { DEBUG("\n Reading row " << row + 1 << " of " << linesToRead); QString line; @@ -1137,6 +1127,8 @@ if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; + QLocale locale(numberFormat); + QStringList lineStringList; // only FileOrPipe support multiple columns if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) @@ -1157,8 +1149,6 @@ DEBUG(" actual col = " << n); if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); - if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); DEBUG(" value string = " << valueString.toStdString()); // set value depending on data type @@ -1187,6 +1177,8 @@ break; } case AbstractColumn::Text: + if (removeQuotesEnabled) + valueString.remove(QRegExp("[\"\']")); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: @@ -1209,7 +1201,7 @@ static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: - static_cast*>(m_dataContainer[n])->operator[](currentRow).clear(); + static_cast*>(m_dataContainer[n])->operator[](currentRow) = ""; break; case AbstractColumn::Month: //TODO @@ -1252,8 +1244,9 @@ plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } - } else - m_prepared = true; + } + + m_prepared = true; DEBUG("AsciiFilterPrivate::readFromLiveDevice() DONE"); return bytesread; @@ -1297,28 +1290,18 @@ lines = m_actualRows; //skip data lines, if required - DEBUG(" Skipping " << m_actualStartRow << " lines"); - for (int i = 0; i < m_actualStartRow; ++i) + DEBUG(" Skipping " << m_actualStartRow - 1 << " lines"); + for (int i = 0; i < m_actualStartRow - 1; ++i) device.readLine(); - DEBUG(" Reading " << qMin(lines, m_actualRows) << " lines, " << m_actualCols << " columns"); - - if (qMin(lines, m_actualRows) == 0 || m_actualCols == 0) - return; - + DEBUG(" Reading " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); ++i) { QString line = device.readLine(); - // remove any newline - line.remove(QLatin1Char('\n')); - line.remove(QLatin1Char('\r')); - + line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); - if (removeQuotesEnabled) - line.remove(QLatin1Char('"')); - if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; @@ -1342,11 +1325,8 @@ continue; } - //column counting starts with 1, substract 1 as well as another 1 for the index column if required - int col = createIndexEnabled ? n + startColumn - 2: n + startColumn - 1; - - if (col < lineStringList.size()) { - QString valueString = lineStringList.at(col); + if ((createIndexEnabled ? n - 1 : n) < lineStringList.size()) { + QString valueString = lineStringList.at(createIndexEnabled ? n - 1 : n); // set value depending on data type switch (columnModes[n]) { @@ -1368,6 +1348,8 @@ break; } case AbstractColumn::Text: { + if (removeQuotesEnabled) + valueString.remove(QRegExp("[\"\']")); QVector* colData = static_cast*>(m_dataContainer[n]); colData->operator[](currentRow) = valueString; break; @@ -1388,7 +1370,7 @@ static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: - static_cast*>(m_dataContainer[n])->operator[](currentRow).clear(); + static_cast*>(m_dataContainer[n])->operator[](currentRow) = ""; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: @@ -1402,7 +1384,7 @@ } DEBUG(" Read " << currentRow << " lines"); - dataSource->finalizeImport(m_columnOffset, startColumn, startColumn + m_actualCols - 1, currentRow, dateTimeFormat, importMode); + dataSource->finalizeImport(m_columnOffset, startColumn, endColumn, currentRow, dateTimeFormat, importMode); } /*! @@ -1461,10 +1443,6 @@ for (int i = 0; i < linesToRead; ++i) { QString line = newData.at(i); - // remove any newline - line = line.remove('\n'); - line = line.remove('\r'); - if (simplifyWhitespacesEnabled) line = line.simplified(); @@ -1481,8 +1459,6 @@ for (int n = 0; n < lineStringList.size(); ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); - if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); switch (columnModes[n]) { case AbstractColumn::Numeric: { @@ -1503,6 +1479,8 @@ break; } case AbstractColumn::Text: + if (removeQuotesEnabled) + valueString.remove(QRegExp("[\"\']")); lineString += valueString; break; case AbstractColumn::Month: // never happens @@ -1510,7 +1488,7 @@ break; } } else // missing columns in this line - lineString += QString(); + lineString += QLatin1String(""); } dataStrings << lineString; } @@ -1524,15 +1502,8 @@ QVector AsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; - //dirty hack: set readingFile and readingFileName in order to know in lineNumber(QIODevice) - //that we're reading from a file and to benefit from much faster wc on linux - //TODO: redesign the APIs and remove this later - readingFile = true; - readingFileName = fileName; KFilterDev device(fileName); const int deviceError = prepareDeviceToRead(device); - readingFile = false; - if (deviceError != 0) { DEBUG("Device error = " << deviceError); return dataStrings; @@ -1552,29 +1523,27 @@ if (createIndexEnabled) start = 1; for (int i = start; i < m_actualCols; i++) - vectorNames << "Column " + QString::number(i + 1); + vectorNames << "Column " + QString::number(i + 1); } QDEBUG(" column names = " << vectorNames); //skip data lines, if required - DEBUG(" Skipping " << m_actualStartRow << " lines"); - for (int i = 0; i < m_actualStartRow; ++i) + DEBUG(" Skipping " << m_actualStartRow - 1 << " lines"); + for (int i = 0; i < m_actualStartRow - 1; ++i) device.readLine(); DEBUG(" Generating preview for " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); ++i) { QString line = device.readLine(); - // remove any newline - line = line.remove('\n'); - line = line.remove('\r'); - + line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; + const QStringList& lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); QDEBUG(" line = " << lineStringList); @@ -1586,14 +1555,8 @@ continue; } - //column counting starts with 1, substract 1 as well as another 1 for the index column if required - int col = createIndexEnabled ? n + startColumn - 2: n + startColumn - 1; - - if (col < lineStringList.size()) { - QString valueString = lineStringList.at(col); - if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); - + if ((createIndexEnabled ? n - 1 : n) < lineStringList.size()) { + QString valueString = lineStringList.at(createIndexEnabled ? n - 1 : n); //DEBUG(" valueString = " << valueString.toStdString()); if (skipEmptyParts && !QString::compare(valueString, " ")) // handle left white spaces continue; @@ -1618,6 +1581,8 @@ break; } case AbstractColumn::Text: + if (removeQuotesEnabled) + valueString.remove(QRegExp("[\"\']")); lineString += valueString; break; case AbstractColumn::Month: // never happens @@ -1625,7 +1590,7 @@ break; } } else // missing columns in this line - lineString += QString(); + lineString += QLatin1String(""); } dataStrings << lineString; @@ -1656,7 +1621,6 @@ writer->writeAttribute( "separatingCharacter", d->separatingCharacter); writer->writeAttribute( "autoMode", QString::number(d->autoModeEnabled)); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled)); - writer->writeAttribute( "createTimestamp", QString::number(d->createTimestampEnabled)); writer->writeAttribute( "header", QString::number(d->headerEnabled)); writer->writeAttribute( "vectorNames", d->vectorNames.join(' ')); writer->writeAttribute( "skipEmptyParts", QString::number(d->skipEmptyParts)); @@ -1667,6 +1631,7 @@ writer->writeAttribute( "endRow", QString::number(d->endRow)); writer->writeAttribute( "startColumn", QString::number(d->startColumn)); writer->writeAttribute( "endColumn", QString::number(d->endColumn)); + writer->writeEndElement(); } @@ -1676,198 +1641,369 @@ bool AsciiFilter::load(XmlStreamReader* reader) { KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); - QString str; - READ_STRING_VALUE("commentCharacter", commentCharacter); - READ_STRING_VALUE("separatingCharacter", separatingCharacter); - - READ_INT_VALUE("createIndex", createIndexEnabled, bool); - READ_INT_VALUE("createTimestamp", createTimestampEnabled, bool); - READ_INT_VALUE("autoMode", autoModeEnabled, bool); - READ_INT_VALUE("header", headerEnabled, bool); + QString str = attribs.value("commentCharacter").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("commentCharacter").toString()); + else + d->commentCharacter = str; - str = attribs.value("vectorNames").toString(); - d->vectorNames = str.split(' '); //may be empty + str = attribs.value("separatingCharacter").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("separatingCharacter").toString()); + else + d->separatingCharacter = str; - READ_INT_VALUE("simplifyWhitespaces", simplifyWhitespacesEnabled, bool); - READ_DOUBLE_VALUE("nanValue", nanValue); - READ_INT_VALUE("removeQuotes", removeQuotesEnabled, bool); - READ_INT_VALUE("skipEmptyParts", skipEmptyParts, bool); - READ_INT_VALUE("startRow", startRow, int); - READ_INT_VALUE("endRow", endRow, int); - READ_INT_VALUE("startColumn", startColumn, int); - READ_INT_VALUE("endColumn", endColumn, int); - return true; -} + str = attribs.value("createIndex").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("createIndex").toString()); + else + d->createIndexEnabled = str.toInt(); -int AsciiFilterPrivate::isPrepared() { - return m_prepared; -} + str = attribs.value("autoMode").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("autoMode").toString()); + else + d->autoModeEnabled = str.toInt(); -#ifdef HAVE_MQTT -int AsciiFilterPrivate::prepareToRead(const QString& message) { - QStringList lines = message.split('\n'); - if (lines.isEmpty()) - return 1; + str = attribs.value("header").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("header").toString()); + else + d->headerEnabled = str.toInt(); - // Parse the first line: - // Determine the number of columns, create the columns and use (if selected) the first row to name them - QString firstLine = lines.at(0); - if (simplifyWhitespacesEnabled) - firstLine = firstLine.simplified(); - DEBUG("First line: \'" << firstLine.toStdString() << '\''); + str = attribs.value("vectorNames").toString(); + d->vectorNames = str.split(' '); //may be empty - // determine separator and split first line - QStringList firstLineStringList; - if (separatingCharacter == "auto") { - DEBUG("automatic separator"); - QRegExp regExp("(\\s+)|(,\\s+)|(;\\s+)|(:\\s+)"); - firstLineStringList = firstLine.split(regExp, (QString::SplitBehavior)skipEmptyParts); - } else { // use given separator - // replace symbolic "TAB" with '\t' - m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), "\t\t", Qt::CaseInsensitive); - m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); - // replace symbolic "SPACE" with ' ' - m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); - m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); - m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); - m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); - firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); - } - DEBUG("separator: \'" << m_separator.toStdString() << '\''); - DEBUG("number of columns: " << firstLineStringList.size()); - QDEBUG("first line: " << firstLineStringList); + str = attribs.value("simplifyWhitespaces").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("simplifyWhitespaces").toString()); + else + d->simplifyWhitespacesEnabled = str.toInt(); - //all columns are read plus the optional column for the index and for the timestamp - m_actualCols = firstLineStringList.size() + int(createIndexEnabled) + int(createTimestampEnabled); + str = attribs.value("nanValue").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("nanValue").toString()); + else + d->nanValue = str.toDouble(); - //column names: - //when reading the message strings for different topics, it's not possible to specify vector names - //since the different topics can have different content and different number of columns/vectors - //->we always set the vector names here to fixed values - vectorNames.clear(); - columnModes.clear(); + str = attribs.value("removeQuotes").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("removeQuotes").toString()); + else + d->removeQuotesEnabled = str.toInt(); - //add index column - if (createIndexEnabled) { - vectorNames << i18n("index"); - columnModes << AbstractColumn::Integer; - } + str = attribs.value("skipEmptyParts").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("skipEmptyParts").toString()); + else + d->skipEmptyParts = str.toInt(); - //add timestamp column - if (createTimestampEnabled) { - vectorNames << i18n("timestamp"); - columnModes << AbstractColumn::DateTime; - } + str = attribs.value("startRow").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("startRow").toString()); + else + d->startRow = str.toInt(); - //parse the first data line to determine data type for each column - int i = 1; - for (auto& valueString : firstLineStringList) { - if (simplifyWhitespacesEnabled) - valueString = valueString.simplified(); - if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); + str = attribs.value("endRow").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("endRow").toString()); + else + d->endRow = str.toInt(); - vectorNames << i18n("value %1").arg(i); - columnModes << AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); - ++i; - } + str = attribs.value("startColumn").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("startColumn").toString()); + else + d->startColumn = str.toInt(); - m_actualStartRow = startRow; - m_actualRows = lines.size(); + str = attribs.value("endColumn").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("endColumn").toString()); + else + d->endColumn = str.toInt(); - QDEBUG("column modes = " << columnModes); - DEBUG("actual cols/rows (w/o header): " << m_actualCols << ' ' << m_actualRows); + return true; +} - return 0; +int AsciiFilterPrivate::isPrepared() { + return m_prepared; } +#ifdef HAVE_MQTT + /*! - * generates the preview for the string \s message. + * \brief Offers a preview about the data received by the topic + * \param list + * \param message + * \param topic */ -QVector AsciiFilterPrivate::preview(const QString& message) { +void AsciiFilterPrivate::MQTTPreview(QVector& list, const QString& message, const QString& topic) { QVector dataStrings; - prepareToRead(message); - //number formatting - DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); - QLocale locale(numberFormat); + if (!message.isEmpty()) { + qDebug()<<"ascii mqtt preview for: " << topic; - // Read the data - QStringList lines = message.split('\n'); - int i = 0; - for (auto line : lines) { - if (simplifyWhitespacesEnabled) - line = line.simplified(); + //check how many lines can be read + int linesToRead = 0; + QVector newData; + QStringList newDataList = message.split(QRegExp("\n|\r\n|\r"), QString::SkipEmptyParts); + for (const auto& valueString : newDataList) { + const QStringList splitString = valueString.split(' ', QString::SkipEmptyParts); + for (const auto& valueString2 : splitString) { + if (!valueString2.isEmpty() && !valueString2.startsWith(commentCharacter) ) { + linesToRead++; + newData.push_back(valueString2); + } + } + } + qDebug() <<" data investigated, lines to read: "< numeric + if (mode == AbstractColumn::Numeric && columnModes[colSize - 1] == AbstractColumn::Integer) { + columnModes[colSize - 1] = mode; + } + // text: non text -> text + if ( (mode == AbstractColumn::Text) && (columnModes[colSize - 1] != AbstractColumn::Text) ) { + columnModes[colSize - 1] = mode; + } + } + int forStart = 0; + int forEnd = 0; + + if (list.isEmpty()) { + forStart = 0; + forEnd = linesToRead; + } + else { + if (mqttPreviewFirstEmptyColCount == 0) { + dataStrings = list; + forStart = 0; + forEnd = dataStrings.size(); + } + else { + forStart = 1; + forEnd = linesToRead; + dataStrings = list; + const QLocale locale(numberFormat); + //this is the first column having values, after at least 1 empty column + //until now only one row was filled with NaN values, so for starters we fill this row with the new value + switch (columnModes[colSize - 1]) { case AbstractColumn::Numeric: { bool isNumber; - const double value = locale.toDouble(valueString, &isNumber); - lineString += QString::number(isNumber ? value : nanValue, 'g', 15); + const double value = locale.toDouble(newData[0], &isNumber); + dataStrings[0] += QString::number(isNumber ? value : nanValue, 'g', 16); break; } case AbstractColumn::Integer: { bool isNumber; - const int value = locale.toInt(valueString, &isNumber); - lineString += QString::number(isNumber ? value : 0); + const int value = locale.toInt(newData[0], &isNumber); + dataStrings[0] += QString::number(isNumber ? value : 0); break; } case AbstractColumn::DateTime: { - const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); - lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); + const QDateTime valueDateTime = QDateTime::fromString(newData[0], dateTimeFormat); + dataStrings[0] += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); - lineString += valueString; + newData[0].remove(QRegExp("[\"\']")); + dataStrings[0] += newData[0]; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } - } else // missing columns in this line - lineString += QString(); + } } + //We add every forEnd-forStart values to the list + for (int i = forStart; i < forEnd; ++i) { + QString line = newData[i]; - ++i; - dataStrings << lineString; + if (simplifyWhitespacesEnabled) + line = line.simplified(); + + //skip empty or commented lines + if (line.isEmpty() || line.startsWith(commentCharacter)) + continue; + + QLocale locale(numberFormat); + + QStringList lineString; + //We don't have to do any preparation if there were already data containing topics + if (!list.isEmpty() && mqttPreviewFirstEmptyColCount == 0) + lineString = dataStrings[i]; + + //Add index if it is the case + if (list.isEmpty() || mqttPreviewFirstEmptyColCount > 0) + if (createIndexEnabled) + lineString += QString::number(i); + + //If this is the first not empty topic, add nan value to all the empty topics before this one + if (!list.isEmpty() && mqttPreviewFirstEmptyColCount > 0) { + for (int j = 0; j < mqttPreviewFirstEmptyColCount; j++) { + lineString += QString::number(nanValue, 'g', 16); + } + } + + //Add the actual value to the topic + switch (columnModes[colSize - 1]) { + case AbstractColumn::Numeric: { + bool isNumber; + const double value = locale.toDouble(line, &isNumber); + lineString += QString::number(isNumber ? value : nanValue, 'g', 16); + break; + } + case AbstractColumn::Integer: { + bool isNumber; + const int value = locale.toInt(line, &isNumber); + lineString += QString::number(isNumber ? value : 0); + break; + } + case AbstractColumn::DateTime: { + const QDateTime valueDateTime = QDateTime::fromString(line, dateTimeFormat); + lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); + break; + } + case AbstractColumn::Text: + if (removeQuotesEnabled) + line.remove(QRegExp("[\"\']")); + lineString += line; + break; + case AbstractColumn::Month: // never happens + case AbstractColumn::Day: + break; + } + qDebug()<<"column updated with value"; + + // if the list was empty, or this is the first not empty topic, we append the new line + if (list.isEmpty() || mqttPreviewFirstEmptyColCount > 0) + dataStrings << lineString; + //Otherwise we update the already existing line + if (!list.isEmpty() && mqttPreviewFirstEmptyColCount == 0) + dataStrings[i] = lineString; + } + //The first empty topics were taken care of + if (mqttPreviewFirstEmptyColCount > 0) { + mqttPreviewFirstEmptyColCount = 0; + } } + //the message is empty and there were only empty messages before this + else if (list.isEmpty() || mqttPreviewFirstEmptyColCount > 0) { + //increment the counter + mqttPreviewFirstEmptyColCount ++; + + //determine number of columns + int colSize; + if (mqttPreviewFirstEmptyColCount == 1) { + if (createIndexEnabled) + colSize = 2; + else colSize = 1; + } + else + colSize = columnModes.size() + 1; + columnModes.resize(colSize); - return dataStrings; + //add index if needed + if (mqttPreviewFirstEmptyColCount == 1) + if (createIndexEnabled) { + columnModes[0] = AbstractColumn::ColumnMode::Integer; + vectorNames.prepend("index"); + } + + //Add new column fot the mepty topic + vectorNames.append( topic); + columnModes[colSize-1] = AbstractColumn::ColumnMode::Numeric; + + //Add a NaN value to the topic's column + QStringList lineString; + if (mqttPreviewFirstEmptyColCount == 1) { + //Add index if needed + if (createIndexEnabled) + lineString += QString::number(0); + lineString += QString::number(nanValue, 'g', 16); + //Append since this is the first empty column + dataStrings << lineString; + } + else { + //Update the already existing line + dataStrings = list; + dataStrings[0] += QString::number(nanValue, 'g', 16); + } + } + //The message is empty but there already was a non empty message + else if (!list.isEmpty()) { + //Append vector name + vectorNames.append( topic); + + int colSize = columnModes.size() + 1; + columnModes.resize(colSize); + columnModes[colSize-1] = AbstractColumn::ColumnMode::Numeric; + dataStrings = list; + //Add as many NaN values as many lines the list already has + for (int i = 0; i < dataStrings.size(); ++i) { + dataStrings[i] += QString::number(nanValue, 'g', 16); + } + } + //update the list + list = dataStrings; } /*! * \brief Returns the statistical data that is needed by the topic for its MQTTClient's will message * \param topic */ -QString AsciiFilterPrivate::MQTTColumnStatistics(const MQTTTopic* topic) const { +QString AsciiFilterPrivate::MQTTColumnStatistics(const MQTTTopic* topic) const{ Column* const tempColumn = topic->child(m_actualCols - 1); QString statistics; @@ -1930,7 +2066,7 @@ return statistics; } -AbstractColumn::ColumnMode AsciiFilterPrivate::MQTTColumnMode() const { +AbstractColumn::ColumnMode AsciiFilterPrivate::MQTTColumnMode() const{ return columnModes[m_actualCols - 1]; } @@ -1941,7 +2077,7 @@ * \param topic * \param dataSource */ -void AsciiFilterPrivate::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) { +void AsciiFilterPrivate::readMQTTTopic(const QString& message, const QString& topic, AbstractDataSource*dataSource) { //If the message is empty, there is nothing to do if (message.isEmpty()) { DEBUG("No new data available"); @@ -1950,13 +2086,13 @@ MQTTTopic* spreadsheet = dynamic_cast(dataSource); - const int keepNValues = spreadsheet->mqttClient()->keepNValues(); + const int keepNValues = spreadsheet->keepNValues(); if (!m_prepared) { qDebug()<<"Start preparing filter for: " << spreadsheet->topicName(); //Prepare the filter - const int mqttPrepareError = prepareToRead(message); + const int mqttPrepareError = prepareMQTTTopicToRead(message, topic); if (mqttPrepareError != 0) { DEBUG("Mqtt Prepare Error = " << mqttPrepareError); qDebug()<setRowCount(m_actualRows > 1 ? m_actualRows : 1); else { - spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); - m_actualRows = spreadsheet->mqttClient()->keepNValues(); + spreadsheet->setRowCount(spreadsheet->keepNValues()); + m_actualRows = spreadsheet->keepNValues(); } m_dataContainer.resize(m_actualCols); @@ -2018,7 +2154,7 @@ m_dataContainer[n] = static_cast(vector); break; } - //TODO + //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; @@ -2037,10 +2173,11 @@ } else { //we have to read all the data when reading from end //so we set readingType to TillEnd - if (static_cast (spreadsheet->mqttClient()->readingType()) == MQTTClient::ReadingType::FromEnd) + if (spreadsheet->readingType() == MQTTClient::ReadingType::FromEnd) { readingType = MQTTClient::ReadingType::TillEnd; - else - readingType = spreadsheet->mqttClient()->readingType(); + } else { + readingType = static_cast(spreadsheet->readingType()); + } } //count the new lines, increase actualrows on each @@ -2050,40 +2187,57 @@ int newLinesTillEnd = 0; QVector newData; if (readingType != MQTTClient::ReadingType::TillEnd) { - newData.reserve(spreadsheet->mqttClient()->sampleSize()); - newData.resize(spreadsheet->mqttClient()->sampleSize()); + newData.reserve(spreadsheet->sampleSize()); + newData.resize(spreadsheet->sampleSize()); } int newDataIdx = 0; - //TODO: bool sampleSizeReached = false; + bool sampleSizeReached = false; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportReadingFromFile: "); #endif QStringList newDataList = message.split(QRegExp("\n|\r\n|\r"), QString::SkipEmptyParts); - for (auto& line : newDataList) { - newData.push_back(line); - newLinesTillEnd++; - - if (readingType != MQTTClient::ReadingType::TillEnd) { - newLinesForSampleSizeNotTillEnd++; - //for Continuous reading and FromEnd we read sample rate number of lines if possible - if (newLinesForSampleSizeNotTillEnd == spreadsheet->mqttClient()->sampleSize()) { - //TODO: sampleSizeReached = true; - break; + for (auto& valueString : newDataList) { + if (!valueString.startsWith(commentCharacter)) { + const QStringList splitString = valueString.split(m_separator, static_cast(skipEmptyParts)); + + for (const auto& valueString2 : splitString) { + if (!valueString2.isEmpty() && !valueString2.startsWith(commentCharacter)) { + + if (readingType != MQTTClient::ReadingType::TillEnd) + newData[newDataIdx++] = valueString2; + else + newData.push_back(valueString2); + newLinesTillEnd++; + + if (readingType != MQTTClient::ReadingType::TillEnd) { + newLinesForSampleSizeNotTillEnd++; + //for Continuous reading and FromEnd we read sample rate number of lines if possible + if (newLinesForSampleSizeNotTillEnd == spreadsheet->sampleSize()) { + sampleSizeReached = true; + break; + } + } + } } + //if the sample size limit is reached we brake from the outer loop as well + if (sampleSizeReached) + break; } } } qDebug()<<"Processing message done"; //now we reset the readingType - if (spreadsheet->mqttClient()->readingType() == MQTTClient::ReadingType::FromEnd) - readingType = static_cast(spreadsheet->mqttClient()->readingType()); + if (static_cast(spreadsheet->readingType()) == MQTTClient::ReadingType::FromEnd) + readingType = static_cast(spreadsheet->readingType()); //we had less new lines than the sample rate specified if (readingType != MQTTClient::ReadingType::TillEnd) - qDebug() << "Removed empty lines: " << newData.removeAll(QString()); + qDebug() << "Removed empty lines: " << newData.removeAll(""); + + qDebug()<<"Create index enabled: "<rowCount(); @@ -2092,19 +2246,20 @@ m_actualRows = spreadsheetRowCountBeforeResize; else { //if the keepNValues changed since the last read we have to manage the columns accordingly - if (m_actualRows != spreadsheet->mqttClient()->keepNValues()) { - if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { - spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); - qDebug()<<"rowcount set to: " << spreadsheet->mqttClient()->keepNValues(); + if (m_actualRows != spreadsheet->keepNValues()) { + if (m_actualRows < spreadsheet->keepNValues()) { + spreadsheet->setRowCount(spreadsheet->keepNValues()); + qDebug()<<"rowcount set to: " << spreadsheet->keepNValues(); } //Calculate the difference between the old and new keepNValues int rowDiff = 0; - if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) - rowDiff = m_actualRows - spreadsheet->mqttClient()->keepNValues(); - - if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) - rowDiff = spreadsheet->mqttClient()->keepNValues() - m_actualRows; + if (m_actualRows > spreadsheet->keepNValues()) { + rowDiff = m_actualRows - spreadsheet->keepNValues(); + } + if (m_actualRows < spreadsheet->keepNValues()) { + rowDiff = spreadsheet->keepNValues() - m_actualRows; + } for (int n = 0; n < columnModes.size(); ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) @@ -2115,25 +2270,26 @@ //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places - if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { - for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { + if (m_actualRows > spreadsheet->keepNValues()) { + for (int i = 0; i < spreadsheet->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = - static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); + static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with NaN - if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { - vector->reserve( spreadsheet->mqttClient()->keepNValues()); - vector->resize( spreadsheet->mqttClient()->keepNValues()); + if (m_actualRows < spreadsheet->keepNValues()) { + vector->reserve( spreadsheet->keepNValues()); + vector->resize( spreadsheet->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { - static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = - static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); + static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->keepNValues() - i) = + static_cast*>(m_dataContainer[n])->operator[](spreadsheet->keepNValues() - i - rowDiff); } - for (int i = 0; i < rowDiff; i++) + for (int i = 0; i < rowDiff; i++) { static_cast*>(m_dataContainer[n])->operator[](i) = nanValue; + } } break; } @@ -2143,24 +2299,25 @@ //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places - if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { - for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { + if (m_actualRows > spreadsheet->keepNValues()) { + for (int i = 0; i < spreadsheet->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = - static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); + static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with 0 - if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { - vector->reserve( spreadsheet->mqttClient()->keepNValues()); - vector->resize( spreadsheet->mqttClient()->keepNValues()); + if (m_actualRows < spreadsheet->keepNValues()) { + vector->reserve( spreadsheet->keepNValues()); + vector->resize( spreadsheet->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { - static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = - static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); + static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->keepNValues() - i) = + static_cast*>(m_dataContainer[n])->operator[](spreadsheet->keepNValues() - i - rowDiff); } - for (int i = 0; i < rowDiff; i++) + for (int i = 0; i < rowDiff; i++) { static_cast*>(m_dataContainer[n])->operator[](i) = 0; + } } break; } @@ -2170,24 +2327,24 @@ //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places - if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { - for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { + if (m_actualRows > spreadsheet->keepNValues()) { + for (int i = 0; i < spreadsheet->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = - static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); + static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions - //then fill the remaining lines with empty lines - if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { - vector->reserve( spreadsheet->mqttClient()->keepNValues()); - vector->resize( spreadsheet->mqttClient()->keepNValues()); + //then fill the remaining lines with "" + if (m_actualRows < spreadsheet->keepNValues()) { + vector->reserve( spreadsheet->keepNValues()); + vector->resize( spreadsheet->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { - static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = - static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); + static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->keepNValues() - i) = + static_cast*>(m_dataContainer[n])->operator[](spreadsheet->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) - static_cast*>(m_dataContainer[n])->operator[](i).clear(); + static_cast*>(m_dataContainer[n])->operator[](i) = ""; } break; } @@ -2197,39 +2354,39 @@ //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places - if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { - for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { + if (m_actualRows > spreadsheet->keepNValues()) { + for (int i = 0; i < spreadsheet->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = - static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); + static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with null datetime - if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { - vector->reserve( spreadsheet->mqttClient()->keepNValues()); - vector->resize( spreadsheet->mqttClient()->keepNValues()); + if (m_actualRows < spreadsheet->keepNValues()) { + vector->reserve( spreadsheet->keepNValues()); + vector->resize( spreadsheet->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { - static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = - static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); + static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->keepNValues() - i) = + static_cast*>(m_dataContainer[n])->operator[](spreadsheet->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = QDateTime(); } break; } - //TODO + //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } //if the keepNValues got smaller resize the spreadsheet - if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) - spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); + if (m_actualRows > spreadsheet->keepNValues()) + spreadsheet->setRowCount(spreadsheet->keepNValues()); //set the new row count - m_actualRows = spreadsheet->mqttClient()->keepNValues(); + m_actualRows = spreadsheet->keepNValues(); qDebug()<<"actual rows: "<mqttClient()->sampleSize()); + m_actualRows += qMin(newData.size(), spreadsheet->sampleSize()); else { m_actualRows += newData.size(); } @@ -2262,21 +2419,25 @@ } else { //we read max sample size number of lines when the reading mode //is ContinuouslyFixed or FromEnd - if (spreadsheet->mqttClient()->sampleSize() <= spreadsheet->mqttClient()->keepNValues()) - linesToRead = qMin(spreadsheet->mqttClient()->sampleSize(), newLinesTillEnd); + if (spreadsheet->sampleSize() <= spreadsheet->keepNValues()) + linesToRead = qMin(spreadsheet->sampleSize(), newLinesTillEnd); else - linesToRead = qMin(spreadsheet->mqttClient()->keepNValues(), newLinesTillEnd); + linesToRead = qMin(spreadsheet->keepNValues(), newLinesTillEnd); } - } else + } else { linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; + } if (linesToRead == 0) return; + } else { - if (keepNValues != 0) + if (keepNValues != 0) { linesToRead = newLinesTillEnd > m_actualRows ? m_actualRows : newLinesTillEnd; - else + } + else { linesToRead = newLinesTillEnd; + } } qDebug()<<"linestoread = " << linesToRead; @@ -2330,7 +2491,7 @@ m_dataContainer[n] = static_cast(vector); break; } - //TODO + //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; @@ -2345,8 +2506,9 @@ if (readingType == MQTTClient::ReadingType::TillEnd) { if (newLinesTillEnd > m_actualRows) currentRow = 0; - else + else { currentRow = m_actualRows - newLinesTillEnd; + } } else { //we read max sample rate number of lines when the reading mode //is ContinuouslyFixed or FromEnd @@ -2393,7 +2555,7 @@ m_dataContainer[col] = static_cast(vector); break; } - //TODO + //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; @@ -2410,21 +2572,18 @@ //From end means that we read the last sample size amount of data if (readingType == MQTTClient::ReadingType::FromEnd) { if (m_prepared) { - if (newData.size() > spreadsheet->mqttClient()->sampleSize()) - newDataIdx = newData.size() - spreadsheet->mqttClient()->sampleSize(); + if (newData.size() > spreadsheet->sampleSize()) + newDataIdx = newData.size() - spreadsheet->sampleSize(); } } qDebug() << "newDataIdx: " << newDataIdx; - - //read the data static int indexColumnIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportFillingContainers: "); #endif int row = 0; - QLocale locale(numberFormat); for (; row < linesToRead; ++row) { QString line; if (readingType == MQTTClient::ReadingType::FromEnd) @@ -2436,93 +2595,98 @@ line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) + { + qDebug()<<"found empty line "<*>(m_dataContainer[0])->operator[](currentRow) = index; - ++offset; + col = 1; + QString tempIndex; + if (keepNValues != 0) + tempIndex = QString::number(indexColumnIdx++); + else + tempIndex = QString::number(currentRow); + switch (columnModes[0]) { + case AbstractColumn::Numeric: { + bool isNumber; + const double value = locale.toDouble(tempIndex, &isNumber); + static_cast*>(m_dataContainer[0])->operator[](currentRow) = (isNumber ? value : nanValue); + break; + } + case AbstractColumn::Integer: { + bool isNumber; + const int value = locale.toInt(tempIndex, &isNumber); + static_cast*>(m_dataContainer[0])->operator[](currentRow) = (isNumber ? value : 0); + break; + } + case AbstractColumn::DateTime: { + const QDateTime valueDateTime = QDateTime::fromString(tempIndex, dateTimeFormat); + static_cast*>(m_dataContainer[0])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); + break; + } + case AbstractColumn::Text: + if (removeQuotesEnabled) + tempIndex.remove(QRegExp("[\"\']")); + static_cast*>(m_dataContainer[0])->operator[](currentRow) = tempIndex; + break; + case AbstractColumn::Month: + //TODO + break; + case AbstractColumn::Day: + //TODO + break; + } } - //add current timestamp if required - if (createTimestampEnabled) { - static_cast*>(m_dataContainer[offset])->operator[](currentRow) = QDateTime::currentDateTime(); - ++offset; - } + //setting timestamp on current time + static_cast*>(m_dataContainer[col])->operator[](currentRow) = QDateTime::currentDateTime(); - //parse the columns - QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); - qDebug()<<"########################################################################"; - qDebug()<*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : nanValue); - break; - } - case AbstractColumn::Integer: { - bool isNumber; - const int value = locale.toInt(valueString, &isNumber); - static_cast*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : 0); - break; - } - case AbstractColumn::DateTime: { - const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); - static_cast*>(m_dataContainer[col])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); - break; - } - case AbstractColumn::Text: - if (removeQuotesEnabled) - valueString.remove(QLatin1Char('"')); - static_cast*>(m_dataContainer[col])->operator[](currentRow) = valueString; - break; - case AbstractColumn::Month: - //TODO - break; - case AbstractColumn::Day: - //TODO - break; - } - } else { - DEBUG(" missing columns in this line"); - switch (columnModes[n]) { - case AbstractColumn::Numeric: - static_cast*>(m_dataContainer[col])->operator[](currentRow) = nanValue; - break; - case AbstractColumn::Integer: - static_cast*>(m_dataContainer[col])->operator[](currentRow) = 0; - break; - case AbstractColumn::DateTime: - static_cast*>(m_dataContainer[col])->operator[](currentRow) = QDateTime(); - break; - case AbstractColumn::Text: - static_cast*>(m_dataContainer[col])->operator[](currentRow).clear(); - break; - case AbstractColumn::Month: - //TODO - break; - case AbstractColumn::Day: - //TODO - break; - } - } + // set value depending on data type + switch (columnModes[m_actualCols - 1]) { + case AbstractColumn::Numeric: { + bool isNumber; + const double value = locale.toDouble(valueString, &isNumber); + static_cast*>(m_dataContainer[m_actualCols - 1])->operator[](currentRow) = (isNumber ? value : nanValue); + break; } + case AbstractColumn::Integer: { + bool isNumber; + const int value = locale.toInt(valueString, &isNumber); + static_cast*>(m_dataContainer[m_actualCols - 1])->operator[](currentRow) = (isNumber ? value : 0); + break; + } + case AbstractColumn::DateTime: { + const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); + static_cast*>(m_dataContainer[m_actualCols - 1])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); + break; + } + case AbstractColumn::Text: + if (removeQuotesEnabled) + valueString.remove(QRegExp("[\"\']")); + static_cast*>(m_dataContainer[m_actualCols - 1])->operator[](currentRow) = valueString; + break; + case AbstractColumn::Month: + //TODO + break; + case AbstractColumn::Day: + //TODO + break; + } + currentRow++; } } if (m_prepared) { + qDebug()<<"notifying plots"; //notify all affected columns and plots about the changes PERFTRACE("AsciiLiveDataImport, notify affected columns and plots"); @@ -2530,6 +2694,7 @@ QVector curves = project->children(AbstractAspect::Recursive); QVector plots; + qDebug()<<"Uploading plots"; for (int n = 0; n < m_actualCols; ++n) { Column* column = spreadsheet->column(n); @@ -2553,10 +2718,174 @@ // plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } - } else + } + + //Set prepared true if needed + if (!m_prepared) m_prepared = true; +} + +/*! + * \brief Prepares the filter to read from messages received by the MQTTTopic. + * Returns whether the preparation was successful or not + * \param message + * \param topic + */ +int AsciiFilterPrivate::prepareMQTTTopicToRead(const QString& message, const QString& topic) { + Q_UNUSED(topic) + vectorNames.append("value"); + if (endColumn == -1) + endColumn = 1; + else endColumn++; + + vectorNames.prepend("timestamp"); + endColumn++; + + if (createIndexEnabled) { + vectorNames.prepend("index"); + endColumn++; + } + + m_actualCols = endColumn - startColumn + 1; + qDebug()<<"actual cols: "<(skipEmptyParts)); + + if (!firstLineStringList.isEmpty()) { + int length1 = firstLineStringList.at(0).length(); + if (firstLineStringList.size() > 1) { + int pos2 = firstLine.indexOf(firstLineStringList.at(1), length1); + m_separator = firstLine.mid(length1, pos2 - length1); + } else { + //old: separator = line.right(line.length() - length1); + m_separator = ' '; + } + } + } else { // use given separator + // replace symbolic "TAB" with '\t' + m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), "\t\t", Qt::CaseInsensitive); + m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); + // replace symbolic "SPACE" with ' ' + m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); + m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); + m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); + m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); + firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); + } + DEBUG("separator: \'" << m_separator.toStdString() << '\''); + DEBUG("number of columns: " << firstLineStringList.size()); + QDEBUG("first line: " << firstLineStringList); + DEBUG("headerEnabled = " << headerEnabled); + // parse first data line to determine data type for each column + columnModes.resize(m_actualCols); + int col = 0; + if (createIndexEnabled) { + columnModes[0] = AbstractColumn::Integer; + col = 1; + } + + //set column mode for timestamp + columnModes[col] = AbstractColumn::DateTime; + col++; + + auto firstValue = firstLineStringList.takeFirst();//use first value to identify column mode + while (firstValue.isEmpty() || firstValue.startsWith(commentCharacter) )//get the first usable value + firstValue = firstLineStringList.takeFirst(); + + if (simplifyWhitespacesEnabled) + firstValue = firstValue.simplified(); + + columnModes[m_actualCols-1] = AbstractFileFilter::columnMode(firstValue, dateTimeFormat, numberFormat); + + //Improve the column mode based on the other values from the first line + for (auto& valueString : firstLineStringList) { + if (!valueString.isEmpty() && !valueString.startsWith(commentCharacter) ) { + if (createIndexEnabled) + col = 1; + else + col = 0; - DEBUG("AsciiFilterPrivate::readFromMQTTTopic() DONE"); + if (simplifyWhitespacesEnabled) + valueString = valueString.simplified(); + AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); + + // numeric: integer -> numeric + if (mode == AbstractColumn::Numeric && columnModes[m_actualCols-1] == AbstractColumn::Integer) { + columnModes[m_actualCols-1] = mode; + } + // text: non text -> text + if (mode == AbstractColumn::Text && columnModes[m_actualCols-1] != AbstractColumn::Text) { + columnModes[m_actualCols-1] = mode; + } + } + } + //Improve the column mode based on the remaining values + for (const auto& valueString : lineList) { + QStringList splitString = valueString.split(m_separator, (QString::SplitBehavior)skipEmptyParts); + for (auto& valueString2 : splitString) { + if (!valueString2.isEmpty() && !valueString2.startsWith(commentCharacter)) { + + if (simplifyWhitespacesEnabled) + valueString2 = valueString2.simplified(); + AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString2, dateTimeFormat, numberFormat); + + // numeric: integer -> numeric + if (mode == AbstractColumn::Numeric && columnModes[m_actualCols-1] == AbstractColumn::Integer) { + columnModes[m_actualCols-1] = mode; + } + // text: non text -> text + if (mode == AbstractColumn::Text && columnModes[m_actualCols-1] != AbstractColumn::Text) { + columnModes[m_actualCols-1] = mode; + } + } + } + } + + //determine rowcount + int tempRowCount = 0; + QStringList newDataList = message.split(QRegExp("\n|\r\n|\r"), QString::SkipEmptyParts); + for (const auto& valueString : newDataList) { + if (!valueString.startsWith(commentCharacter)) { + QStringList splitString = valueString.split(m_separator, static_cast(skipEmptyParts)); + for (const auto& valueString2 : splitString) { + if (!valueString2.isEmpty() && !valueString2.startsWith(commentCharacter)) { + tempRowCount ++; + } + } + } + } + + QDEBUG("column modes = " << columnModes); + m_actualRows = tempRowCount; + + ///////////////////////////////////////////////////////////////// + + int actualEndRow = endRow; + DEBUG("endRow = " << endRow); + if (endRow == -1 || endRow > m_actualRows) + actualEndRow = m_actualRows; + + if (m_actualRows > actualEndRow) + m_actualRows = actualEndRow; + + + DEBUG("start/end column: " << startColumn << ' ' << endColumn); + DEBUG("start/end row: " << m_actualStartRow << ' ' << actualEndRow); + DEBUG("actual cols/rows (w/o header incl. start rows): " << m_actualCols << ' ' << m_actualRows); + + return 0; } /*! @@ -2611,7 +2940,7 @@ m_dataContainer[n] = static_cast(vector); break; } - //TODO + //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; diff --git a/src/backend/datasources/filters/AsciiFilterPrivate.h b/src/backend/datasources/filters/AsciiFilterPrivate.h --- a/src/backend/datasources/filters/AsciiFilterPrivate.h +++ b/src/backend/datasources/filters/AsciiFilterPrivate.h @@ -52,57 +52,50 @@ void readDataFromFile(const QString& fileName, AbstractDataSource* = nullptr, AbstractFileFilter::ImportMode = AbstractFileFilter::Replace); void write(const QString& fileName, AbstractDataSource*); - QVector preview(const QString& fileName, int lines); QVector preview(QIODevice& device); - QString separator() const; #ifdef HAVE_MQTT - int prepareToRead(const QString&); - QVector preview(const QString& message); + void MQTTPreview(QVector&, const QString&, const QString&); AbstractColumn::ColumnMode MQTTColumnMode() const; - QString MQTTColumnStatistics(const MQTTTopic*) const; - void readMQTTTopic(const QString& message, AbstractDataSource*); - void setPreparedForMQTT(bool, MQTTTopic*, const QString&); + QString MQTTColumnStatistics(const MQTTTopic* ) const; + void readMQTTTopic(const QString&, const QString&, AbstractDataSource*dataSource); + int prepareMQTTTopicToRead(const QString& message, const QString& topic); + void setPreparedForMQTT(bool, MQTTTopic*topic, const QString&); #endif const AsciiFilter* q; - QString commentCharacter{'#'}; - QString separatingCharacter{QStringLiteral("auto")}; + QString commentCharacter; + QString separatingCharacter; QString dateTimeFormat; - QLocale::Language numberFormat{QLocale::C}; - bool autoModeEnabled{true}; - bool headerEnabled{true}; - bool skipEmptyParts{false}; - bool simplifyWhitespacesEnabled{false}; - double nanValue{NAN}; - bool removeQuotesEnabled{false}; - bool createIndexEnabled{false}; - bool createTimestampEnabled{true}; + QLocale::Language numberFormat; + bool autoModeEnabled; + bool headerEnabled; + bool skipEmptyParts; + bool simplifyWhitespacesEnabled; + double nanValue; + bool removeQuotesEnabled; + bool createIndexEnabled; QStringList vectorNames; QVector columnModes; - int startRow{1}; - int endRow{-1}; - int startColumn{1}; - int endColumn{-1}; - int mqttPreviewFirstEmptyColCount{0}; + int startRow; + int endRow; + int startColumn; + int endColumn; + int mqttPreviewFirstEmptyColCount; int isPrepared(); - //TODO: redesign and remove this later - bool readingFile{false}; - QString readingFileName; - private: static const unsigned int m_dataTypeLines = 10; // maximum lines to read for determining data types QString m_separator; - int m_actualStartRow{1}; - int m_actualRows{0}; - int m_actualCols{0}; - int m_prepared{false}; - int m_columnOffset{0}; // indexes the "start column" in the datasource. Data will be imported starting from this column. + int m_actualStartRow; + 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 }; diff --git a/src/backend/datasources/filters/BinaryFilter.cpp b/src/backend/datasources/filters/BinaryFilter.cpp --- a/src/backend/datasources/filters/BinaryFilter.cpp +++ b/src/backend/datasources/filters/BinaryFilter.cpp @@ -513,7 +513,7 @@ emit q->completed(100*i/m_actualRows); } - dataSource->finalizeImport(columnOffset, 1, m_actualCols, m_actualRows, QString(), importMode); + dataSource->finalizeImport(columnOffset, 1, m_actualCols, m_actualRows, "", importMode); } /*! diff --git a/src/backend/datasources/filters/FITSFilter.cpp b/src/backend/datasources/filters/FITSFilter.cpp --- a/src/backend/datasources/filters/FITSFilter.cpp +++ b/src/backend/datasources/filters/FITSFilter.cpp @@ -380,7 +380,7 @@ delete[] data; if (dataSource) - dataSource->finalizeImport(columnOffset, 1, actualCols, lines, QString(), importMode); + dataSource->finalizeImport(columnOffset, 1, actualCols, lines, "", importMode); fits_close_file(m_fitsFile, &status); @@ -538,6 +538,7 @@ numericDataPointers.squeeze(); } + char* array = new char[1000]; //TODO: why 1000? int row = 1; if (startRow != 1) { if (startRow != 0) @@ -560,8 +561,6 @@ } } - char array[FLEN_VALUE]; - char* tmpArr[1] = {array}; for (; row <= lines; ++row) { int numericixd = 0; int stringidx = 0; @@ -572,10 +571,10 @@ if (!matrixNumericColumnIndices.contains(col)) continue; } - if (fits_read_col_str(m_fitsFile, col, row, 1, 1, nullptr, tmpArr, nullptr, &status)) + if (fits_read_col_str(m_fitsFile, col, row, 1, 1, nullptr, &array, nullptr, &status)) printError(status); if (!noDataSource) { - QString str = QString::fromLatin1(array); + const QString& str = QString::fromLatin1(array); if (str.isEmpty()) { if (columnNumericTypes.at(col - 1)) static_cast*>(numericDataPointers[numericixd++])->push_back(0); @@ -602,8 +601,10 @@ dataStrings << line; } + delete[] array; + if (!noDataSource) - dataSource->finalizeImport(columnOffset, 1, actualCols, lines, QString(), importMode); + dataSource->finalizeImport(columnOffset, 1, actualCols, lines, "", importMode); fits_close_file(m_fitsFile, &status); return dataStrings; @@ -680,18 +681,26 @@ const int tfields = matrix->columnCount(); QVector columnNames; columnNames.resize(tfields); - columnNames.squeeze(); + columnNames.reserve(tfields); QVector tform; tform.resize(tfields); - tform.squeeze(); + tform.reserve(tfields); + QVector tunit; + tunit.resize(tfields); + tunit.reserve(tfields); //TODO: mode const QVector>* const matrixData = static_cast>*>(matrix->data()); + QVector column; const MatrixModel* matrixModel = static_cast(matrix->view())->model(); const int precision = matrix->precision(); for (int i = 0; i < tfields; ++i) { + column = matrixData->at(i); const QString& columnName = matrixModel->headerData(i, Qt::Horizontal).toString(); - columnNames[i] = new char[columnName.size() + 1]; - strcpy(columnNames[i], columnName.toLatin1().constData()); + columnNames[i] = new char[columnName.size()]; + strcpy(columnNames[i], columnName.toLatin1().data()); + + tunit[i] = new char[1]; + strcpy(tunit[i], ""); int maxSize = -1; for (int row = 0; row < nrows; ++row) { if (matrix->text(row, i).size() > maxSize) @@ -702,19 +711,18 @@ tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".") + QString::number(precision); } else tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".0"); - tform[i] = new char[tformn.size() + 1]; - strcpy(tform[i], tformn.toLatin1().constData()); + tform[i] = new char[tformn.size()]; + strcpy(tform[i], tformn.toLatin1().data()); } //TODO extension name containing[] ? - int r = fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), nullptr, - matrix->name().toLatin1().constData(), &status); - for (int i = 0; i < tfields; ++i) { - delete[] tform[i]; - delete[] columnNames[i]; - } - if (r) { + if (fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), tunit.data(), + matrix->name().toLatin1().data(),&status )) { printError(status); + + qDeleteAll(tform); + qDeleteAll(tunit); + qDeleteAll(columnNames); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { @@ -723,10 +731,13 @@ } return; } + qDeleteAll(tform); + qDeleteAll(tunit); + qDeleteAll(columnNames); double* columnNumeric = new double[nrows]; for (int col = 1; col <= tfields; ++col) { - const QVector& column = matrixData->at(col-1); + column = matrixData->at(col-1); for (int r = 0; r < column.size(); ++r) columnNumeric[r] = column.at(r); @@ -809,25 +820,25 @@ QVector columnNames; columnNames.resize(tfields); - columnNames.squeeze(); + columnNames.reserve(tfields); QVector tform; tform.resize(tfields); - tform.squeeze(); + tform.reserve(tfields); QVector tunit; tunit.resize(tfields); - tunit.squeeze(); + tunit.reserve(tfields); for (int i = 0; i < tfields; ++i) { const Column* const column = spreadsheet->column(i); - columnNames[i] = new char[column->name().size() + 1]; - strcpy(columnNames[i], column->name().toLatin1().constData()); + columnNames[i] = new char[column->name().size()]; + strcpy(columnNames[i], column->name().toLatin1().data()); if (commentsAsUnits) { - tunit[i] = new char[column->comment().size() + 1]; + tunit[i] = new char[column->comment().size()]; strcpy(tunit[i], column->comment().toLatin1().constData()); } else { - tunit[i] = new char[1]; - tunit[i][0] = '\0'; + tunit[i] = new char[2]; + strcpy(tunit[i], ""); } switch (column->columnMode()) { case AbstractColumn::Numeric: { @@ -884,15 +895,12 @@ } //TODO extension name containing[] ? - int r = fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), tunit.data(), - spreadsheet->name().toLatin1().constData(), &status); - for (int i = 0; i < tfields; ++i) { - delete[] tform[i]; - delete[] tunit[i]; - delete[] columnNames[i]; - } - if (r) { + if (fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), tunit.data(), + spreadsheet->name().toLatin1().data(),&status )) { printError(status); + qDeleteAll(tform); + qDeleteAll(tunit); + qDeleteAll(columnNames); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { @@ -902,11 +910,16 @@ return; } + qDeleteAll(tform); + qDeleteAll(tunit); + qDeleteAll(columnNames); + QVector column; column.resize(nrows); - column.squeeze(); + column.reserve(nrows); double* columnNumeric = new double[nrows]; + bool hadTextColumn = false; for (int col = 1; col <= tfields; ++col) { const Column* c = spreadsheet->column(col-1); AbstractColumn::ColumnMode columnMode = c->columnMode(); @@ -928,15 +941,16 @@ return; } } else { + hadTextColumn = true; for (int row = 0; row < nrows; ++row) { - column[row] = new char[c->textAt(row).size() + 1]; - strcpy(column[row], c->textAt(row).toLatin1().constData()); + column[row] = new char[c->textAt(row).size()]; + + strcpy(column[row], c->textAt(row).toLatin1().data()); } fits_write_col(m_fitsFile, TSTRING, col, 1, 1, nrows, column.data(), &status); - for (int row = 0; row < nrows; ++row) - delete[] column[row]; if (status) { printError(status); + qDeleteAll(column); status = 0; fits_close_file(m_fitsFile, &status); return; @@ -945,6 +959,8 @@ } delete[] columnNumeric; + if (hadTextColumn) + qDeleteAll(column); status = 0; fits_close_file(m_fitsFile, &status); @@ -1232,7 +1248,7 @@ if (keywordUpdate.commentUpdated) { if (fits_modify_comment(m_fitsFile, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), - QByteArray().constData(), &status)) { + QString("").toLatin1().data(), &status)) { printError(status); status = 0; } diff --git a/src/backend/datasources/filters/HDF5Filter.cpp b/src/backend/datasources/filters/HDF5Filter.cpp --- a/src/backend/datasources/filters/HDF5Filter.cpp +++ b/src/backend/datasources/filters/HDF5Filter.cpp @@ -862,7 +862,7 @@ } default: DEBUG("unknown size " << sizeof(H5T_NATIVE_CHAR) << " of H5T_NATIVE_CHAR"); - return QStringList(QString()); + return QStringList(""); } } else if (H5Tequal(atype, H5T_STD_U8LE)) { uint8_t value; @@ -906,7 +906,7 @@ } default: DEBUG("unknown size " << sizeof(H5T_NATIVE_UCHAR) << " of H5T_NATIVE_UCHAR"); - return QStringList(QString()); + return QStringList(""); } } else if (H5Tequal(atype, H5T_STD_I16LE) || H5Tequal(atype, H5T_STD_I16BE) || H5Tequal(atype, H5T_NATIVE_SHORT)) { short value; @@ -1082,7 +1082,7 @@ } case H5T_COMPOUND: { // not shown in tree widget - QDEBUG(readHDF5Compound(tid).join(QString())); + QDEBUG(readHDF5Compound(tid).join("")); break; } case H5T_ENUM: { @@ -1234,14 +1234,14 @@ m_status = H5Iget_name(tid, link, MAXNAMELENGTH); handleError(m_status, "H5Iget_name"); - QTreeWidgetItem* dataTypeItem = new QTreeWidgetItem(QStringList()<setIcon(0, QIcon::fromTheme("accessories-calculator")); dataTypeItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(dataTypeItem); } void HDF5FilterPrivate::scanHDF5DataSet(hid_t did, char *dataSetName, QTreeWidgetItem* parentItem) { - QString attr = scanHDF5Attrs(did).join(QString()); + QString attr = scanHDF5Attrs(did).join(""); char link[MAXNAMELENGTH]; m_status = H5Iget_name(did, link, MAXNAMELENGTH); @@ -1291,9 +1291,9 @@ hid_t pid = H5Dget_create_plist(did); handleError((int)pid, "H5Dget_create_plist"); - dataSetProps << ", " << readHDF5PropertyList(pid).join(QString()); + dataSetProps << ", " << readHDF5PropertyList(pid).join(""); - QTreeWidgetItem* dataSetItem = new QTreeWidgetItem(QStringList()<setIcon(0, QIcon::fromTheme("x-office-spreadsheet")); for (int i = 0; i < dataSetItem->columnCount(); ++i) { if (rows > 0 && cols > 0 && regs > 0) { @@ -1431,9 +1431,9 @@ // multiLinkList.clear(); crashes scanHDF5Group(group, rootName, rootItem); m_status = H5Gclose(group); - handleError(m_status, "H5Gclose", QString()); + handleError(m_status, "H5Gclose", ""); m_status = H5Fclose(file); - handleError(m_status, "H5Fclose", QString()); + handleError(m_status, "H5Fclose", ""); #else DEBUG("HDF5 not available"); Q_UNUSED(fileName) @@ -1548,9 +1548,8 @@ QVector columnModes; columnModes.resize(actualCols); - // use current data set name (without path) for column name - QStringList vectorNames = {currentDataSetName.mid(currentDataSetName.lastIndexOf("/") + 1)}; - QDEBUG(" vector names = " << vectorNames) + //TODO: use given names? + QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); @@ -1734,12 +1733,8 @@ QVector columnModes; columnModes.resize(actualCols); - // use current data set name (without path) append by "_" and column number for column names + //TODO: use given names? QStringList vectorNames; - QString colName = currentDataSetName.mid(currentDataSetName.lastIndexOf("/") + 1); - for (int i = 0; i < actualCols; i++) - vectorNames << colName + QLatin1String("_") + QString::number(i + 1); - QDEBUG(" vector names = " << vectorNames) if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); @@ -1872,7 +1867,7 @@ if (!dataSource) return dataStrings; - dataSource->finalizeImport(columnOffset, 1, actualCols, -1, QString(), mode); + dataSource->finalizeImport(columnOffset, 1, actualCols, -1, "", mode); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) @@ -1891,7 +1886,7 @@ DEBUG("HDF5Filter::readDataFromFile()"); if (currentDataSetName.isEmpty()) { - DEBUG("WARNING: No data set selected"); + DEBUG("No data set selected"); return; } diff --git a/src/backend/datasources/filters/HDF5FilterPrivate.h b/src/backend/datasources/filters/HDF5FilterPrivate.h --- a/src/backend/datasources/filters/HDF5FilterPrivate.h +++ b/src/backend/datasources/filters/HDF5FilterPrivate.h @@ -51,7 +51,7 @@ const HDF5Filter* q; - QString currentDataSetName; + QString currentDataSetName{""}; int startRow{1}; int endRow{-1}; int startColumn{1}; diff --git a/src/backend/datasources/filters/JsonFilter.cpp b/src/backend/datasources/filters/JsonFilter.cpp --- a/src/backend/datasources/filters/JsonFilter.cpp +++ b/src/backend/datasources/filters/JsonFilter.cpp @@ -33,7 +33,6 @@ #include "backend/datasources/filters/JsonFilterPrivate.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/core/column/Column.h" -#include "backend/spreadsheet/Spreadsheet.h" #include #include @@ -357,7 +356,7 @@ static_cast*>(m_dataContainer[column])->operator[](row) = QDateTime(); break; case AbstractColumn::Text: - static_cast*>(m_dataContainer[column])->operator[](row) = QString(); + static_cast*>(m_dataContainer[column])->operator[](row) = ""; break; case AbstractColumn::Month: case AbstractColumn::Day: @@ -451,7 +450,7 @@ int countRows = 0; int countCols = -1; QJsonValue firstRow; - QString firstRowName; + QString firstRowName = ""; importObjectNames = (importObjectNames && (rowType == QJsonValue::Object)); switch (containerType) { @@ -615,13 +614,6 @@ emit q->completed(100 * i/m_actualRows); } - //set the plot designation to 'X' for index and name columns, if available - Spreadsheet* spreadsheet = dynamic_cast(dataSource); - if (createIndexEnabled) - spreadsheet->column(m_columnOffset )->setPlotDesignation(Column::X); - if (importObjectNames) - spreadsheet->column(m_columnOffset + (int)createIndexEnabled)->setPlotDesignation(Column::X); - dataSource->finalizeImport(m_columnOffset, startColumn, startColumn + m_actualCols - 1, m_actualRows, dateTimeFormat, importMode); } @@ -704,7 +696,7 @@ if (columnModes[n] == AbstractColumn::Numeric) lineString += QString::number(value.toDouble(), 'g', 16); else - lineString += lineString += QString(); + lineString += lineString += QLatin1String(""); break; case QJsonValue::String: //TODO: add parsing string before appending @@ -715,7 +707,7 @@ case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: - lineString += QString(); + lineString += QLatin1String(""); break; } } diff --git a/src/backend/datasources/filters/JsonFilterPrivate.h b/src/backend/datasources/filters/JsonFilterPrivate.h --- a/src/backend/datasources/filters/JsonFilterPrivate.h +++ b/src/backend/datasources/filters/JsonFilterPrivate.h @@ -42,7 +42,7 @@ explicit JsonFilterPrivate (JsonFilter* owner); int checkRow(QJsonValueRef value, int &countCols); - int parseColumnModes(QJsonValue row, QString rowName = QString()); + int parseColumnModes(QJsonValue row, QString rowName = ""); void setEmptyValue(int column, int row); void setValueFromString(int column, int row, QString value); diff --git a/src/backend/datasources/filters/NetCDFFilter.cpp b/src/backend/datasources/filters/NetCDFFilter.cpp --- a/src/backend/datasources/filters/NetCDFFilter.cpp +++ b/src/backend/datasources/filters/NetCDFFilter.cpp @@ -451,7 +451,7 @@ } QStringList props; props << translateDataType(type) << " (" << QString::number(len) << ")"; - QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(name) << typeName << props.join(QString()) << valueString.join(", ")); + QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(name) << typeName << props.join("") << valueString.join(", ")); attrItem->setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); @@ -478,7 +478,7 @@ QString value; if (i == ulid) value = i18n("unlimited"); - QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(name) << i18n("dimension") << props.join(QString()) << value); + QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(name) << i18n("dimension") << props.join("") << value); attrItem->setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); @@ -514,7 +514,7 @@ props << ")"; QStringList rowStrings; - rowStrings << QString(name) << i18n("variable") << props.join(QString()); + rowStrings << QString(name) << i18n("variable") << props.join(""); if (ndims == 0) {// get value of zero dim var switch (type) { case NC_BYTE: { NC_SCAN_VAR(signed char, schar); break; } @@ -538,7 +538,7 @@ } } else { - rowStrings << QString(); + rowStrings << ""; } auto* varItem = new QTreeWidgetItem(rowStrings); @@ -936,7 +936,7 @@ handleError(m_status, "nc_close"); if (dataSource) - dataSource->finalizeImport(columnOffset, 1, actualCols, -1, QString(), mode); + dataSource->finalizeImport(columnOffset, 1, actualCols, -1, "", mode); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) diff --git a/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp b/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp --- a/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp +++ b/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp @@ -270,7 +270,7 @@ emit q->completed(100 * currentRow/actualRows); } - dataSource->finalizeImport(columnOffset, 1, actualCols, currentRow, QString(), importMode); + dataSource->finalizeImport(columnOffset, 1, actualCols, currentRow, "", importMode); } /*! diff --git a/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp b/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp --- a/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp +++ b/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp @@ -250,7 +250,7 @@ emit q->completed(100 * currentRow/actualRows); } - dataSource->finalizeImport(columnOffset, 1, actualCols, currentRow, QString(), importMode); + dataSource->finalizeImport(columnOffset, 1, actualCols, currentRow, "", importMode); } /*! diff --git a/src/backend/datasources/filters/QJsonModel.cpp b/src/backend/datasources/filters/QJsonModel.cpp --- a/src/backend/datasources/filters/QJsonModel.cpp +++ b/src/backend/datasources/filters/QJsonModel.cpp @@ -27,9 +27,7 @@ #include #include #include -#include #include -#include QJsonTreeItem::QJsonTreeItem(QJsonTreeItem* parent) : mParent(parent) {} @@ -158,16 +156,8 @@ } bool QJsonModel::loadJson(const QByteArray& json) { - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(json, &error); - if (error.error == QJsonParseError::NoError) - return loadJson(doc); - else { - QMessageBox::critical(0, i18n("Failed to load JSON document"), - i18n("Failed to load JSON document. Error: %1").arg(error.errorString())); - return false; - } - + auto const& jdoc = QJsonDocument::fromJson(json); + return loadJson(jdoc); } bool QJsonModel::loadJson(const QJsonDocument& jdoc) { @@ -192,6 +182,7 @@ return true; } + qDebug()<setIsLoading(true); - worksheet->setTheme(QString()); + worksheet->setTheme(""); loadWorksheet(worksheet, preview); aspect = worksheet; break; @@ -624,7 +624,7 @@ Column* col = spreadsheet->column((int)j); QString name(column.name.c_str()); - col->setName(name.replace(QRegExp(".*_"), QString())); + col->setName(name.replace(QRegExp(".*_"),"")); if (preview) continue; @@ -1804,7 +1804,7 @@ QString OriginProjectParser::parseOriginText(const QString &str) const { DEBUG("parseOriginText()"); QStringList lines = str.split('\n'); - QString text; + QString text = ""; for (int i = 0; i < lines.size(); ++i) { if (i > 0) text.append("
"); @@ -2051,7 +2051,7 @@ while (pos > -1) { QString value = rxline.cap(0); int len = value.length(); - value.replace(QRegExp(" "), QString()); + value.replace(QRegExp(" "),""); value = "\\c{" + value.mid(3, value.length()-4) + '}'; line.replace(pos, len, value); pos = rxline.indexIn(line); diff --git a/src/backend/gsl/ExpressionParser.cpp b/src/backend/gsl/ExpressionParser.cpp --- a/src/backend/gsl/ExpressionParser.cpp +++ b/src/backend/gsl/ExpressionParser.cpp @@ -851,13 +851,13 @@ //Mathematical constants m_constantsNames << i18n("Base of exponentials"); m_constantsValues << QString::number(M_E,'g',15); - m_constantsUnits << QString(); + m_constantsUnits << ""; m_constantsNames << i18n("Pi"); m_constantsValues << QString::number(M_PI,'g',15); - m_constantsUnits << QString(); + m_constantsUnits << ""; m_constantsNames << i18n("Euler's constant"); m_constantsValues << QString::number(M_EULER,'g',15); - m_constantsUnits << QString(); + m_constantsUnits << ""; for (int i = 0; i < 3; i++) m_constantsGroupIndex << 0; @@ -950,7 +950,7 @@ m_constantsUnits << "kg"; m_constantsNames << i18n("Electromagnetic fine structure constant"); m_constantsValues << QString::number(GSL_CONST_NUM_FINE_STRUCTURE,'g',15); - m_constantsUnits << QString(); + m_constantsUnits << ""; m_constantsNames << i18n("Rydberg constant"); m_constantsValues << QString::number(GSL_CONST_MKSA_RYDBERG,'g',15); m_constantsUnits << "kg m^2 / s^2"; diff --git a/src/backend/lib/macros.h b/src/backend/lib/macros.h --- a/src/backend/lib/macros.h +++ b/src/backend/lib/macros.h @@ -164,7 +164,7 @@ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ - virtual void finalize() override { m_target->finalize_method(); } \ + virtual void finalize() { m_target->finalize_method(); } \ }; // setter class with finalize() and signal emitting. @@ -173,7 +173,7 @@ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ - virtual void finalize() override { emit m_target->q->field_name##Changed(m_target->*m_field); } \ + virtual void finalize() { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_F_S(class_name, cmd_name, value_type, field_name, finalize_method) \ @@ -181,7 +181,7 @@ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ - virtual void finalize() override { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ + virtual void finalize() { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; // setter class with finalize() and signal emitting for changing several properties in one single step (embedded in beginMacro/endMacro) @@ -190,8 +190,8 @@ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardMacroSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ - virtual void finalize() override { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ - virtual void finalizeUndo() override { emit m_target->q->field_name##Changed(m_target->*m_field); } \ + virtual void finalize() { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ + virtual void finalizeUndo() { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, field_name, init_method) \ @@ -223,7 +223,7 @@ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ - virtual void finalize() override { m_target->finalize_method(); } \ + virtual void finalize() { m_target->finalize_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, method_name, init_method) \ @@ -416,7 +416,7 @@ if (column){ \ writer->writeAttribute( #columnName, column->path() ); \ } else { \ - writer->writeAttribute( #columnName, QString() ); \ + writer->writeAttribute( #columnName, "" ); \ } \ } while(0) @@ -470,7 +470,7 @@ if (obj){ \ writer->writeAttribute( #name, obj->path() ); \ } else { \ - writer->writeAttribute( #name, QString() ); \ + writer->writeAttribute( #name, "" ); \ } \ } while(0) diff --git a/src/backend/lib/trace.h b/src/backend/lib/trace.h --- a/src/backend/lib/trace.h +++ b/src/backend/lib/trace.h @@ -50,7 +50,7 @@ #define PERFTRACE_ENABLED 1 -#define PERFTRACE_CURVES 1 +#define PERFTRACE_CURVES 0 #define PERFTRACE_LIVE_IMPORT 1 #ifdef PERFTRACE_ENABLED diff --git a/src/backend/nsl/Makefile b/src/backend/nsl/Makefile --- a/src/backend/nsl/Makefile +++ b/src/backend/nsl/Makefile @@ -1,8 +1,22 @@ -all: nsl_stats_test nsl_filter_test nsl_filter_test_fftw nsl_geom_linesim_test nsl_geom_linesim_morse_test nsl_fit_test +all: nsl_stats_test nsl_smooth_ma_test nsl_smooth_mal_test nsl_smooth_percentile_test nsl_smooth_savgol_test nsl_dft_test nsl_dft_test_fftw nsl_sf_window_test nsl_filter_test nsl_filter_test_fftw nsl_geom_linesim_test nsl_geom_linesim_morse_test nsl_diff_test nsl_int_test nsl_fit_test nsl_stats_test: nsl_stats_test.c nsl_stats.c gcc -o $@ $^ -lm -lgsl -lgslcblas +nsl_smooth_ma_test: nsl_smooth_ma_test.c nsl_smooth.c nsl_sf_kernel.c nsl_stats.c + gcc -o $@ $^ -lm -lgsl -lgslcblas +nsl_smooth_mal_test: nsl_smooth_mal_test.c nsl_smooth.c nsl_sf_kernel.c nsl_stats.c + gcc -o $@ $^ -lm -lgsl -lgslcblas +nsl_smooth_percentile_test: nsl_smooth_percentile_test.c nsl_smooth.c nsl_sf_kernel.c nsl_stats.c + gcc -o $@ $^ -lm -lgsl -lgslcblas +nsl_smooth_savgol_test: nsl_smooth_savgol_test.c nsl_smooth.c nsl_sf_kernel.c nsl_stats.c + gcc -o $@ $^ -lm -lgsl -lgslcblas +nsl_dft_test: nsl_dft_test.c nsl_dft.c nsl_sf_window.c + gcc -o $@ $^ -lm -lgsl -lgslcblas +nsl_dft_test_fftw: nsl_dft_test.c nsl_dft.c nsl_sf_window.c + gcc -o $@ $^ -lm -DHAVE_FFTW3 -lfftw3 -lgsl -lgslcblas +nsl_sf_window_test: nsl_sf_window_test.c nsl_sf_window.c + gcc -o $@ $^ -lm -lgsl -lgslcblas nsl_filter_test: nsl_filter_test.c nsl_filter.c nsl_sf_poly.c gcc -o $@ $^ -lm -lgsl -lgslcblas nsl_filter_test_fftw: nsl_filter_test.c nsl_filter.c nsl_sf_poly.c @@ -11,8 +25,12 @@ gcc -o $@ $^ -lm -lgsl -lgslcblas nsl_geom_linesim_morse_test: nsl_geom_linesim_morse_test.c nsl_geom_linesim.c nsl_geom.c nsl_sort.c nsl_stats.c gcc -O2 -o $@ $^ -lm -lgsl -lgslcblas +nsl_diff_test: nsl_diff_test.c nsl_diff.c nsl_sf_poly.c + gcc -o $@ $^ -lm -lgsl -lgslcblas +nsl_int_test: nsl_int_test.c nsl_int.c nsl_sf_poly.c + gcc -o $@ $^ -lm -lgsl -lgslcblas nsl_fit_test: nsl_fit_test.c nsl_fit.c nsl_sf_basic.c Faddeeva.c gcc -o $@ $^ -lm -lgsl -lgslcblas clean: - rm -f nsl_stats_test nsl_dft_test nsl_dft_test_fftw nsl_filter_test nsl_filter_test_fftw nsl_geom_linesim_test nsl_geom_linesim_morse_test nsl_diff_test nsl_fit_test + rm -f nsl_stats_test nsl_smooth_ma_test nsl_smooth_mal_test nsl_smooth_percentile_test nsl_smooth_savgol_test nsl_dft_test nsl_dft_test_fftw nsl_sf_window_test nsl_filter_test nsl_filter_test_fftw nsl_geom_linesim_test nsl_geom_linesim_morse_test nsl_diff_test nsl_int_test nsl_fit_test diff --git a/src/backend/nsl/nsl_dft_test.c b/src/backend/nsl/nsl_dft_test.c new file mode 100644 --- /dev/null +++ b/src/backend/nsl/nsl_dft_test.c @@ -0,0 +1,146 @@ +/*************************************************************************** + File : nsl_dft_test.c + Project : LabPlot + Description : NSL discrete Fourier transform functions + -------------------------------------------------------------------- + Copyright : (C) 2016 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 +#include "nsl_dft.h" + +#define NN 10 +#define TWOSIDED 1 /* 0,1 */ + +int main() { + double data[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data[]={1, 2, 3, 3, 1};*/ + const int N=10; + +#if TWOSIDED == 1 + int size=N; +#else + int size=N/2; +#endif + + /* input */ + int i; + for(i=0; i < N; i++) + printf("%g ", data[i]); + puts("\nraw:"); + + // 'raw' result + nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_raw); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data[i]); + puts("\nreal:"); + + double data2[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data2[]={1, 2, 3, 3, 1};*/ + nsl_dft_transform(data2, 1, N, TWOSIDED, nsl_dft_result_real); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data2[i]); + puts("\nimag:"); + + double data3[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data3[]={1, 2, 3, 3, 1};*/ + nsl_dft_transform(data3, 1, N, TWOSIDED, nsl_dft_result_imag); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data3[i]); + puts("\nmagnitude:"); + + double data4[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data4[]={1, 2, 3, 3, 1};*/ + nsl_dft_transform(data4, 1, N, TWOSIDED, nsl_dft_result_magnitude); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data4[i]); + puts("\namplitude:"); + + double data5[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data5[]={1, 2, 3, 3, 1};*/ + nsl_dft_transform(data5, 1, N, TWOSIDED, nsl_dft_result_amplitude); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data5[i]); + puts("\npower:"); + + double data6[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data6[]={1, 2, 3, 3, 1};*/ + nsl_dft_transform(data6, 1, N, TWOSIDED, nsl_dft_result_power); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data6[i]); + puts("\nphase:"); + + double data7[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data7[]={1, 2, 3, 3, 1};*/ + nsl_dft_transform(data7, 1, N, TWOSIDED, nsl_dft_result_phase); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data7[i]); + puts("\ndB:"); + + double data8[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + /*double data8[]={1, 2, 3, 3, 1};*/ + nsl_dft_transform(data8, 1, N, TWOSIDED, nsl_dft_result_dB); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data8[i]); + puts("\nsquare magnitude:"); + + double data9[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + nsl_dft_transform(data9, 1, N, TWOSIDED, nsl_dft_result_squaremagnitude); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data9[i]); + puts("\nsquare amplitude:"); + + double data10[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + nsl_dft_transform(data10, 1, N, TWOSIDED, nsl_dft_result_squareamplitude); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data10[i]); + puts("\nnormdB:"); + + double data11[]={1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; + nsl_dft_transform(data11, 1, N, TWOSIDED, nsl_dft_result_normdB); + + /* output */ + for(i=0; i < size; i++) + printf("%g ", data11[i]); + puts(""); +} diff --git a/src/backend/nsl/nsl_diff_test.c b/src/backend/nsl/nsl_diff_test.c new file mode 100644 --- /dev/null +++ b/src/backend/nsl/nsl_diff_test.c @@ -0,0 +1,143 @@ +/*************************************************************************** + File : nsl_diff_test.c + Project : LabPlot + Description : NSL numerical differentiation functions + -------------------------------------------------------------------- + Copyright : (C) 2016 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 +#include "nsl_diff.h" + +int main() { + const double xdata[]={1,2,4,8,16,32,64}; + const int n=7; + + /*printf("function x^2/x^3:\n"); + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata[i]); + puts("");*/ + + printf("expecting 2*x as derivative (second order):\n"); + double ydata[]={1,4,16,64,256,1024,4096}; + /*int status = nsl_diff_deriv_first_equal(xdata, ydata, n);*/ + int status = nsl_diff_first_deriv(xdata, ydata, n, 2); + + size_t i; + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g (%g)\n", xdata[i], ydata[i], 2.*xdata[i]); + puts(""); + + printf("expecting 3*x^2 as derivative (fourth order):\n"); + double ydata4[]={1,8,64,512,4096,32768,262144}; + status = nsl_diff_first_deriv(xdata, ydata4, n, 4); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g (%g)\n", xdata[i], ydata4[i], 3.*xdata[i]*xdata[i]); + puts(""); + + printf("avg derivative:\n"); + double ydata2[]={1,4,16,64,256,1024,4096}; + status = nsl_diff_first_deriv_avg(xdata, ydata2, n); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata2[i]); + puts(""); + + printf("expecting 2 as second derivative (first order):\n"); + double ydata3[]={1,4,16,64,256,1024,4096}; + status = nsl_diff_second_deriv(xdata, ydata3, n, 1); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata3[i]); + puts(""); + + printf("expecting 2 as second derivative (second order):\n"); + double ydata5[]={1,4,16,64,256,1024,4096}; + status = nsl_diff_second_deriv(xdata, ydata5, n, 2); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata5[i]); + puts(""); + + printf("expecting 6*x as second derivative (third order):\n"); + double ydata6[]={1,8,64,512,4096,32768,262144}; + status = nsl_diff_second_deriv(xdata, ydata6, n, 3); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g (%g)\n", xdata[i], ydata6[i], 6*xdata[i]); + puts(""); + + printf("expecting 6 as third derivative (second order):\n"); + double ydata7[]={1,8,64,512,4096,32768,262144}; + status = nsl_diff_third_deriv(xdata, ydata7, n, 2); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata7[i]); + puts(""); + + printf("expecting 0 as fourth derivative (first order):\n"); + double ydata8[]={1,8,64,512,4096,32768,262144}; + status = nsl_diff_fourth_deriv(xdata, ydata8, n, 1); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata8[i]); + puts(""); + + printf("expecting 0 as fourth derivative (third order):\n"); + double ydata9[]={1,8,64,512,4096,32768,262144}; + status = nsl_diff_fourth_deriv(xdata, ydata9, n, 3); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata9[i]); + puts(""); + + printf("expecting 0 as fifth derivative (second order):\n"); + double ydata10[]={1,8,64,512,4096,32768,262144}; + status = nsl_diff_fifth_deriv(xdata, ydata10, n, 2); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata10[i]); + puts(""); + + printf("expecting 0 as sixth derivative (first order):\n"); + double ydata11[]={1,8,64,512,4096,32768,262144}; + status = nsl_diff_sixth_deriv(xdata, ydata11, n, 1); + + if (status == 0) + for (i=0; i < n; i++) + printf("%g %g\n", xdata[i], ydata11[i]); + puts(""); + + return 0; +} diff --git a/src/backend/nsl/nsl_filter.c b/src/backend/nsl/nsl_filter.c --- a/src/backend/nsl/nsl_filter.c +++ b/src/backend/nsl/nsl_filter.c @@ -262,10 +262,12 @@ size_t i; for(i=0; i < 2*(n/2+1); i++) printf("%g ", data[i]); - printf("\nreal: "); + puts(""); + printf("real: "); for(i=0; i < n/2+1; i++) printf("%g ", data[2*i]); - printf("\nimag: "); + puts(""); + printf("imag: "); for(i=0; i < n/2+1; i++) printf("%g ", data[2*i+1]); puts(""); diff --git a/src/backend/nsl/nsl_int_test.c b/src/backend/nsl/nsl_int_test.c new file mode 100644 --- /dev/null +++ b/src/backend/nsl/nsl_int_test.c @@ -0,0 +1,101 @@ +/*************************************************************************** + File : nsl_int_test.c + Project : LabPlot + Description : NSL numerical integration functions + -------------------------------------------------------------------- + Copyright : (C) 2016 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 +#include "nsl_int.h" + +int main() { + double xdata[]={1,2,3,5,7}; + double ydata[]={2,2,2,-2,-2}; + int n=5; + + printf("data:\n"); + size_t i; + for (i = 0; i < n; i++) + printf("%g %g\n", xdata[i], ydata[i]); + puts(""); + + printf("integral (rectangle, 1-point):\n"); + int status = nsl_int_rectangle(xdata, ydata, n, 0); + + for (i = 0; i < n; i++) + printf("%g %g\n", xdata[i], ydata[i]); + printf("sum = %g (status = %d)\n", ydata[n-1], status); + puts(""); + + printf("area (rectangle, 1-point):\n"); + double ydata4[]={2,2,2,2,2}; + status = nsl_int_rectangle(xdata, ydata4, n, 1); + + for (i = 0; i < n; i++) + printf("%g %g\n", xdata[i], ydata4[i]); + printf("sum = %g (status = %d)\n", ydata4[n-1], status); + puts(""); + + printf("integral (trapezoid, 2-point):\n"); + double ydata2[]={1,2,3,-1,-3}; + status = nsl_int_trapezoid(xdata, ydata2, n, 0); + + for (i = 0; i < n; i++) + printf("%g %g\n", xdata[i], ydata2[i]); + printf("sum = %g (status = %d)\n", ydata2[n-1], status); + puts(""); + + printf("area (trapezoid, 2-point):\n"); + double ydata3[]={1,2,3,-1,-3}; + status = nsl_int_trapezoid(xdata, ydata3, n, 1); + + for (i = 0; i < n; i++) + printf("%g %g\n", xdata[i], ydata3[i]); + printf("sum = %g (status = %d)\n", ydata3[n-1], status); + puts(""); + + printf("integral (Simpson's 1/3, 3-point):\n"); + /*double ydata5[]={2,2,2,2,2};*/ + double ydata5[]={1,2,3,-1,-3}; + size_t np = nsl_int_simpson(xdata, ydata5, n, 0); + + for (i = 0; i < np; i++) + printf("%g %g\n", xdata[i], ydata5[i]); + printf("sum = %g (n = %zu, status = %d)\n", ydata5[np-1], np, status); + puts(""); + + printf("integral (Simpson's 3/8, 4-point):\n"); + double xdata2[]={1,2,3,5,7,8,9}; + double ydata6[]={2,2,2,2,2,2,2}; + /*double ydata6[]={1,2,3,4,5,-1,-2};*/ + n=7; + np = nsl_int_simpson_3_8(xdata2, ydata6, n, 0); + + for (i = 0; i < np; i++) + printf("%g %g\n", xdata2[i], ydata6[i]); + printf("sum = %g (n = %zu, status = %d)\n", ydata6[np-1], np, status); + puts(""); + + return 0; +} diff --git a/src/backend/nsl/nsl_sf_basic.h b/src/backend/nsl/nsl_sf_basic.h --- a/src/backend/nsl/nsl_sf_basic.h +++ b/src/backend/nsl/nsl_sf_basic.h @@ -3,7 +3,7 @@ Project : LabPlot Description : NSL special basic functions -------------------------------------------------------------------- - Copyright : (C) 2017-2019 by Stefan Gerlach (stefan.gerlach@uni.kn) + Copyright : (C) 2017-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -29,9 +29,10 @@ #ifndef NSL_SF_BASIC_H #define NSL_SF_BASIC_H -#include /* fixed size int types */ #include -#include "nsl_complex.h" +#if !defined(_MSC_VER) +#include +#endif /* random functions */ double nsl_sf_rand(void); @@ -43,14 +44,6 @@ /* Heavyside theta function */ double nsl_sf_theta(double x); -/* log2(x) for integer value x */ -int nsl_sf_log2_int(unsigned int x); -int nsl_sf_log2_int0(unsigned int x); -int nsl_sf_log2_int2(int x); -int nsl_sf_log2_int3(uint64_t x); -int nsl_sf_log2p1_int(int x); -int nsl_sf_log2_longlong(unsigned long long x); - /* more trig. functions */ double nsl_sf_sec(double x); double nsl_sf_csc(double x); @@ -73,7 +66,7 @@ double nsl_sf_erfi(double x); double nsl_sf_im_w_of_x(double x); #if !defined(_MSC_VER) -double nsl_sf_im_w_of_z(COMPLEX z); +double nsl_sf_im_w_of_z(complex double z); #endif double nsl_sf_dawson(double x); double nsl_sf_voigt(double x, double sigma, double gamma); diff --git a/src/backend/nsl/nsl_sf_basic.c b/src/backend/nsl/nsl_sf_basic.c --- a/src/backend/nsl/nsl_sf_basic.c +++ b/src/backend/nsl/nsl_sf_basic.c @@ -68,87 +68,6 @@ return 0; } -/* - * source: https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers - * source: http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup - */ -int nsl_sf_log2_int(unsigned int x) { -#ifdef _MSC_VER - return nsl_sf_log2_int2(x); -#else - return nsl_sf_log2_int0(x); -#endif -} -int nsl_sf_log2_int0(unsigned int x) { -#ifdef _MSC_VER - return 0; /* not implemented yet */ -#else - return (int) (8*sizeof (unsigned int) - __builtin_clz(x) - 1); -#endif -} -int nsl_sf_log2_int2(int x) { - const signed char LogTable256[256] = { - -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, - 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 - }; - - unsigned int r; // r will be lg(v) - unsigned int t, tt; // temporaries - if ((tt = x >> 16)) - r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt]; - else - r = (t = x >> 8) ? 8 + LogTable256[t] : LogTable256[x]; - - return r; -} -int nsl_sf_log2_int3(uint64_t value) { - const int tab64[64] = { - 63, 0, 58, 1, 59, 47, 53, 2, - 60, 39, 48, 27, 54, 33, 42, 3, - 61, 51, 37, 40, 49, 18, 28, 20, - 55, 30, 34, 11, 43, 14, 22, 4, - 62, 57, 46, 52, 38, 26, 32, 41, - 50, 36, 17, 19, 29, 10, 13, 21, - 56, 45, 25, 31, 35, 16, 9, 12, - 44, 24, 15, 8, 23, 7, 6, 5}; - - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - value |= value >> 32; - - return tab64[((uint64_t)((value - (value >> 1))*0x07EDD5E59A4E28C2)) >> 58]; -} -int nsl_sf_log2p1_int(int x) { - // fastest method - return nsl_sf_log2_int(x) + 1; - //TODO: why is this so slow - //return (int)log2(x) + 1; -} -int nsl_sf_log2_longlong(unsigned long long x) { -#ifdef _MSC_VER - return 0; /* not implemented yet */ -#else - return (int) (8*sizeof (unsigned long long) - __builtin_clzll(x) - 1); -#endif -} - double nsl_sf_sec(double x) { return 1./cos(x); } double nsl_sf_csc(double x) { return 1./sin(x); } double nsl_sf_cot(double x) { return 1./tan(x); } @@ -207,7 +126,7 @@ } #if !defined(_MSC_VER) -double nsl_sf_im_w_of_z(COMPLEX z) { +double nsl_sf_im_w_of_z(complex double z) { #ifdef HAVE_LIBCERF return cimag(w_of_z(z)); #else @@ -232,7 +151,7 @@ #elif defined(_MSC_VER) return 0.; // not supported yet #else - COMPLEX z = (x + I*gamma)/(sqrt(2.)*sigma); + double complex z = (x + I*gamma)/(sqrt(2.)*sigma); return creal(Faddeeva_w(z, 0))/(sqrt(2.*M_PI)*sigma); #endif } diff --git a/src/backend/nsl/nsl_sf_poly.h b/src/backend/nsl/nsl_sf_poly.h --- a/src/backend/nsl/nsl_sf_poly.h +++ b/src/backend/nsl/nsl_sf_poly.h @@ -30,7 +30,21 @@ #define NSL_SF_POLY_H #include -#include "nsl_complex.h" + +#ifdef _MSC_VER +#include +#define COMPLEX _Dcomplex +#else + +/* C++ including this header */ +#ifdef __cplusplus +#define COMPLEX double _Complex +#else /* C */ +#include +#define COMPLEX double complex +#endif + +#endif /* Chebychev T_n(x) */ double nsl_sf_poly_chebyshev_T(int n, double x); diff --git a/src/backend/nsl/nsl_complex.h b/src/backend/nsl/nsl_sf_window_test.c rename from src/backend/nsl/nsl_complex.h rename to src/backend/nsl/nsl_sf_window_test.c --- a/src/backend/nsl/nsl_complex.h +++ b/src/backend/nsl/nsl_sf_window_test.c @@ -1,9 +1,9 @@ /*************************************************************************** - File : nsl_complex.h + File : nsl_sf_window_test.c Project : LabPlot - Description : NSL complex data type support + Description : NSL window functions test -------------------------------------------------------------------- - Copyright : (C) 2019 by Stefan Gerlach (stefan.gerlach@uni.kn) + Copyright : (C) 2016 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -26,22 +26,20 @@ * * ***************************************************************************/ -#ifndef NSL_COMPLEX_H -#define NSL_COMPLEX_H +#include +#include "nsl_sf_window.h" -#ifdef _MSC_VER -#include -#define COMPLEX _Dcomplex -#else +int main() { + const int N = 10; + double data[] = {1,1,1,1,1,1,1,1,1,1}; -/* C++ including this header */ -#ifdef __cplusplus -#define COMPLEX double _Complex -#else /* C */ -#include -#define COMPLEX double complex -#endif + int i; + nsl_sf_window_type t; -#endif /* _MSC_VER */ - -#endif /* NSL_COMPLEX_H */ + for (t = nsl_sf_window_uniform; t <= nsl_sf_window_lanczos; t++) { + nsl_sf_apply_window(data, N, t); + for(i = 0; i < N; i++) + printf("%g ", data[i]); + puts("\n"); + } +} diff --git a/src/backend/nsl/nsl_smooth.c b/src/backend/nsl/nsl_smooth.c --- a/src/backend/nsl/nsl_smooth.c +++ b/src/backend/nsl/nsl_smooth.c @@ -265,8 +265,8 @@ result[i] += w[j]*data[index]; break; case nsl_smooth_pad_nearest: - /*printf(" %d", index);*/ - result[i] += w[j]*data[GSL_MAX(0, index)]; + /*printf(" %d",GSL_MAX(0,index));*/ + result[i] += w[j]*data[index]; break; case nsl_smooth_pad_constant: if (index < 0) diff --git a/tests/CommonTest.cpp b/src/backend/nsl/nsl_smooth_ma_test.c rename from tests/CommonTest.cpp rename to src/backend/nsl/nsl_smooth_ma_test.c --- a/tests/CommonTest.cpp +++ b/src/backend/nsl/nsl_smooth_ma_test.c @@ -1,9 +1,10 @@ /*************************************************************************** - File : CommonTest.cpp + File : nsl_smooth_ma_test.c Project : LabPlot - Description : General test class + Description : NSL smooth functions -------------------------------------------------------------------- - Copyright : (C) 2019 Stefan Gerlach (stefan.gerlach@uni.kn) + Copyright : (C) 2016 by Stefan Gerlach (stefan.gerlach@uni.kn) + ***************************************************************************/ /*************************************************************************** @@ -25,13 +26,46 @@ * * ***************************************************************************/ -#include "CommonTest.h" -#include "backend/core/column/Column.h" +#include +#include "nsl_smooth.h" -void CommonTest::initTestCase() { - // needed in order to have the signals triggered by SignallingUndoCommand, see LabPlot.cpp - //TODO: redesign/remove this - qRegisterMetaType("const AbstractAspect*"); - qRegisterMetaType("const AbstractColumn*"); -} +int main() { + double data[9]={2,2,5,2,1,0,1,4,9}; + int i,points=5; + nsl_smooth_weight_type weight = nsl_smooth_weight_uniform; + int status; + printf("pad_none\n"); + status = nsl_smooth_moving_average(data, 9, points, weight, nsl_smooth_pad_none); + for(i=0;i<9;i++) + printf(" %g",data[i]); + printf(" status = %d\n", status); + /*printf("pad_interp\n"); + status = nsl_smooth_moving_average(data, 9, points, weight, nsl_smooth_pad_interp); + */ + printf("\npad_mirror\n"); + double data2[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average(data2, 9, points, weight, nsl_smooth_pad_mirror); + for(i=0;i<9;i++) + printf(" %g",data2[i]); + printf(" status = %d\n", status); + printf("\npad_nearest\n"); + double data3[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average(data3, 9, points, weight, nsl_smooth_pad_nearest); + for(i=0;i<9;i++) + printf(" %g",data3[i]); + printf(" status = %d\n", status); + printf("\npad_constant\n"); + double data4[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average(data4, 9, points, weight, nsl_smooth_pad_constant); + for(i=0;i<9;i++) + printf(" %g",data4[i]); + printf(" status = %d\n", status); + printf("\npad_periodic\n"); + double data5[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average(data5, 9, points, weight, nsl_smooth_pad_periodic); + for(i=0;i<9;i++) + printf(" %g",data5[i]); + printf(" status = %d\n", status); + puts(""); +} diff --git a/tests/nsl/sf/NSLSFWindowTest.h b/src/backend/nsl/nsl_smooth_mal_test.c rename from tests/nsl/sf/NSLSFWindowTest.h rename to src/backend/nsl/nsl_smooth_mal_test.c --- a/tests/nsl/sf/NSLSFWindowTest.h +++ b/src/backend/nsl/nsl_smooth_mal_test.c @@ -1,9 +1,10 @@ /*************************************************************************** - File : NSLSFWindowTest.h + File : nsl_smooth_mal_test.c Project : LabPlot - Description : NSL Tests for special window functions + Description : NSL smooth functions -------------------------------------------------------------------- - Copyright : (C) 2019 Stefan Gerlach (stefan.gerlach@uni.kn) + Copyright : (C) 2016 by Stefan Gerlach (stefan.gerlach@uni.kn) + ***************************************************************************/ /*************************************************************************** @@ -24,21 +25,47 @@ * Boston, MA 02110-1301 USA * * * ***************************************************************************/ -#ifndef NSLSFWINDOWTEST_H -#define NSLSFWINDOWTEST_H - -#include "../NSLTest.h" -class NSLSFWindowTest : public NSLTest { - Q_OBJECT +#include +#include "nsl_smooth.h" -private slots: - void testWindowTypes(); +int main() { + double data[9]={2,2,5,2,1,0,1,4,9}; + int i,points=5; + nsl_smooth_weight_type weight = nsl_smooth_weight_uniform; - void testPerformance_triangle(); - void testPerformance_welch(); - void testPerformance_flat_top(); -private: - QString m_dataDir; -}; -#endif + int status; + printf("pad_none\n"); + status = nsl_smooth_moving_average_lagged(data, 9, points, weight, nsl_smooth_pad_none); + for(i=0;i<9;i++) + printf(" %g",data[i]); + printf(" status = %d\n", status); + /*printf("pad_interp\n"); + status = nsl_smooth_moving_average(data, 9, points, weight, nsl_smooth_pad_interp); + */ + printf("\npad_mirror\n"); + double data2[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average_lagged(data2, 9, points, weight, nsl_smooth_pad_mirror); + for(i=0;i<9;i++) + printf(" %g",data2[i]); + printf(" status = %d\n", status); + printf("\npad_nearest\n"); + double data3[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average_lagged(data3, 9, points, weight, nsl_smooth_pad_nearest); + for(i=0;i<9;i++) + printf(" %g",data3[i]); + printf(" status = %d\n", status); + printf("\npad_constant\n"); + double data4[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average_lagged(data4, 9, points, weight, nsl_smooth_pad_constant); + for(i=0;i<9;i++) + printf(" %g",data4[i]); + printf(" status = %d\n", status); + printf("\npad_periodic\n"); + double data5[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_moving_average_lagged(data5, 9, points, weight, nsl_smooth_pad_periodic); + for(i=0;i<9;i++) + printf(" %g",data5[i]); + printf(" status = %d\n", status); + puts(""); +} diff --git a/tests/CommonTest.h b/src/backend/nsl/nsl_smooth_percentile_test.c rename from tests/CommonTest.h rename to src/backend/nsl/nsl_smooth_percentile_test.c --- a/tests/CommonTest.h +++ b/src/backend/nsl/nsl_smooth_percentile_test.c @@ -1,9 +1,10 @@ /*************************************************************************** - File : CommonTest.h + File : nsl_smooth_percentile_test.c Project : LabPlot - Description : General test class + Description : NSL smooth functions -------------------------------------------------------------------- - Copyright : (C) 2019 Stefan Gerlach (stefan.gerlach@uni.kn) + Copyright : (C) 2016 by Stefan Gerlach (stefan.gerlach@uni.kn) + ***************************************************************************/ /*************************************************************************** @@ -24,31 +25,47 @@ * Boston, MA 02110-1301 USA * * * ***************************************************************************/ -#ifndef COMMONTEST_H -#define COMMONTEST_H - -#include -#include // DEBUG() -extern "C" { -#include -} +#include +#include "nsl_smooth.h" -class CommonTest : public QObject { - Q_OBJECT +int main() { + double data[9]={2,2,5,2,1,0,1,4,9}; + int i,points=5; + double percentile = 0.5; -private slots: - void initTestCase(); -protected: - // compare floats with given delta - // delta - relative error - static inline void FuzzyCompare(double actual, double expected, double delta = 1.e-12) { - if (fabs(expected) < delta) - QVERIFY(fabs(actual) < delta); - else { - DEBUG(std::setprecision(15) << actual - fabs(actual)*delta << " <= " << expected << " <= " << actual + fabs(actual)*delta); - QVERIFY(!gsl_fcmp(actual, expected, delta)); - } - } -}; -#endif + int status; + printf("pad_none\n"); + status = nsl_smooth_percentile(data, 9, points, percentile, nsl_smooth_pad_none); + for(i=0;i<9;i++) + printf(" %g",data[i]); + printf(" status = %d\n", status); + /*printf("pad_interp\n"); + status = nsl_smooth_moving_average(data, 9, points, weight, nsl_smooth_pad_interp); + */ + printf("\npad_mirror\n"); + double data2[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_percentile(data2, 9, points, percentile, nsl_smooth_pad_mirror); + for(i=0;i<9;i++) + printf(" %g",data2[i]); + printf(" status = %d\n", status); + printf("\npad_nearest\n"); + double data3[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_percentile(data3, 9, points, percentile, nsl_smooth_pad_nearest); + for(i=0;i<9;i++) + printf(" %g",data3[i]); + printf(" status = %d\n", status); + printf("\npad_constant\n"); + double data4[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_percentile(data4, 9, points, percentile, nsl_smooth_pad_constant); + for(i=0;i<9;i++) + printf(" %g",data4[i]); + printf(" status = %d\n", status); + printf("\npad_periodic\n"); + double data5[9]={2,2,5,2,1,0,1,4,9}; + status = nsl_smooth_percentile(data5, 9, points, percentile, nsl_smooth_pad_periodic); + for(i=0;i<9;i++) + printf(" %g",data5[i]); + printf(" status = %d\n", status); + puts(""); +} diff --git a/src/backend/nsl/nsl_smooth_savgol_test.c b/src/backend/nsl/nsl_smooth_savgol_test.c new file mode 100644 --- /dev/null +++ b/src/backend/nsl/nsl_smooth_savgol_test.c @@ -0,0 +1,123 @@ +/*************************************************************************** + File : nsl_smooth_savgol_test.c + Project : LabPlot + Description : NSL smooth functions + -------------------------------------------------------------------- + Copyright : (C) 2016 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 +#include "nsl_smooth.h" + +int main() { + /* savgol coefficients */ + int i,j,points=3, order=1; + gsl_matrix *h; + + h = gsl_matrix_alloc(points, points); + nsl_smooth_savgol_coeff(points, order, h); + for(j=0;jname() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "column") { - Column* column = new Column(QString()); + Column* column = new Column(""); if (!column->load(reader, preview)) { delete column; setColumnCount(0); @@ -765,9 +765,8 @@ //############################################################################## int Spreadsheet::prepareImport(QVector& dataContainer, AbstractFileFilter::ImportMode importMode, int actualRows, int actualCols, QStringList colNameList, QVector columnMode) { - DEBUG("Spreadsheet::prepareImport()") - DEBUG(" resize spreadsheet to rows = " << actualRows << " and cols = " << actualCols) - QDEBUG(" column name list = " << colNameList) + DEBUG("Spreadsheet::prepareImport()"); + DEBUG(" resize spreadsheet to rows = " << actualRows << " and cols = " << actualCols); int columnOffset = 0; setUndoAware(false); if (m_model != nullptr) @@ -849,8 +848,6 @@ returns column offset depending on import mode */ int Spreadsheet::resize(AbstractFileFilter::ImportMode mode, QStringList colNameList, int cols) { - DEBUG("Spreadsheet::resize()") - QDEBUG(" column name list = " << colNameList) // name additional columns for (int k = colNameList.size(); k < cols; k++ ) colNameList.append( "Column " + QString::number(k+1) ); diff --git a/src/backend/spreadsheet/SpreadsheetModel.h b/src/backend/spreadsheet/SpreadsheetModel.h --- a/src/backend/spreadsheet/SpreadsheetModel.h +++ b/src/backend/spreadsheet/SpreadsheetModel.h @@ -89,7 +89,7 @@ private: Spreadsheet* m_spreadsheet; bool m_formula_mode{false}; - QVector m_vertical_header_data; + QList m_vertical_header_data; QStringList m_horizontal_header_data; int m_defaultHeaderHeight; bool m_suppressSignals{false}; diff --git a/src/backend/worksheet/Worksheet.h b/src/backend/worksheet/Worksheet.h --- a/src/backend/worksheet/Worksheet.h +++ b/src/backend/worksheet/Worksheet.h @@ -78,9 +78,9 @@ void setIsClosing(); CartesianPlotActionMode cartesianPlotActionMode(); + bool lockPlot(); void setCartesianPlotActionMode(CartesianPlotActionMode mode); - void setPlotsLocked(bool); - bool plotsLocked(); + void setLockPlot(bool lock); BASIC_D_ACCESSOR_DECL(float, backgroundOpacity, BackgroundOpacity) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundType, backgroundType, BackgroundType) diff --git a/src/backend/worksheet/Worksheet.cpp b/src/backend/worksheet/Worksheet.cpp --- a/src/backend/worksheet/Worksheet.cpp +++ b/src/backend/worksheet/Worksheet.cpp @@ -30,7 +30,6 @@ #include "WorksheetPrivate.h" #include "WorksheetElement.h" #include "commonfrontend/worksheet/WorksheetView.h" -#include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" @@ -109,7 +108,7 @@ //default theme KConfigGroup settings = KSharedConfig::openConfig()->group(QLatin1String("Settings_Worksheet")); - d->theme = settings.readEntry(QStringLiteral("Theme"), QString()); + d->theme = settings.readEntry(QLatin1String("Theme"), ""); if (!d->theme.isEmpty()) loadTheme(d->theme); } @@ -255,7 +254,7 @@ const CartesianPlot* plot = dynamic_cast(aspect); if (plot) { auto* p = const_cast(plot); - p->setLocked(d->plotsLocked); + p->setLockPlot(d->lockPlot); } //recalculated the layout @@ -407,28 +406,19 @@ return d->cartesianPlotActionMode; } -bool Worksheet::plotsLocked() { - return d->plotsLocked; +bool Worksheet::lockPlot() { + return d->lockPlot; } void Worksheet::setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode mode) { - if (d->cartesianPlotActionMode == mode) - return; - d->cartesianPlotActionMode = mode; - project()->setChanged(true); } -void Worksheet::setPlotsLocked(bool lock) { - if (d->plotsLocked == lock) - return; - - d->plotsLocked = lock; +void Worksheet::setLockPlot(bool lock) { + d->lockPlot = lock; for (auto* plot: children()) - plot->setLocked(lock); - - project()->setChanged(true); + plot->setLockPlot(lock); } /* =============================== getter methods for general options ==================================== */ @@ -830,8 +820,8 @@ writer->writeEndElement(); // cartesian properties - writer->writeStartElement( "plotProperties" ); - writer->writeAttribute( "plotsLocked", QString::number(d->plotsLocked) ); + writer->writeStartElement( "cartesianProperties" ); + writer->writeAttribute( "lockPlot", QString::number(d->lockPlot) ); writer->writeAttribute( "cartesianPlotActionMode", QString::number(d->cartesianPlotActionMode)); writer->writeEndElement(); @@ -848,7 +838,7 @@ return false; //clear the theme that was potentially set in init() in order to correctly load here the worksheets without any theme used - d->theme.clear(); + d->theme = ""; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; @@ -957,13 +947,22 @@ d->backgroundFileName = str; READ_DOUBLE_VALUE("opacity", backgroundOpacity); - } else if(!preview && reader->name() == "plotProperties") { + } else if(!preview && reader->name() == "cartesianProperties") { attribs = reader->attributes(); - READ_INT_VALUE("plotsLocked", plotsLocked, bool); - READ_INT_VALUE("cartesianPlotActionMode", cartesianPlotActionMode, Worksheet::CartesianPlotActionMode); + str = attribs.value("lockPlot").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("lockPlot").toString()); + else + setLockPlot(str.toInt()); + + str = attribs.value("cartesianPlotActionMode").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.subs("cartesianPlotActionMode").toString()); + else + setCartesianPlotActionMode(static_cast(str.toInt())); } else if (reader->name() == "cartesianPlot") { - CartesianPlot* plot = new CartesianPlot(QString()); + CartesianPlot* plot = new CartesianPlot(""); plot->setIsLoading(true); if (!plot->load(reader, preview)) { delete plot; @@ -971,7 +970,7 @@ } else addChildFast(plot); } else if (reader->name() == "textLabel") { - TextLabel* label = new TextLabel(QString()); + TextLabel* label = new TextLabel(""); if (!label->load(reader, preview)) { delete label; return false; diff --git a/src/backend/worksheet/WorksheetElementContainerPrivate.h b/src/backend/worksheet/WorksheetElementContainerPrivate.h --- a/src/backend/worksheet/WorksheetElementContainerPrivate.h +++ b/src/backend/worksheet/WorksheetElementContainerPrivate.h @@ -36,7 +36,6 @@ class WorksheetElementContainer; class WorksheetElementContainerPrivate : public QGraphicsItem { - public: explicit WorksheetElementContainerPrivate(WorksheetElementContainer*); ~WorksheetElementContainerPrivate() override= default; diff --git a/src/backend/worksheet/WorksheetPrivate.h b/src/backend/worksheet/WorksheetPrivate.h --- a/src/backend/worksheet/WorksheetPrivate.h +++ b/src/backend/worksheet/WorksheetPrivate.h @@ -4,7 +4,7 @@ Description : Private members of Worksheet. -------------------------------------------------------------------- Copyright : (C) 2012 by Alexander Semke (alexander.semke@web.de) - + ***************************************************************************/ /*************************************************************************** @@ -75,7 +75,7 @@ int layoutColumnCount{2}; int layoutRowCount{2}; QString theme; - bool plotsLocked{false}; + bool lockPlot{false}; // lock plot, so that they are anymore movable or zoomable with the mouse or the mousewheel. Only with the zoomm tool. Worksheet::CartesianPlotActionMode cartesianPlotActionMode{Worksheet::CartesianPlotActionMode::ApplyActionToSelection}; }; diff --git a/src/backend/worksheet/plots/AbstractPlotPrivate.h b/src/backend/worksheet/plots/AbstractPlotPrivate.h --- a/src/backend/worksheet/plots/AbstractPlotPrivate.h +++ b/src/backend/worksheet/plots/AbstractPlotPrivate.h @@ -31,8 +31,7 @@ #include "backend/worksheet/WorksheetElementContainerPrivate.h" -class AbstractPlotPrivate : public WorksheetElementContainerPrivate { - +class AbstractPlotPrivate:public WorksheetElementContainerPrivate { public: explicit AbstractPlotPrivate(AbstractPlot* owner); ~AbstractPlotPrivate() override = default; diff --git a/src/backend/worksheet/plots/cartesian/Axis.cpp b/src/backend/worksheet/plots/cartesian/Axis.cpp --- a/src/backend/worksheet/plots/cartesian/Axis.cpp +++ b/src/backend/worksheet/plots/cartesian/Axis.cpp @@ -172,7 +172,7 @@ d->majorTicksDirection = (Axis::TicksDirection) group.readEntry("MajorTicksDirection", (int) Axis::ticksOut); d->majorTicksType = (Axis::TicksType) group.readEntry("MajorTicksType", (int) Axis::TicksTotalNumber); d->majorTicksNumber = group.readEntry("MajorTicksNumber", 11); - d->majorTicksIncrement = group.readEntry("MajorTicksIncrement", -1.0); // set to negative value, so axisdocks determines the value to not to have to much labels the first time switched to TicksIncrement + d->majorTicksIncrement = group.readEntry("MajorTicksIncrement", 1.0); d->majorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine) ); d->majorTicksPen.setColor( group.readEntry("MajorTicksColor", QColor(Qt::black) ) ); d->majorTicksPen.setWidthF( group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); @@ -182,7 +182,7 @@ d->minorTicksDirection = (Axis::TicksDirection) group.readEntry("MinorTicksDirection", (int) Axis::ticksOut); d->minorTicksType = (Axis::TicksType) group.readEntry("MinorTicksType", (int) Axis::TicksTotalNumber); d->minorTicksNumber = group.readEntry("MinorTicksNumber", 1); - d->minorTicksIncrement = group.readEntry("MinorTicksIncrement", -1.0); + d->minorTicksIncrement = group.readEntry("MinorTicksIncrement", 0.5); d->minorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine) ); d->minorTicksPen.setColor( group.readEntry("MinorTicksColor", QColor(Qt::black) ) ); d->minorTicksPen.setWidthF( group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); @@ -774,7 +774,7 @@ exec(new AxisSetLabelsOffsetCmd(d, offset, ki18n("%1: set label offset"))); } -STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, retransformTickLabelPositions); +STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, recalcShapeAndBoundingRect); void Axis::setLabelsRotationAngle(qreal angle) { Q_D(Axis); if (angle != d->labelsRotationAngle) @@ -1440,7 +1440,6 @@ for (const auto value : tickLabelValues) { str = QString::number(value, 'e', labelsPrecision); if (str == "-" + nullStr) str = nullStr; - str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers10) { @@ -1496,12 +1495,10 @@ for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { - if (i == j) - continue; - - if (tempValues.at(i) == tempValues.at(j)) { - //duplicate for the current precision found, increase the precision and check again - return upperLabelsPrecision(precision + 1); + if (i == j) continue; + if (tempValues.at(i) == tempValues.at(j)) { + //duplicate for the current precision found, increase the precision and check again + return upperLabelsPrecision(precision + 1); } } } @@ -1566,24 +1563,15 @@ QTextDocument td; td.setDefaultFont(labelsFont); - double cosinus = cos(labelsRotationAngle * M_PI / 180); // calculate only one time - double sinus = sin(labelsRotationAngle * M_PI / 180); // calculate only one time + for ( int i = 0; i < majorTickPoints.size(); i++ ) { - if ((orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || - (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric)) { - if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { - width = fm.width(tickLabelStrings.at(i)); - } else { - td.setHtml(tickLabelStrings.at(i)); - width = td.size().width(); - height = td.size().height(); - } - } else { // Datetime + if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { width = fm.width(tickLabelStrings.at(i)); + } else { + td.setHtml(tickLabelStrings.at(i)); + width = td.size().width(); + height = td.size().height(); } - - double diffx = cosinus * width; - double diffy = sinus * width; anchorPoint = majorTickPoints.at(i); //center align all labels with respect to the end point of the tick line @@ -1591,47 +1579,19 @@ if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); - } else { + } + else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } - - // for rotated labels (angle is not zero), align label's corner at the position of the tick - if (abs(labelsRotationAngle) > 179.999 && abs(labelsRotationAngle) < 180.009) { // +-180° - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() + width/2); - pos.setY( endPoint.y() + labelsOffset ); - } else { - pos.setX( startPoint.x() + width/2); - pos.setY( startPoint.y() - height + labelsOffset ); - } - } else if (labelsRotationAngle <= -0.01) { // [-0.01°, -180°) - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() + sinus * height/2); - pos.setY( endPoint.y() + labelsOffset + cosinus * height/2); - } else { - pos.setX( startPoint.x() + sinus * height/2 - diffx); - pos.setY( startPoint.y() + labelsOffset + cosinus * height/2 + diffy); - } - } else if (labelsRotationAngle >= 0.01) { // [0.01°, 180°) - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() - diffx + sinus * height/2); - pos.setY( endPoint.y() + labelsOffset + diffy + cosinus * height/2); - } else { - pos.setX( startPoint.x() + sinus * height/2); - pos.setY( startPoint.y() + labelsOffset + cosinus * height/2); - } - } else { // 0° - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() - width/2); - pos.setY( endPoint.y() + height + labelsOffset ); - } else { - pos.setX( startPoint.x() - width/2); - pos.setY( startPoint.y() + labelsOffset ); - } + if (labelsPosition == Axis::LabelsOut) { + pos.setX( endPoint.x() - width/2); + pos.setY( endPoint.y() + height + labelsOffset ); + } else { + pos.setX( startPoint.x() - width/2); + pos.setY( startPoint.y() - labelsOffset ); } - // ---------------------- vertical ------------------------- - } else { + } else {// vertical if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); @@ -1639,57 +1599,12 @@ startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } - - if (labelsRotationAngle >= 89.999 && labelsRotationAngle <= 90.009) { // +90° - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() - labelsOffset); - pos.setY( endPoint.y() + width/2 ); - } else { - pos.setX( startPoint.x() - labelsOffset); - pos.setY( startPoint.y() + width/2); - } - } else if (labelsRotationAngle >= -90.999 && labelsRotationAngle <= -89.009) { // -90° - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() - labelsOffset - height); - pos.setY( endPoint.y() - width/2 ); - } else { - pos.setX( startPoint.x() - labelsOffset); - pos.setY( startPoint.y() - width/2 ); - } - } else if (abs(labelsRotationAngle) > 179.999 && abs(labelsRotationAngle) < 180.009) { // +-180° - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() - labelsOffset); - pos.setY( endPoint.y() - height/2); - } else { - pos.setX( startPoint.x() - labelsOffset + width); - pos.setY( startPoint.y() - height/2 ); - } - } else if (abs(labelsRotationAngle) >= 0.01 && abs(labelsRotationAngle) < 90.01) { // [0.01°, 90°) - if (labelsPosition == Axis::LabelsOut) { - // left - pos.setX( endPoint.x() - labelsOffset - diffx + sinus * height/2); - pos.setY( endPoint.y() + cosinus * height/2 + diffy); - } else { - pos.setX( startPoint.x() - labelsOffset + sinus * height/2); - pos.setY( startPoint.y() + cosinus * height/2); - } - } else if (abs(labelsRotationAngle) >= 90.01 && abs(labelsRotationAngle) < 180) { // [90.01, 180) - if (labelsPosition == Axis::LabelsOut) { - // left - pos.setX( endPoint.x() - labelsOffset + sinus * height/2); - pos.setY( endPoint.y() + cosinus * height/2); - } else { - pos.setX( startPoint.x() - labelsOffset - diffx + sinus * height/2); - pos.setY( startPoint.y() + diffy + cosinus * height/2); - } - } else { // 0° - if (labelsPosition == Axis::LabelsOut) { - pos.setX( endPoint.x() - width - labelsOffset); - pos.setY( endPoint.y() + height/2 ); - } else { - pos.setX( startPoint.x() - labelsOffset); - pos.setY( startPoint.y() + height/2 ); - } + if (labelsPosition == Axis::LabelsOut) { + pos.setX( endPoint.x() - width - labelsOffset ); + pos.setY( endPoint.y() + height/2 ); + } else { + pos.setX( startPoint.x() + labelsOffset ); + pos.setY( startPoint.y() + height/2 ); } } tickLabelPoints << pos; @@ -1847,16 +1762,15 @@ for (int i = 0; i < tickLabelPoints.size(); i++) { tempPath = QPainterPath(); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { - tempPath.addRect(fm.boundingRect(tickLabelStrings.at(i))); + tempPath.addRect( fm.boundingRect(tickLabelStrings.at(i)) ); } else { td.setHtml(tickLabelStrings.at(i)); - tempPath.addRect(QRectF(0, -td.size().height(), td.size().width(), td.size().height())); + tempPath.addRect( QRectF(0, -td.size().height(), td.size().width(), td.size().height()) ); } trafo.reset(); trafo.translate( tickLabelPoints.at(i).x(), tickLabelPoints.at(i).y() ); - - trafo.rotate(-labelsRotationAngle); + trafo.rotate( -labelsRotationAngle ); tempPath = trafo.map(tempPath); tickLabelsPath.addPath(WorksheetElement::shapeFromPath(tempPath, linePen)); @@ -1873,15 +1787,11 @@ qreal offsetX = titleOffsetX - labelsOffset; //the distance to the axis line qreal offsetY = titleOffsetY - labelsOffset; //the distance to the axis line if (orientation == Axis::AxisHorizontal) { - offsetY -= title->graphicsItem()->boundingRect().height()/2; - if (labelsPosition == Axis::LabelsOut) - offsetY -= tickLabelsPath.boundingRect().height(); - title->setPosition( QPointF( (rect.topLeft().x() + rect.topRight().x())/2 + titleOffsetX, rect.bottomLeft().y() - offsetY ) ); + offsetY -= title->graphicsItem()->boundingRect().height()/2 + tickLabelsPath.boundingRect().height(); + title->setPosition( QPointF( (rect.topLeft().x() + rect.topRight().x())/2 + offsetX, rect.bottomLeft().y() - offsetY ) ); } else { - offsetX -= title->graphicsItem()->boundingRect().width()/2; - if (labelsPosition == Axis::LabelsOut) - offsetX -= tickLabelsPath.boundingRect().width(); - title->setPosition( QPointF( rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y())/2 - titleOffsetY) ); + offsetX -= title->graphicsItem()->boundingRect().width()/2 + tickLabelsPath.boundingRect().width(); + title->setPosition( QPointF( rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y())/2 - offsetY) ); } axisShape.addPath(WorksheetElement::shapeFromPath(title->graphicsItem()->mapToParent(title->graphicsItem()->shape()), linePen)); } @@ -1943,32 +1853,21 @@ painter->setFont(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); - if ((orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || - (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric)) { - for (int i = 0; i < tickLabelPoints.size(); i++) { - painter->translate(tickLabelPoints.at(i)); - painter->save(); - painter->rotate(-labelsRotationAngle); - - if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { - painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); - } else { - td.setHtml(tickLabelStrings.at(i)); - painter->translate(0, -td.size().height()); - td.drawContents(painter); - } - painter->restore(); - painter->translate(-tickLabelPoints.at(i)); - } - } else { // datetime - for (int i = 0; i < tickLabelPoints.size(); i++) { - painter->translate(tickLabelPoints.at(i)); - painter->save(); - painter->rotate(-labelsRotationAngle); + for (int i = 0; i < tickLabelPoints.size(); i++) { + painter->translate(tickLabelPoints.at(i)); + painter->save(); + painter->rotate(-labelsRotationAngle); + + if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); - painter->restore(); - painter->translate(-tickLabelPoints.at(i)); + } else { + td.setHtml(tickLabelStrings.at(i)); + painter->translate(0, -td.size().height()); + td.drawContents(painter); } + + painter->restore(); + painter->translate(-tickLabelPoints.at(i)); } } diff --git a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h --- a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h +++ b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h @@ -75,6 +75,7 @@ QVector mapLogicalToScene(const QVector&, MappingFlags flags = DefaultMapping) const override; void mapLogicalToScene(const QVector& logicalPoints, QVector& scenePoints, std::vector& visiblePoints, MappingFlags flags = DefaultMapping) const; + void mapLogicalToScene(int startIndex, int endIndex, const QVector &logicalPoints, QVector& scenePoints, std::vector &visiblePoints, QVector >& scenePointsUsed, double minLogicalDiffX, double minLogicalDiffY, MappingFlags flags = DefaultMapping) const; QPointF mapLogicalToScene(QPointF, MappingFlags flags = DefaultMapping) const override; QVector mapLogicalToScene(const QVector&, MappingFlags flags = DefaultMapping) const override; diff --git a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp --- a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp @@ -253,11 +253,11 @@ } /*! - Maps the points in logical coordinates from \c points and fills the \c visiblePoints with the points in logical coordinates restricted to the current intervals. - \param logicalPoints List of points in logical coordinates - \param scenePoints List for the points in scene coordinates - \param visiblePoints List for the logical coordinates restricted to the current region of the coordinate system - \param flags + Maps the points in logical coordinates from @p points and fills the @p visiblePoints with the points in logical coordinates restricted to the current intervals. + @param logicalPoints List of points in logical coordinates + @param scenePoints List for the points in scene coordinates + @param visiblePoints List for the logical coordinates restricted to the current region of the coordinate system + @param flags */ void CartesianCoordinateSystem::mapLogicalToScene(const QVector& logicalPoints, QVector& scenePoints, std::vector& visiblePoints, MappingFlags flags) const { @@ -297,6 +297,59 @@ } } +/*! + Maps the points in logical coordinates from \c points and fills the \c visiblePoints with the points in logical coordinates restricted to the current intervals. + If there are points, which lies on another the point will not be added a second time. + @param logicalPoints List of points in logical coordinates + @param scenePoints List for the points in scene coordinates + @param visiblePoints List for the logical coordinates restricted to the current region of the coordinate system + @param scenePointsUsed List of bools which scene point was already used + */ +void CartesianCoordinateSystem::mapLogicalToScene(int startIndex, int endIndex, const QVector& logicalPoints, + QVector& scenePoints, std::vector& visiblePoints, QVector>& scenePointsUsed, double minLogicalDiffX, double minLogicalDiffY, MappingFlags flags) const { + const QRectF pageRect = d->plot->dataRect(); + const bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + + for (const auto* xScale : d->xScales) { + if (!xScale) continue; + + for (const auto* yScale : d->yScales) { + if (!yScale) continue; + + for (int i = startIndex; i <= endIndex; ++i) { + const QPointF& point = logicalPoints.at(i); + + double x = point.x(); + if (!xScale->contains(x)) + continue; + + if (!xScale->map(&x)) + continue; + + double y = point.y(); + if (!yScale->contains(y)) + continue; + + if (!yScale->map(&y)) + continue; + + const QPointF mappedPoint(x, y); + if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) { + + int indexX = (int)((mappedPoint.x() - d->plot->dataRect().x())*minLogicalDiffX); + int indexY = (int)((mappedPoint.y() - d->plot->dataRect().y())*minLogicalDiffY); + if (scenePointsUsed[indexX][indexY]) + continue; + + scenePointsUsed[indexX][indexY] = true; + scenePoints.append(mappedPoint); + visiblePoints[i].flip(); + } + } + } + } +} + QPointF CartesianCoordinateSystem::mapLogicalToScene(QPointF logicalPoint, MappingFlags flags) const { const QRectF pageRect = d->plot->dataRect(); bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.h b/src/backend/worksheet/plots/cartesian/CartesianPlot.h --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.h @@ -55,14 +55,14 @@ class XYCorrelationCurve; class KConfig; -class CartesianPlot : public AbstractPlot { +class CartesianPlot:public AbstractPlot { Q_OBJECT public: explicit CartesianPlot(const QString &name); ~CartesianPlot() override; - enum Scale {ScaleLinear, ScaleLog10, ScaleLog2, ScaleLn, ScaleLog10Abs, ScaleLog2Abs, ScaleLnAbs, ScaleSqrt, ScaleX2}; + enum Scale {ScaleLinear, ScaleLog10, ScaleLog2, ScaleLn, ScaleSqrt, ScaleX2}; enum Type {FourAxes, TwoAxes, TwoAxesCentered, TwoAxesCenteredZero}; enum RangeFormat {Numeric, DateTime}; enum RangeType {RangeFree, RangeLast, RangeFirst}; @@ -101,7 +101,7 @@ void setRect(const QRectF&) override; QRectF dataRect() const; void setMouseMode(const MouseMode); - void setLocked(const bool); + void setLockPlot(const bool lock); MouseMode mouseMode() const; void navigate(NavigationOperation); void setSuppressDataChangedSignal(bool); @@ -159,7 +159,7 @@ //"add new" actions QAction* addCurveAction; QAction* addEquationCurveAction; - QAction* addHistogramAction; + QAction* addHistogramPlot; QAction* addDataReductionCurveAction; QAction* addDifferentiationCurveAction; QAction* addIntegrationCurveAction; @@ -231,9 +231,9 @@ void addLegend(); void addTextLabel(); void addCustomPoint(); - bool scaleAuto(); - bool scaleAutoX(); - bool scaleAutoY(); + void scaleAuto(); + void scaleAutoX(); + void scaleAutoY(); void zoomIn(); void zoomOut(); void zoomInX(); diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include @@ -393,25 +394,25 @@ void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); - addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this); - addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), this); + addHistogramPlot = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("Histogram"), this); + addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical equation"), this); // no icons yet - addDataReductionCurveAction = new QAction(i18n("xy-curve from a Data Reduction"), this); - addDifferentiationCurveAction = new QAction(i18n("xy-curve from a Differentiation"), this); - addIntegrationCurveAction = new QAction(i18n("xy-curve from an Integration"), this); - addInterpolationCurveAction = new QAction(i18n("xy-curve from an Interpolation"), this); - addSmoothCurveAction = new QAction(i18n("xy-curve from a Smooth"), this); - addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve from a Fit to Data"), this); - addFourierFilterCurveAction = new QAction(i18n("xy-curve from a Fourier Filter"), this); - addFourierTransformCurveAction = new QAction(i18n("xy-curve from a Fourier Transform"), this); - addConvolutionCurveAction = new QAction(i18n("xy-curve from a (De-)Convolution"), this); - addCorrelationCurveAction = new QAction(i18n("xy-curve from a Auto-/Cross-Correlation"), this); + addDataReductionCurveAction = new QAction(i18n("xy-curve from a data reduction"), this); + addDifferentiationCurveAction = new QAction(i18n("xy-curve from a differentiation"), this); + addIntegrationCurveAction = new QAction(i18n("xy-curve from an integration"), this); + addInterpolationCurveAction = new QAction(i18n("xy-curve from an interpolation"), this); + addSmoothCurveAction = new QAction(i18n("xy-curve from a smooth"), this); + addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve from a fit to data"), this); + addFourierFilterCurveAction = new QAction(i18n("xy-curve from a Fourier filter"), this); + addFourierTransformCurveAction = new QAction(i18n("xy-curve from a Fourier transform"), this); + addConvolutionCurveAction = new QAction(i18n("xy-curve from a (de-)convolution"), this); + addCorrelationCurveAction = new QAction(i18n("xy-curve from a correlation"), this); // addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("xy-curve from an interpolation"), this); // addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smooth-curve"), i18n("xy-curve from a smooth"), this); // addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("xy-curve from a Fourier filter"), this); // addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_transform-curve"), i18n("xy-curve from a Fourier transform"), this); // addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("xy-curve from a (de-)convolution"), this); -// addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-correlation-curve"), i18n("xy-curve from a auto-/cross-correlation"), this); +// addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-correlation-curve"), i18n("xy-curve from a correlation"), this); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), this); if (children().size()>0) addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action @@ -422,7 +423,7 @@ addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), this); connect(addCurveAction, SIGNAL(triggered()), SLOT(addCurve())); - connect(addHistogramAction,SIGNAL(triggered()), SLOT(addHistogram())); + connect(addHistogramPlot,SIGNAL(triggered()), SLOT(addHistogram())); connect(addEquationCurveAction, SIGNAL(triggered()), SLOT(addEquationCurve())); connect(addDataReductionCurveAction, SIGNAL(triggered()), SLOT(addDataReductionCurve())); connect(addDifferentiationCurveAction, SIGNAL(triggered()), SLOT(addDifferentiationCurve())); @@ -449,7 +450,7 @@ addInterpolationAction = new QAction(i18n("Interpolate"), this); addSmoothAction = new QAction(i18n("Smooth"), this); addConvolutionAction = new QAction(i18n("Convolute/Deconvolute"), this); - addCorrelationAction = new QAction(i18n("Auto-/Cross-Correlation"), this); + addCorrelationAction = new QAction(i18n("Correlation"), this); QAction* fitAction = new QAction(i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); @@ -548,7 +549,7 @@ addNewMenu = new QMenu(i18n("Add New")); addNewMenu->addAction(addCurveAction); - addNewMenu->addAction(addHistogramAction); + addNewMenu->addAction(addHistogramPlot); addNewMenu->addAction(addEquationCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addDataReductionCurveAction); @@ -949,14 +950,14 @@ STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales) void CartesianPlot::setXMin(double xMin) { Q_D(CartesianPlot); - if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) + if (xMin != d->xMin) exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales) void CartesianPlot::setXMax(double xMax) { Q_D(CartesianPlot); - if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) + if (xMax != d->xMax) exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x"))); } @@ -1278,7 +1279,7 @@ } void CartesianPlot::addCorrelationCurve() { - XYCorrelationCurve* curve = new XYCorrelationCurve("Auto-/Cross-Correlation"); + XYCorrelationCurve* curve = new XYCorrelationCurve("Correlation"); this->addChild(curve); } @@ -1471,18 +1472,14 @@ Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; - bool updated = false; if (d->autoScaleX && d->autoScaleY) - updated = this->scaleAuto(); + this->scaleAuto(); else if (d->autoScaleX) - updated = this->scaleAutoX(); + this->scaleAutoX(); else if (d->autoScaleY) - updated = this->scaleAutoY(); - - if (!updated || !QObject::sender()) { - //even if the plot ranges were not changed, either no auto scale active or the new data - //is within the current ranges and no change of the ranges is required, - //retransform the curve in order to show the changes + this->scaleAutoY(); + else { + //free ranges -> rentransform the curve that sent auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); @@ -1491,14 +1488,11 @@ if (hist) hist->retransform(); else { - //no sender available, the function was called in CartesianPlot::dataChanged() - //via plot->dataChaged() in the file filter (live data source got new data) + //no sender available, the function was called in CartesianPlot::dataChanged() (live data source got new data) //-> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive - for (auto* c : children()) { - c->recalcLogicalPoints(); + for (auto* c : children()) c->retransform(); - } } } } @@ -1509,7 +1503,7 @@ Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::xDataChanged() { - if (project() && project()->isLoading()) + if (project()->isLoading()) return; Q_D(CartesianPlot); @@ -1517,22 +1511,11 @@ return; d->curvesXMinMaxIsDirty = true; - bool updated = false; if (d->autoScaleX) - updated = this->scaleAutoX(); - - if (!updated) { - //even if the plot ranges were not changed, either no auto scale active or the new data - //is within the current ranges and no change of the ranges is required, - //retransform the curve in order to show the changes + this->scaleAutoX(); + else { auto* curve = dynamic_cast(QObject::sender()); - if (curve) - curve->retransform(); - else { - auto* hist = dynamic_cast(QObject::sender()); - if (hist) - hist->retransform(); - } + curve->retransform(); } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data @@ -1552,7 +1535,7 @@ Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::yDataChanged() { - if (project() && project()->isLoading()) + if (project()->isLoading()) return; Q_D(CartesianPlot); @@ -1560,22 +1543,11 @@ return; d->curvesYMinMaxIsDirty = true; - bool updated = false; if (d->autoScaleY) this->scaleAutoY(); - - if (!updated) { - //even if the plot ranges were not changed, either no auto scale active or the new data - //is within the current ranges and no change of the ranges is required, - //retransform the curve in order to show the changes + else { auto* curve = dynamic_cast(QObject::sender()); - if (curve) - curve->retransform(); - else { - auto* hist = dynamic_cast(QObject::sender()); - if (hist) - hist->retransform(); - } + curve->retransform(); } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data @@ -1632,12 +1604,12 @@ } } -void CartesianPlot::setLocked(bool locked) { +void CartesianPlot::setLockPlot(const bool lock) { Q_D(CartesianPlot); - d->locked = locked; + d->plotLocked = lock; } -bool CartesianPlot::scaleAutoX() { +void CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { int count = 0; @@ -1719,11 +1691,9 @@ } d->retransformScales(); } - - return update; } -bool CartesianPlot::scaleAutoY() { +void CartesianPlot::scaleAutoY() { Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { @@ -1804,11 +1774,9 @@ } d->retransformScales(); } - - return update; } -bool CartesianPlot::scaleAuto() { +void CartesianPlot::scaleAuto() { DEBUG("CartesianPlot::scaleAuto()"); Q_D(CartesianPlot); @@ -1958,8 +1926,6 @@ } d->retransformScales(); } - - return (updateX || updateY); } void CartesianPlot::zoomIn() { @@ -2109,9 +2075,8 @@ } void CartesianPlotPrivate::retransformScales() { - DEBUG("CartesianPlotPrivate::retransformScales()"); - DEBUG(" xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax); PERFTRACE("CartesianPlotPrivate::retransformScales()"); + DEBUG(" xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax); auto* plot = dynamic_cast(q); QVector scales; @@ -2385,8 +2350,17 @@ // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); - else - return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, type); + else { + double base; + if (type == CartesianPlot::ScaleLog10) + base = 10.0; + else if (type == CartesianPlot::ScaleLog2) + base = 2.0; + else + base = M_E; + + return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, base); + } } /*! @@ -2430,7 +2404,7 @@ m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } else { - if (!locked && dataRect.contains(event->pos()) ){ + if (!plotLocked && dataRect.contains(event->pos()) ){ panningStarted = true; m_panningStart = event->pos(); setCursor(Qt::ClosedHandCursor); @@ -2463,7 +2437,7 @@ } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { - q->info(QString()); + q->info(""); return; } @@ -2564,9 +2538,9 @@ } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { - if (locked) - return; + if (plotLocked) + return; //determine first, which axes are selected and zoom only in the corresponding direction. //zoom the entire plot if no axes selected. bool zoomX = false; @@ -2646,10 +2620,7 @@ QGraphicsItem::hoverMoveEvent(event); } -void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { - Q_UNUSED(option) - Q_UNUSED(widget) - +void CartesianPlotPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget) { if (!isVisible()) return; @@ -2668,20 +2639,7 @@ painter->restore(); } - float penWidth = 6.; - QRectF rect = q->m_plotArea->graphicsItem()->boundingRect(); - rect = QRectF(-rect.width()/2 - penWidth / 2, -rect.height()/2 - penWidth / 2, - rect.width() + penWidth, rect.height() + penWidth); - - if (m_hovered && !isSelected() && !m_printing) { - painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), penWidth, Qt::SolidLine)); - painter->drawRect(rect); - } - - if (isSelected() && !m_printing) { - painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), penWidth, Qt::SolidLine)); - painter->drawRect(rect); - } + WorksheetElementContainerPrivate::paint(painter, option, widget); } //############################################################################## @@ -2716,10 +2674,10 @@ writer->writeStartElement( "coordinateSystem" ); writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) ); writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) ); - writer->writeAttribute( "xMin", QString::number(d->xMin, 'g', 16)); - writer->writeAttribute( "xMax", QString::number(d->xMax, 'g', 16) ); - writer->writeAttribute( "yMin", QString::number(d->yMin, 'g', 16) ); - writer->writeAttribute( "yMax", QString::number(d->yMax, 'g', 16) ); + writer->writeAttribute( "xMin", QString::number(d->xMin) ); + writer->writeAttribute( "xMax", QString::number(d->xMax) ); + writer->writeAttribute( "yMin", QString::number(d->yMin) ); + writer->writeAttribute( "yMax", QString::number(d->yMax) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "xRangeFormat", QString::number(d->xRangeFormat) ); @@ -2963,7 +2921,7 @@ } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { - Axis* axis = new Axis(QString()); + Axis* axis = new Axis(""); if (axis->load(reader, preview)) addChildFast(axis); else { @@ -2971,7 +2929,7 @@ return false; } } else if (reader->name() == "xyCurve") { - XYCurve* curve = new XYCurve(QString()); + XYCurve* curve = new XYCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -2979,7 +2937,7 @@ return false; } } else if (reader->name() == "xyEquationCurve") { - XYEquationCurve* curve = new XYEquationCurve(QString()); + XYEquationCurve* curve = new XYEquationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -2987,7 +2945,7 @@ return false; } } else if (reader->name() == "xyDataReductionCurve") { - XYDataReductionCurve* curve = new XYDataReductionCurve(QString()); + XYDataReductionCurve* curve = new XYDataReductionCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -2995,7 +2953,7 @@ return false; } } else if (reader->name() == "xyDifferentiationCurve") { - XYDifferentiationCurve* curve = new XYDifferentiationCurve(QString()); + XYDifferentiationCurve* curve = new XYDifferentiationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3003,7 +2961,7 @@ return false; } } else if (reader->name() == "xyIntegrationCurve") { - XYIntegrationCurve* curve = new XYIntegrationCurve(QString()); + XYIntegrationCurve* curve = new XYIntegrationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3011,7 +2969,7 @@ return false; } } else if (reader->name() == "xyInterpolationCurve") { - XYInterpolationCurve* curve = new XYInterpolationCurve(QString()); + XYInterpolationCurve* curve = new XYInterpolationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3019,7 +2977,7 @@ return false; } } else if (reader->name() == "xySmoothCurve") { - XYSmoothCurve* curve = new XYSmoothCurve(QString()); + XYSmoothCurve* curve = new XYSmoothCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3027,7 +2985,7 @@ return false; } } else if (reader->name() == "xyFitCurve") { - XYFitCurve* curve = new XYFitCurve(QString()); + XYFitCurve* curve = new XYFitCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3035,7 +2993,7 @@ return false; } } else if (reader->name() == "xyFourierFilterCurve") { - XYFourierFilterCurve* curve = new XYFourierFilterCurve(QString()); + XYFourierFilterCurve* curve = new XYFourierFilterCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3043,7 +3001,7 @@ return false; } } else if (reader->name() == "xyFourierTransformCurve") { - XYFourierTransformCurve* curve = new XYFourierTransformCurve(QString()); + XYFourierTransformCurve* curve = new XYFourierTransformCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3051,7 +3009,7 @@ return false; } } else if (reader->name() == "xyConvolutionCurve") { - XYConvolutionCurve* curve = new XYConvolutionCurve(QString()); + XYConvolutionCurve* curve = new XYConvolutionCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3059,7 +3017,7 @@ return false; } } else if (reader->name() == "xyCorrelationCurve") { - XYCorrelationCurve* curve = new XYCorrelationCurve(QString()); + XYCorrelationCurve* curve = new XYCorrelationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { @@ -3067,7 +3025,7 @@ return false; } } else if (reader->name() == "cartesianPlotLegend") { - m_legend = new CartesianPlotLegend(this, QString()); + m_legend = new CartesianPlotLegend(this, ""); if (m_legend->load(reader, preview)) addChildFast(m_legend); else { @@ -3075,7 +3033,7 @@ return false; } } else if (reader->name() == "customPoint") { - CustomPoint* point = new CustomPoint(this, QString()); + CustomPoint* point = new CustomPoint(this, ""); if (point->load(reader, preview)) addChildFast(point); else { diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h --- a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h @@ -74,7 +74,7 @@ CartesianCoordinateSystem* cSystem{nullptr}; bool suppressRetransform{false}; bool panningStarted{false}; - bool locked{false}; + bool plotLocked{false}; private: QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; diff --git a/src/backend/worksheet/plots/cartesian/Histogram.h b/src/backend/worksheet/plots/cartesian/Histogram.h --- a/src/backend/worksheet/plots/cartesian/Histogram.h +++ b/src/backend/worksheet/plots/cartesian/Histogram.h @@ -165,8 +165,8 @@ void binCountChanged(int); void binWidthChanged(float); void autoBinRangesChanged(bool); - void binRangesMinChanged(double); - void binRangesMaxChanged(double); + void binRangesMinChanged(bool); + void binRangesMaxChanged(bool); //Symbol-Tab void symbolsStyleChanged(Symbol::Style); diff --git a/src/backend/worksheet/plots/cartesian/Histogram.cpp b/src/backend/worksheet/plots/cartesian/Histogram.cpp --- a/src/backend/worksheet/plots/cartesian/Histogram.cpp +++ b/src/backend/worksheet/plots/cartesian/Histogram.cpp @@ -154,7 +154,7 @@ Returns an icon to be used in the project explorer. */ QIcon Histogram::icon() const { - return QIcon::fromTheme("view-object-histogram-linear"); + return QIcon::fromTheme("labplot-xy-curve"); } QGraphicsItem* Histogram::graphicsItem() const { @@ -676,11 +676,6 @@ setAcceptHoverEvents(true); } -HistogramPrivate::~HistogramPrivate() { - if (m_histogram) - gsl_histogram_free(m_histogram); -} - QString HistogramPrivate::name() const { return q->name(); } @@ -886,21 +881,21 @@ } } - DEBUG("min " << binRangesMin) - DEBUG("max " << binRangesMax) - DEBUG("number of bins " << m_bins) + DEBUG("min " << binRangesMin); + DEBUG("max " << binRangesMax); + DEBUG("number of bins " << m_bins); //calculate the histogram if (m_bins > 0) { m_histogram = gsl_histogram_alloc (m_bins); - gsl_histogram_set_ranges_uniform (m_histogram, binRangesMin, binRangesMax); + gsl_histogram_set_ranges_uniform (m_histogram, binRangesMin, binRangesMax+1); for (int row = 0; row < dataColumn->rowCount(); ++row) { if ( dataColumn->isValid(row) && !dataColumn->isMasked(row) ) gsl_histogram_increment(m_histogram, dataColumn->valueAt(row)); } } else - DEBUG("Number of bins must be positiv integer") + DEBUG("Number of bins must be positiv integer"); } //histogram changed because of the actual data changes or because of new bin settings, @@ -959,7 +954,9 @@ if (!m_histogram) return; - const double width = (binRangesMax - binRangesMin)/m_bins; + const double min = gsl_histogram_min(m_histogram); + const double max = gsl_histogram_max(m_histogram); + const double width = (max - min)/m_bins; double value = 0.; if (lineType == Histogram::Bars) { for (size_t i = 0; i < m_bins; ++i) { @@ -968,7 +965,7 @@ else value += gsl_histogram_get(m_histogram, i); - const double x = binRangesMin + i*width; + const double x = min + i*width; lines.append(QLineF(x, 0., x, value)); lines.append(QLineF(x, value, x + width, value)); lines.append(QLineF(x + width, value, x + width, 0.)); @@ -982,7 +979,7 @@ else value += gsl_histogram_get(m_histogram, i); - const double x = binRangesMin + i*width; + const double x = min + i*width; lines.append(QLineF(x, prevValue, x, value)); lines.append(QLineF(x, value, x + width, value)); pointsLogical.append(QPointF(x+width/2, value)); @@ -999,21 +996,23 @@ else value += gsl_histogram_get(m_histogram, i); - const double x = binRangesMin + i*width - width/2; + const double x = min + i*width - width/2; lines.append(QLineF(x, 0., x, value)); pointsLogical.append(QPointF(x, value)); } } if (lineType != Histogram::DropLines) - lines.append(QLineF(binRangesMin, 0., binRangesMax, 0.)); + lines.append(QLineF(min, 0., max, 0.)); } void HistogramPrivate::horizontalHistogram() { if (!m_histogram) return; - const double width = (binRangesMax - binRangesMin)/m_bins; + const double min = gsl_histogram_min(m_histogram); + const double max = gsl_histogram_max(m_histogram); + const double width = (max - min)/m_bins; double value = 0.; if (lineType == Histogram::Bars) { for (size_t i = 0; i < m_bins; ++i) { @@ -1022,7 +1021,7 @@ else value += gsl_histogram_get(m_histogram,i); - const double y = binRangesMin + i*width; + const double y = min + i*width; lines.append(QLineF(0., y, value, y)); lines.append(QLineF(value, y, value, y + width)); lines.append(QLineF(value, y + width, 0., y + width)); @@ -1036,7 +1035,7 @@ else value += gsl_histogram_get(m_histogram, i); - const double y = binRangesMin + i*width; + const double y = min + i*width; lines.append(QLineF(prevValue, y, value, y)); lines.append(QLineF(value, y, value, y + width)); pointsLogical.append(QPointF(value, y+width/2)); @@ -1053,14 +1052,14 @@ else value += gsl_histogram_get(m_histogram, i); - const double y = binRangesMin + i*width - width/2; + const double y = min + i*width - width/2; lines.append(QLineF(0., y, value, y)); pointsLogical.append(QPointF(value, y)); } } if (lineType != Histogram::DropLines) - lines.append(QLineF(0., binRangesMin, 0., binRangesMax)); + lines.append(QLineF(0., min, 0., max)); } void HistogramPrivate::updateSymbols() { diff --git a/src/backend/worksheet/plots/cartesian/HistogramPrivate.h b/src/backend/worksheet/plots/cartesian/HistogramPrivate.h --- a/src/backend/worksheet/plots/cartesian/HistogramPrivate.h +++ b/src/backend/worksheet/plots/cartesian/HistogramPrivate.h @@ -45,7 +45,6 @@ class HistogramPrivate : public QGraphicsItem { public: explicit HistogramPrivate(Histogram* owner); - ~HistogramPrivate() override; QString name() const; QRectF boundingRect() const override; diff --git a/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp @@ -361,7 +361,7 @@ READ_STRING_VALUE("status", convolutionResult.status); READ_INT_VALUE("time", convolutionResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp @@ -334,7 +334,7 @@ READ_STRING_VALUE("status", correlationResult.status); READ_INT_VALUE("time", correlationResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.h b/src/backend/worksheet/plots/cartesian/XYCurve.h --- a/src/backend/worksheet/plots/cartesian/XYCurve.h +++ b/src/backend/worksheet/plots/cartesian/XYCurve.h @@ -68,11 +68,10 @@ void saveThemeConfig(const KConfig&) override; static int calculateMaxSteps(unsigned int value); double y(double x, bool &valueFound) const; - QDateTime yDateTime(double x, bool &valueFound) const; int indexForX(double x) const; int indexForX(double x, QVector& column, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; - int indexForX(double x, QVector& column, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; - int indexForX(double x, QVector& lines, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; + int indexForXinPointsVector(double x, QVector& column, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; + int indexForXinLinesVector(double x, QVector& lines, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; POINTER_D_ACCESSOR_DECL(const AbstractColumn, xColumn, XColumn) POINTER_D_ACCESSOR_DECL(const AbstractColumn, yColumn, YColumn) @@ -143,7 +142,6 @@ typedef XYCurvePrivate Private; void retransform() override; - void recalcLogicalPoints(); void handleResize(double horizontalRatio, double verticalRatio, bool pageResize) override; private slots: diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include @@ -440,7 +441,7 @@ } // Symbols-Tab -STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsStyle, Symbol::Style, symbolsStyle, updateSymbols) +STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsStyle, Symbol::Style, symbolsStyle, retransform) void XYCurve::setSymbolsStyle(Symbol::Style style) { Q_D(XYCurve); if (style != d->symbolsStyle) @@ -868,6 +869,7 @@ bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); + retransform(); return oldValue; } @@ -877,11 +879,16 @@ triggers the update of lines, drop lines, symbols etc. */ void XYCurvePrivate::retransform() { + + if (!isVisible()) + return; + DEBUG("\nXYCurvePrivate::retransform() name = " << name().toStdString() << ", m_suppressRetransform = " << m_suppressRetransform); DEBUG(" plot = " << plot); if (m_suppressRetransform || !plot) return; + { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform()"); #endif @@ -908,13 +915,53 @@ WAIT_CURSOR; //calculate the scene coordinates - { -#ifdef PERFTRACE_CURVES - PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform(), map logical points to scene coordinates"); -#endif - cSystem->mapLogicalToScene(symbolPointsLogical, symbolPointsScene, visiblePoints); + if (symbolsStyle != Symbol::NoSymbols || valuesType != XYCurve::NoValues ) { + { + #ifdef PERFTRACE_CURVES + PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform(), map logical points to scene coordinates"); + #endif + + float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Inch); + float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Inch); + int countPixelX = ceil(widthDatarectInch*QApplication::desktop()->physicalDpiX()); + int countPixelY = ceil(heightDatarectInch*QApplication::desktop()->physicalDpiY()); + + double minLogicalDiffX = 1/(plot->dataRect().width()/countPixelX); + double minLogicalDiffY = 1/(plot->dataRect().height()/countPixelY); + QVector> scenePointsUsed; + // size of the datarect in pixels + scenePointsUsed.resize(countPixelX+1); + for (int i=0; i< countPixelX+1; i++) { + scenePointsUsed[i].resize(countPixelY+1); + + } + int columnProperties = xColumn->properties(); + int startIndex; + int endIndex; + if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || + columnProperties == AbstractColumn::Properties::MonotonicIncreasing) { + double xMin = cSystem->mapSceneToLogical(plot->dataRect().topLeft()).x(); + double xMax = cSystem->mapSceneToLogical(plot->dataRect().bottomRight()).x(); + startIndex= q->indexForX(xMin, symbolPointsLogical, static_cast(columnProperties)); + endIndex = q->indexForX(xMax, symbolPointsLogical, static_cast(columnProperties)); + + if (startIndex > endIndex) { + int tempIndex = startIndex; + startIndex = endIndex; + endIndex = tempIndex; + } + } else { + startIndex = 0; + endIndex = symbolPointsLogical.size()-1; + } + + + cSystem->mapLogicalToScene(startIndex, endIndex, symbolPointsLogical, symbolPointsScene, visiblePoints, scenePointsUsed, minLogicalDiffX, minLogicalDiffY); + } } + + m_suppressRecalc = true; updateLines(); updateDropLines(); @@ -924,6 +971,7 @@ updateErrorBars(); RESET_CURSOR; + } } /*! @@ -948,7 +996,7 @@ //take over only valid and non masked points. for (int row = 0; row < xColumn->rowCount(); row++) { if ( xColumn->isValid(row) && yColumn->isValid(row) - && (!xColumn->isMasked(row)) && (!yColumn->isMasked(row)) ) { + && (!xColumn->isMasked(row)) && (!yColumn->isMasked(row)) ) { switch (xColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: @@ -989,6 +1037,83 @@ visiblePoints = std::vector(symbolPointsLogical.count(), false); } +/*! + * Adds a line, which connects two points, but only if the don't lie on the same xAxis pixel. + * If they lie on the same x pixel, draw a vertical line between the minimum and maximum y value. So all points are included + * @param p0 first point + * @param p1 second point + * @param minY + * @param maxY + * @param overlap if at the previous call was an overlap between the previous two points + * @param minLogicalDiffX + * @param minLogicalDiffY + * @param pixelDiff x pixel distance between two points + */ +void XYCurvePrivate::addLine(QPointF p0, QPointF p1, double& minY, double& maxY, bool& overlap, double minLogicalDiffX, double minLogicalDiffY, int& pixelDiff) { + if (samePixel(p0.x() * minLogicalDiffX, p1.x() * minLogicalDiffX, pixelDiff)) { + if (overlap) { // second and so on time pixel are same + if (p0.y() > maxY) { + maxY = p0.y(); + } + if (p0.y() < minY) { + minY = p0.y(); + } + } else { // first time pixel are same + if (p0.y() < p1.y()) { + minY = p0.y(); + maxY = p1.y(); + } else { + maxY = p0.y(); + minY = p1.y(); + } + overlap = true; + } + } else { + if (overlap) { // when previously overlap was true, draw the previous line + overlap = false; + + // last point from previous pixel must be evaluated + if (p0.y() > maxY) { + maxY = p0.y(); + } + if (p0.y() < minY) { + minY = p0.y(); + } + + if (p1.x() >= plot->xMin() && p1.x() <= plot->xMax()) { // x inside scene + + if (minY == maxY) { + lines.append(QLineF(p0, p1)); // line from previous point to actual point + } else if (p0.y() == minY) { // draw vertical line + lines.append(QLineF(p0.x(),maxY, p0.x(), minY)); + if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) { + return; + } + lines.append(QLineF(p0,p1)); + } else if (p0.y() == maxY) { // draw vertical line + lines.append(QLineF(p0.x(),maxY, p0.x(), minY)); + if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) { + return; + } + // draw line, only if there is a pixelDiff = 1 otherwise no line needed, because when drawing a new vertical line, this line is already included + lines.append(QLineF(p0,p1)); + } else { // last point nor min nor max + lines.append(QLineF(p0.x(),maxY, p0.x(), minY)); + if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) { + return; + } + lines.append(QLineF(p0,p1)); + } + } else {// x in scene + DEBUG("addLine: not in scene"); + } + } else {// overlap + lines.append(QLineF(p0,p1)); + } + + } +} + /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. @@ -1013,6 +1138,14 @@ return; } + float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Inch); + float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Inch); + int countPixelX = ceil(widthDatarectInch*QApplication::desktop()->physicalDpiX()); + int countPixelY = ceil(heightDatarectInch*QApplication::desktop()->physicalDpiY()); + + double minLogicalDiffX = 1/((plot->xMax()-plot->xMin())/countPixelX); + double minLogicalDiffY = 1/((plot->yMax()-plot->yMin())/countPixelY); + //calculate the lines connecting the data points { #ifdef PERFTRACE_CURVES @@ -1020,176 +1153,276 @@ #endif QPointF tempPoint1, tempPoint2; QPointF curPoint, nextPoint; - switch (lineType) { - case XYCurve::NoLine: - break; - case XYCurve::Line: - for (unsigned int i = 0; i < count - 1; i++) { - if (!lineSkipGaps && !connectedPointsLogical[i]) continue; - if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; - lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); - } - break; - case XYCurve::StartHorizontal: - for (unsigned int i = 0; i < count - 1; i++) { - if (!lineSkipGaps && !connectedPointsLogical[i]) continue; - if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; - curPoint = symbolPointsLogical.at(i); - nextPoint = symbolPointsLogical.at(i+1); - tempPoint1 = QPointF(nextPoint.x(), curPoint.y()); - lines.append(QLineF(curPoint, tempPoint1)); - lines.append(QLineF(tempPoint1, nextPoint)); + + int startIndex; + int endIndex; + bool overlap = false; + double maxY, minY; + int pixelDiff; + QPointF p0; + QPointF p1; + + // find index for xMin and xMax to not loop throug all values + int columnProperties = q->xColumn()->properties(); + if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || + columnProperties == AbstractColumn::Properties::MonotonicIncreasing) { + double xMin = cSystem->mapSceneToLogical(plot->dataRect().topLeft()).x(); + double xMax = cSystem->mapSceneToLogical(plot->dataRect().bottomRight()).x(); + startIndex= q->indexForX(xMin); + endIndex = q->indexForX(xMax); + + if (startIndex > endIndex) { + int tempIndex = startIndex; + startIndex = endIndex; + endIndex = tempIndex; } - break; - case XYCurve::StartVertical: - for (unsigned int i = 0; i < count - 1; i++) { - if (!lineSkipGaps && !connectedPointsLogical[i]) continue; - if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; - curPoint = symbolPointsLogical.at(i); - nextPoint = symbolPointsLogical.at(i+1); - tempPoint1 = QPointF(curPoint.x(), nextPoint.y()); - lines.append(QLineF(curPoint, tempPoint1)); - lines.append(QLineF(tempPoint1,nextPoint)); + + startIndex--; // use one value before + endIndex ++; + if (startIndex < 0) + startIndex = 0; + if(endIndex < 0 || endIndex >= count) + endIndex = count-1; + + count = endIndex - startIndex +1; + }else { + startIndex = 0; + endIndex = count-1; + } + + if (columnProperties == AbstractColumn::Properties::Constant) { + tempPoint1 = QPointF(plot->xMin(), plot->yMin()); + tempPoint2 = QPointF(plot->xMin(), plot->yMax()); + lines.append(QLineF(tempPoint1, tempPoint2)); + } else { + switch (lineType) { + case XYCurve::NoLine: + break; + case XYCurve::Line: { + //startIndex+=0; + //endIndex = startIndex+3; + //QPointF entryPoint = symbolPointsLogical[startIndex]; + for (unsigned int i = startIndex; i < endIndex; i++) { + if (!lineSkipGaps && !connectedPointsLogical[i]) + continue; + p0 = symbolPointsLogical[i]; + p1 = symbolPointsLogical[i+1]; + if (lineIncreasingXOnly && (p1.x() < p0.x())) + continue; + addLine(p0, p1, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + // add last line + if (overlap) { + overlap = false; + addLine(p0,p1, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + break; } - break; - case XYCurve::MidpointHorizontal: - for (unsigned int i = 0; i < count - 1; i++) { - if (!lineSkipGaps && !connectedPointsLogical[i]) continue; - if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; - curPoint = symbolPointsLogical.at(i); - nextPoint = symbolPointsLogical.at(i+1); - tempPoint1 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, curPoint.y()); - tempPoint2 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, nextPoint.y()); - lines.append(QLineF(curPoint, tempPoint1)); - lines.append(QLineF(tempPoint1, tempPoint2)); - lines.append(QLineF(tempPoint2, nextPoint)); + case XYCurve::StartHorizontal: { + for (unsigned int i = startIndex; i < endIndex; i++) { + if (!lineSkipGaps && !connectedPointsLogical[i]) continue; + p0 = symbolPointsLogical[i]; + p1 = symbolPointsLogical[i+1]; + if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; + curPoint = p0; + nextPoint = p1; + tempPoint1 = QPointF(nextPoint.x(), curPoint.y()); + addLine(curPoint, tempPoint1, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + addLine(tempPoint1, nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + // add last line + if (overlap) { + overlap = false; + addLine(tempPoint1,nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + break; } - break; - case XYCurve::MidpointVertical: - for (unsigned int i = 0; i < count - 1; i++) { - if (!lineSkipGaps && !connectedPointsLogical[i]) continue; - if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; - curPoint = symbolPointsLogical.at(i); - nextPoint = symbolPointsLogical.at(i+1); - tempPoint1 = QPointF(curPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); - tempPoint2 = QPointF(nextPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); - lines.append(QLineF(curPoint, tempPoint1)); - lines.append(QLineF(tempPoint1, tempPoint2)); - lines.append(QLineF(tempPoint2, nextPoint)); + case XYCurve::StartVertical: { + for (unsigned int i = startIndex; i < endIndex; i++) { + if (!lineSkipGaps && !connectedPointsLogical[i]) continue; + if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; + curPoint = symbolPointsLogical.at(i); + nextPoint = symbolPointsLogical.at(i+1); + tempPoint1 = QPointF(curPoint.x(), nextPoint.y()); + addLine(curPoint, tempPoint1, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + addLine(tempPoint1, nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + // add last line + if (overlap) { + overlap = false; + addLine(tempPoint1,nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + break; } - break; - case XYCurve::Segments2: { - int skip = 0; - for (unsigned int i = 0; i < count - 1; i++) { - if (skip != 1) { - if ( (!lineSkipGaps && !connectedPointsLogical[i]) - || (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) ) { - skip = 0; - continue; - } - lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); - skip++; - } else - skip = 0; + case XYCurve::MidpointHorizontal: { + for (unsigned int i = startIndex; i < endIndex; i++) { + if (!lineSkipGaps && !connectedPointsLogical[i]) continue; + if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; + curPoint = symbolPointsLogical.at(i); + nextPoint = symbolPointsLogical.at(i+1); + tempPoint1 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, curPoint.y()); + tempPoint2 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, nextPoint.y()); + addLine(curPoint, tempPoint1, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + addLine(tempPoint1, tempPoint2, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + addLine(tempPoint2, nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); } break; + // add last line + if (overlap) { + overlap = false; + addLine(tempPoint2,nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } } - case XYCurve::Segments3: { - int skip = 0; - for (unsigned int i = 0; i < count - 1; i++) { - if (skip != 2) { - if ( (!lineSkipGaps && !connectedPointsLogical[i]) - || (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) ) { - skip = 0; - continue; - } - lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); - skip++; - } else - skip = 0; + case XYCurve::MidpointVertical: { + for (unsigned int i = startIndex; i < endIndex; i++) { + if (!lineSkipGaps && !connectedPointsLogical[i]) continue; + if (lineIncreasingXOnly && (symbolPointsLogical.at(i+1).x() < symbolPointsLogical.at(i).x())) continue; + curPoint = symbolPointsLogical.at(i); + nextPoint = symbolPointsLogical.at(i+1); + tempPoint1 = QPointF(curPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); + tempPoint2 = QPointF(nextPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); + addLine(curPoint, tempPoint1, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + addLine(tempPoint1, tempPoint2, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + addLine(tempPoint2, nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); } break; + // add last line + if (overlap) { + overlap = false; + addLine(tempPoint2,nextPoint, minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } } - case XYCurve::SplineCubicNatural: - case XYCurve::SplineCubicPeriodic: - case XYCurve::SplineAkimaNatural: - case XYCurve::SplineAkimaPeriodic: { - gsl_interp_accel *acc = gsl_interp_accel_alloc(); - gsl_spline *spline = nullptr; - - double* x = new double[count]; - double* y = new double[count]; - for (unsigned int i = 0; i < count; i++) { - x[i] = symbolPointsLogical.at(i).x(); - y[i] = symbolPointsLogical.at(i).y(); + case XYCurve::Segments2: { + int skip = 0; + for (unsigned int i = startIndex; i < endIndex; i++) { + if (skip != 1) { + if ( (!lineSkipGaps && !connectedPointsLogical[i]) + || (lineIncreasingXOnly && (symbolPointsLogical[i+1].x() < symbolPointsLogical[i].x())) ) { + skip = 0; + continue; + } + addLine(symbolPointsLogical[i], symbolPointsLogical[i+1], minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + skip++; + } else + skip = 0; + } + // add last line + if (overlap) { + overlap = false; + addLine(symbolPointsLogical[endIndex-1],symbolPointsLogical[endIndex], minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + break; + } + case XYCurve::Segments3: { + int skip = 0; + for (unsigned int i = startIndex; i < endIndex; i++) { + if (skip != 2) { + if ( (!lineSkipGaps && !connectedPointsLogical[i]) + || (lineIncreasingXOnly && (symbolPointsLogical[i+1].x() < symbolPointsLogical[i].x())) ) { + skip = 0; + continue; + } + addLine(symbolPointsLogical[i], symbolPointsLogical[i+1], minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + skip++; + } else + skip = 0; + } + // add last line + if (overlap) { + overlap = false; + addLine(symbolPointsLogical[endIndex-1],symbolPointsLogical[endIndex], minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } + break; } + case XYCurve::SplineCubicNatural: + case XYCurve::SplineCubicPeriodic: + case XYCurve::SplineAkimaNatural: + case XYCurve::SplineAkimaPeriodic: { + gsl_interp_accel *acc = gsl_interp_accel_alloc(); + gsl_spline *spline = nullptr; + + double* x = new double[count]; + double* y = new double[count]; + for (unsigned int i = 0; i < count; i++) { // TODO: interpolating only between the visible points? + x[i] = symbolPointsLogical[i+startIndex].x(); + y[i] = symbolPointsLogical[i+startIndex].y(); + } - gsl_set_error_handler_off(); - if (lineType == XYCurve::SplineCubicNatural) - spline = gsl_spline_alloc(gsl_interp_cspline, count); - else if (lineType == XYCurve::SplineCubicPeriodic) - spline = gsl_spline_alloc(gsl_interp_cspline_periodic, count); - else if (lineType == XYCurve::SplineAkimaNatural) - spline = gsl_spline_alloc(gsl_interp_akima, count); - else if (lineType == XYCurve::SplineAkimaPeriodic) - spline = gsl_spline_alloc(gsl_interp_akima_periodic, count); - - if (!spline) { - QString msg; - if ( (lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) && count < 5) - msg = i18n("Error: Akima spline interpolation requires a minimum of 5 points."); - else - msg = i18n("Error: Could not initialize the spline function."); - emit q->info(msg); + gsl_set_error_handler_off(); + if (lineType == XYCurve::SplineCubicNatural) + spline = gsl_spline_alloc(gsl_interp_cspline, count); + else if (lineType == XYCurve::SplineCubicPeriodic) + spline = gsl_spline_alloc(gsl_interp_cspline_periodic, count); + else if (lineType == XYCurve::SplineAkimaNatural) + spline = gsl_spline_alloc(gsl_interp_akima, count); + else if (lineType == XYCurve::SplineAkimaPeriodic) + spline = gsl_spline_alloc(gsl_interp_akima_periodic, count); + + if (!spline) { + QString msg; + if ( (lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) && count < 5) + msg = i18n("Error: Akima spline interpolation requires a minimum of 5 points."); + else + msg = i18n("Error: Could not initialize the spline function."); + emit q->info(msg); + + recalcShapeAndBoundingRect(); + delete[] x; + delete[] y; + gsl_interp_accel_free (acc); + return; + } - recalcShapeAndBoundingRect(); - delete[] x; - delete[] y; - gsl_interp_accel_free (acc); - return; - } + int status = gsl_spline_init (spline, x, y, count); + if (status) { + //TODO: check in gsl/interp.c when GSL_EINVAL is thrown + QString gslError; + if (status == GSL_EINVAL) + gslError = i18n("x values must be monotonically increasing."); + else + gslError = gslErrorToString(status); + emit q->info( i18n("Error: %1", gslError) ); + + recalcShapeAndBoundingRect(); + delete[] x; + delete[] y; + gsl_spline_free (spline); + gsl_interp_accel_free (acc); + return; + } - int status = gsl_spline_init (spline, x, y, count); - if (status) { - //TODO: check in gsl/interp.c when GSL_EINVAL is thrown - QString gslError; - if (status == GSL_EINVAL) - gslError = i18n("x values must be monotonically increasing."); - else - gslError = gslErrorToString(status); - emit q->info( i18n("Error: %1", gslError) ); + //create interpolating points + std::vector xinterp, yinterp; + for (unsigned int i = 0; i < count - 1; i++) { + const double x1 = x[i]; + const double x2 = x[i+1]; + double xi, yi; + const double step = fabs(x2 - x1)/(lineInterpolationPointsCount + 1); + + for (int i=0; i < (lineInterpolationPointsCount + 1); i++) { + xi = x1+i*step; + yi = gsl_spline_eval(spline, xi, acc); + xinterp.push_back(xi); + yinterp.push_back(yi); + } + } + + for (unsigned int i = 0; i < xinterp.size() - 1; i++) + addLine(QPointF(xinterp[i],yinterp[i]), QPointF(xinterp[i+1], yinterp[i+1]), minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + addLine(QPointF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1]), QPointF(x[count-1], y[count-1]), minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + + // add last line + if (overlap) { + overlap = false; + addLine(QPointF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1]), QPointF(x[count-1], y[count-1]), minY, maxY, overlap, minLogicalDiffX, minLogicalDiffY, pixelDiff); + } - recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); - return; - } - - //create interpolating points - std::vector xinterp, yinterp; - for (unsigned int i = 0; i < count - 1; i++) { - const double x1 = x[i]; - const double x2 = x[i+1]; - const double step = fabs(x2 - x1)/(lineInterpolationPointsCount + 1); - - for (double xi = x1; xi < x2; xi += step) { - const double yi = gsl_spline_eval(spline, xi, acc); - xinterp.push_back(xi); - yinterp.push_back(yi); - } + break; } - - for (unsigned int i = 0; i < xinterp.size() - 1; i++) - lines.append(QLineF(xinterp[i], yinterp[i], xinterp[i+1], yinterp[i+1])); - lines.append(QLineF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1], x[count-1], y[count-1])); - - delete[] x; - delete[] y; - gsl_spline_free (spline); - gsl_interp_accel_free (acc); - break; } } } @@ -1217,6 +1450,11 @@ recalcShapeAndBoundingRect(); } +bool XYCurvePrivate::samePixel(double value1, double value2, int &pixelDiff) { + pixelDiff = int(value1) - int(value2); + return pixelDiff == 0; +} + /*! recalculates the painter path for the drop lines. Called each time when the type of the drop lines is changed. @@ -1343,32 +1581,34 @@ switch (valuesType) { case XYCurve::NoValues: case XYCurve::ValuesX: { - for (int i = 0; i < symbolPointsLogical.size(); ++i) { + for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; - valuesStrings << valuesPrefix + QString::number(symbolPointsLogical.at(i).x()) + valuesSuffix; + valuesStrings << valuesPrefix + QString::number(cSystem->mapSceneToLogical(symbolPointsScene[i]).x()) + valuesSuffix; } break; } case XYCurve::ValuesY: { - for (int i = 0; i < symbolPointsLogical.size(); ++i) { + for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; - valuesStrings << valuesPrefix + QString::number(symbolPointsLogical.at(i).y()) + valuesSuffix; + valuesStrings << valuesPrefix + QString::number(cSystem->mapSceneToLogical(symbolPointsScene[i]).y()) + valuesSuffix; } break; } case XYCurve::ValuesXY: { - for (int i = 0; i < symbolPointsLogical.size(); ++i) { + for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; - valuesStrings << valuesPrefix + QString::number(symbolPointsLogical.at(i).x()) + ',' - + QString::number(symbolPointsLogical.at(i).y()) + valuesSuffix; + QPointF logicalValue = cSystem->mapSceneToLogical(symbolPointsScene[i]); + valuesStrings << valuesPrefix + QString::number(logicalValue.x()) + ',' + + QString::number(logicalValue.y()) + valuesSuffix; } break; } case XYCurve::ValuesXYBracketed: { - for (int i = 0; i < symbolPointsLogical.size(); ++i) { + for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; - valuesStrings << valuesPrefix + '(' + QString::number(symbolPointsLogical.at(i).x()) + ',' - + QString::number(symbolPointsLogical.at(i).y()) +')' + valuesSuffix; + QPointF logicalValue = cSystem->mapSceneToLogical(symbolPointsScene[i]); + valuesStrings << valuesPrefix + '(' + QString::number(logicalValue.x()) + ',' + + QString::number(logicalValue.y()) +')' + valuesSuffix; } break; } diff --git a/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h b/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h --- a/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h +++ b/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h @@ -34,6 +34,7 @@ class CartesianPlot; class CartesianCoordinateSystem; +class XYCurve; class XYCurvePrivate : public QGraphicsItem { public: @@ -46,6 +47,8 @@ void retransform(); void recalcLogicalPoints(); void updateLines(); + void addLine(QPointF p0, QPointF p1, double &minY, double &maxY, bool &overlap, double minLogicalDiffX, double minLogicalDiffY, int &pixelDiff); + bool samePixel(double value1, double value2, int &pixelDiff); void updateDropLines(); void updateSymbols(); void updateValues(); diff --git a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp @@ -377,7 +377,7 @@ READ_DOUBLE_VALUE("posError", dataReductionResult.posError); READ_DOUBLE_VALUE("areaError", dataReductionResult.areaError); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp @@ -325,7 +325,7 @@ READ_STRING_VALUE("status", differentiationResult.status); READ_INT_VALUE("time", differentiationResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -450,7 +450,7 @@ paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ") << UTF8_QSTRING("γ"); break; default: - model.clear(); + model = ""; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) @@ -468,7 +468,7 @@ paramNamesUtf8 << "A" << UTF8_QSTRING("η") << "w" << UTF8_QSTRING("μ"); break; default: - model.clear(); + model = ""; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) @@ -2316,7 +2316,7 @@ READ_DOUBLE_VALUE("bic", fitResult.bic); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp @@ -358,7 +358,7 @@ READ_STRING_VALUE("status", filterResult.status); READ_INT_VALUE("time", filterResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp @@ -343,7 +343,7 @@ READ_STRING_VALUE("status", transformResult.status); READ_INT_VALUE("time", transformResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp @@ -322,7 +322,7 @@ READ_INT_VALUE("time", integrationResult.elapsedTime, int); READ_DOUBLE_VALUE("value", integrationResult.value); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp @@ -515,7 +515,7 @@ READ_STRING_VALUE("status", interpolationResult.status); READ_INT_VALUE("time", interpolationResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp @@ -344,7 +344,7 @@ READ_STRING_VALUE("status", smoothResult.status); READ_INT_VALUE("time", smoothResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; diff --git a/src/commonfrontend/ProjectExplorer.cpp b/src/commonfrontend/ProjectExplorer.cpp --- a/src/commonfrontend/ProjectExplorer.cpp +++ b/src/commonfrontend/ProjectExplorer.cpp @@ -121,16 +121,16 @@ matchCompleteWordAction->setChecked(false); connect(matchCompleteWordAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterMatchCompleteWord); - expandTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand All"), this); + expandTreeAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Expand All"), this); connect(expandTreeAction, &QAction::triggered, m_treeView, &QTreeView::expandAll); - expandSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand Selected"), this); + expandSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Expand Selected"), this); connect(expandSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::expandSelected); - collapseTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse All"), this); + collapseTreeAction = new QAction(i18n("Collapse All"), this); connect(collapseTreeAction, &QAction::triggered, m_treeView, &QTreeView::collapseAll); - collapseSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse Selected"), this); + collapseSelectedTreeAction = new QAction(i18n("Collapse Selected"), this); connect(collapseSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::collapseSelected); deleteSelectedTreeAction = new QAction(QIcon::fromTheme("edit-delete"), i18n("Delete Selected"), this); @@ -189,10 +189,7 @@ // QMenu* objectsMenu = menu->addMenu(i18n("Show/Hide objects")); } } - - if (menu) - menu->exec(event->globalPos()); - + menu->exec(event->globalPos()); delete menu; } diff --git a/src/commonfrontend/matrix/MatrixView.h b/src/commonfrontend/matrix/MatrixView.h --- a/src/commonfrontend/matrix/MatrixView.h +++ b/src/commonfrontend/matrix/MatrixView.h @@ -113,11 +113,6 @@ QAction* action_mirror_vertically; QAction* action_mirror_horizontally; - QAction* action_add_value; - QAction* action_subtract_value; - QAction* action_multiply_value; - QAction* action_divide_value; - QAction* action_header_format_1; QAction* action_header_format_2; QAction* action_header_format_3; @@ -165,8 +160,6 @@ void headerFormatChanged(QAction*); - void modifyValues(); - void addColumns(); void insertEmptyColumns(); void removeSelectedColumns(); diff --git a/src/commonfrontend/matrix/MatrixView.cpp b/src/commonfrontend/matrix/MatrixView.cpp --- a/src/commonfrontend/matrix/MatrixView.cpp +++ b/src/commonfrontend/matrix/MatrixView.cpp @@ -37,7 +37,6 @@ #include "backend/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" -#include "kdefrontend/spreadsheet/AddSubtractValueDialog.h" #include "kdefrontend/matrix/MatrixFunctionDialog.h" #include "kdefrontend/spreadsheet/StatisticsDialog.h" @@ -158,24 +157,14 @@ action_image_view->setCheckable(true); connect(viewActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(switchView(QAction*))); - action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this); - action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this); + action_fill_function = new QAction(QIcon::fromTheme(""), i18n("Function Values"), this); + action_fill_const = new QAction(QIcon::fromTheme(""), i18n("Const Values"), this); action_clear_matrix = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Matrix"), this); action_go_to_cell = new QAction(QIcon::fromTheme("go-jump"), i18n("&Go to Cell"), this); action_transpose = new QAction(i18n("&Transpose"), this); action_mirror_horizontally = new QAction(QIcon::fromTheme("object-flip-horizontal"), i18n("Mirror &Horizontally"), this); action_mirror_vertically = new QAction(QIcon::fromTheme("object-flip-vertical"), i18n("Mirror &Vertically"), this); - - action_add_value = new QAction(i18n("Add Value"), this); - action_add_value->setData(AddSubtractValueDialog::Add); - action_subtract_value = new QAction(i18n("Subtract Value"), this); - action_subtract_value->setData(AddSubtractValueDialog::Subtract); - action_multiply_value = new QAction(i18n("Multiply Value"), this); - action_multiply_value->setData(AddSubtractValueDialog::Multiply); - action_divide_value = new QAction(i18n("Divide Value"), this); - action_divide_value->setData(AddSubtractValueDialog::Divide); - // action_duplicate = new QAction(i18nc("duplicate matrix", "&Duplicate"), this); //TODO //icon @@ -204,15 +193,6 @@ action_statistics_rows = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this); } -void MatrixView::modifyValues() { - const QAction* action = dynamic_cast(QObject::sender()); - AddSubtractValueDialog::Operation op = (AddSubtractValueDialog::Operation)action->data().toInt(); - auto* dlg = new AddSubtractValueDialog(m_matrix, op); - dlg->setMatrices(); - dlg->exec(); -} - - void MatrixView::connectActions() { // selection related actions connect(action_cut_selection, SIGNAL(triggered()), this, SLOT(cutSelection())); @@ -231,10 +211,6 @@ connect(action_transpose, SIGNAL(triggered()), m_matrix, SLOT(transpose())); connect(action_mirror_horizontally, SIGNAL(triggered()), m_matrix, SLOT(mirrorHorizontally())); connect(action_mirror_vertically, SIGNAL(triggered()), m_matrix, SLOT(mirrorVertically())); - connect(action_add_value, &QAction::triggered, this, &MatrixView::modifyValues); - connect(action_subtract_value, &QAction::triggered, this, &MatrixView::modifyValues); - connect(action_multiply_value, &QAction::triggered, this, &MatrixView::modifyValues); - connect(action_divide_value, &QAction::triggered, this, &MatrixView::modifyValues); // column related actions connect(action_add_columns, SIGNAL(triggered()), this, SLOT(addColumns())); @@ -285,32 +261,21 @@ m_matrixMenu->addMenu(submenu); m_matrixMenu->addSeparator(); - // Data manipulation sub-menu - QMenu* dataManipulationMenu = new QMenu(i18n("Manipulate Data"), this); - dataManipulationMenu->addAction(action_add_value); - dataManipulationMenu->addAction(action_subtract_value); - dataManipulationMenu->addAction(action_multiply_value); - dataManipulationMenu->addAction(action_divide_value); - dataManipulationMenu->addSeparator(); - dataManipulationMenu->addAction(action_mirror_horizontally); - dataManipulationMenu->addAction(action_mirror_vertically); - dataManipulationMenu->addSeparator(); - dataManipulationMenu->addAction(action_transpose); - - m_matrixMenu->addMenu(dataManipulationMenu); - m_matrixMenu->addSeparator(); - submenu = new QMenu(i18n("View"), this); submenu->addAction(action_data_view); submenu->addAction(action_image_view); m_matrixMenu->addMenu(submenu); m_matrixMenu->addSeparator(); - m_matrixMenu->addAction(action_select_all); m_matrixMenu->addAction(action_clear_matrix); m_matrixMenu->addSeparator(); + m_matrixMenu->addAction(action_transpose); + m_matrixMenu->addAction(action_mirror_horizontally); + m_matrixMenu->addAction(action_mirror_vertically); + m_matrixMenu->addSeparator(); + m_headerFormatMenu = new QMenu(i18n("Header Format"), this); m_headerFormatMenu->addAction(action_header_format_1); m_headerFormatMenu->addAction(action_header_format_2); @@ -347,20 +312,6 @@ menu->insertMenu(firstAction, submenu); menu->insertSeparator(firstAction); - - // Data manipulation sub-menu - submenu = new QMenu(i18n("Manipulate Data"), const_cast(this)); - submenu->addAction(action_transpose); - submenu->addAction(action_mirror_horizontally); - submenu->addAction(action_mirror_vertically); - submenu->addAction(action_add_value); - submenu->addAction(action_subtract_value); - submenu->addAction(action_multiply_value); - submenu->addAction(action_divide_value); - - menu->insertMenu(firstAction, submenu); - menu->insertSeparator(firstAction); - submenu = new QMenu(i18n("View"), const_cast(this)); submenu->addAction(action_data_view); submenu->addAction(action_image_view); @@ -370,6 +321,10 @@ menu->insertAction(firstAction, action_select_all); menu->insertAction(firstAction, action_clear_matrix); menu->insertSeparator(firstAction); + menu->insertAction(firstAction, action_transpose); + menu->insertAction(firstAction, action_mirror_horizontally); + menu->insertAction(firstAction, action_mirror_vertically); + menu->insertSeparator(firstAction); // menu->insertAction(firstAction, action_duplicate); menu->insertMenu(firstAction, m_headerFormatMenu); @@ -1287,7 +1242,7 @@ textable << tableCaption; textable << QLatin1String("\\centering \n"); textable << QLatin1String("\\begin{tabular}{"); - textable << (gridLines ? QStringLiteral("|") : QString()); + textable<< (gridLines ?QLatin1String("|") : QLatin1String("")); for (int i = 0; i < columnsPerTable; ++i) textable << centeredColumn; if (verticalHeaders) @@ -1350,7 +1305,7 @@ if (captions) remainingTable << tableCaption; remainingTable << QLatin1String("\\centering \n"); - remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); + remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QLatin1String("|"):QLatin1String("")); for (int c = 0; c < remainingColumns; ++c) remainingTable << centeredColumn; if (verticalHeaders) @@ -1413,7 +1368,7 @@ if (captions) textable << tableCaption; textable << QLatin1String("\\centering \n"); - textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); + textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QLatin1String("|"):QLatin1String("")); for (int c = 0; c < cols; ++c) textable << centeredColumn; if (verticalHeaders) diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp --- a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp +++ b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp @@ -201,15 +201,15 @@ action_clear_selection = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Selection"), this); action_select_all = new QAction(QIcon::fromTheme("edit-select-all"), i18n("Select All"), this); -// action_set_formula = new QAction(QIcon::fromTheme(QString()), i18n("Assign &Formula"), this); -// action_recalculate = new QAction(QIcon::fromTheme(QString()), i18n("Recalculate"), this); - action_fill_sel_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); - action_fill_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); - action_fill_random = new QAction(QIcon::fromTheme(QString()), i18n("Uniform Random Values"), this); - action_fill_random_nonuniform = new QAction(QIcon::fromTheme(QString()), i18n("Random Values"), this); - action_fill_equidistant = new QAction(QIcon::fromTheme(QString()), i18n("Equidistant Values"), this); - action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this); - action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this); +// action_set_formula = new QAction(QIcon::fromTheme(""), i18n("Assign &Formula"), this); +// action_recalculate = new QAction(QIcon::fromTheme(""), i18n("Recalculate"), this); + action_fill_sel_row_numbers = new QAction(QIcon::fromTheme(""), i18n("Row Numbers"), this); + action_fill_row_numbers = new QAction(QIcon::fromTheme(""), i18n("Row Numbers"), this); + action_fill_random = new QAction(QIcon::fromTheme(""), i18n("Uniform Random Values"), this); + action_fill_random_nonuniform = new QAction(QIcon::fromTheme(""), i18n("Random Values"), this); + action_fill_equidistant = new QAction(QIcon::fromTheme(""), i18n("Equidistant Values"), this); + action_fill_function = new QAction(QIcon::fromTheme(""), i18n("Function Values"), this); + action_fill_const = new QAction(QIcon::fromTheme(""), i18n("Const Values"), this); //spreadsheet related actions action_toggle_comments = new QAction(QIcon::fromTheme("document-properties"), i18n("Show Comments"), this); @@ -264,15 +264,15 @@ action_multiply_value->setData(AddSubtractValueDialog::Multiply); action_divide_value = new QAction(i18n("Divide by Value"), this); action_divide_value->setData(AddSubtractValueDialog::Divide); - action_drop_values = new QAction(QIcon::fromTheme(QString()), i18n("Drop Values"), this); - action_mask_values = new QAction(QIcon::fromTheme(QString()), i18n("Mask Values"), this); - action_reverse_columns = new QAction(QIcon::fromTheme(QString()), i18n("Reverse"), this); -// action_join_columns = new QAction(QIcon::fromTheme(QString()), i18n("Join"), this); - action_normalize_columns = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize"), this); - action_normalize_selection = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize Selection"), this); + action_drop_values = new QAction(QIcon::fromTheme(""), i18n("Drop Values"), this); + action_mask_values = new QAction(QIcon::fromTheme(""), i18n("Mask Values"), this); + action_reverse_columns = new QAction(QIcon::fromTheme(""), i18n("Reverse"), this); +// action_join_columns = new QAction(QIcon::fromTheme(""), i18n("Join"), this); + action_normalize_columns = new QAction(QIcon::fromTheme(""), i18n("&Normalize"), this); + action_normalize_selection = new QAction(QIcon::fromTheme(""), i18n("&Normalize Selection"), this); //sort and statistics - action_sort_columns = new QAction(QIcon::fromTheme(QString()), i18n("&Selected Columns"), this); + action_sort_columns = new QAction(QIcon::fromTheme(""), i18n("&Selected Columns"), this); action_sort_asc_column = new QAction(QIcon::fromTheme("view-sort-ascending"), i18n("&Ascending"), this); action_sort_desc_column = new QAction(QIcon::fromTheme("view-sort-descending"), i18n("&Descending"), this); action_statistics_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Column Statisti&cs"), this); @@ -389,12 +389,12 @@ m_columnMenu->addMenu(m_plotDataMenu); // Data manipulation sub-menu - QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation"), this); + QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); dataManipulationMenu->addAction(addDataReductionAction); // Data fit sub-menu - QMenu* dataFitMenu = new QMenu(i18n("Fit"), this); + QMenu* dataFitMenu = new QMenu(i18n("Fit")); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); @@ -489,6 +489,7 @@ m_columnMenu->addSeparator(); m_columnMenu->addAction(action_statistics_columns); + action_statistics_columns->setVisible(false); //Spreadsheet menu m_spreadsheetMenu = new QMenu(this); @@ -509,6 +510,7 @@ m_spreadsheetMenu->addAction(action_toggle_comments); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_statistics_all_columns); + action_statistics_all_columns->setVisible(true); //Row menu m_rowMenu = new QMenu(this); @@ -1963,7 +1965,7 @@ col->setSuppressDataChangedSignal(true); if (formulaModeActive()) { for (const auto& i : selectedRows().intervals()) - col->setFormula(i, QString()); + col->setFormula(i, ""); } else { for (const auto& i : selectedRows().intervals()) { if (i.end() == col->rowCount()-1) @@ -2000,7 +2002,7 @@ int col = m_spreadsheet->indexOfChild(column); for (int row = last; row >= first; row--) if (isCellSelected(row, col)) - column->setFormula(row, QString()); + column->setFormula(row, ""); } else { int col = m_spreadsheet->indexOfChild(column); for (int row = last; row >= first; row--) @@ -2484,7 +2486,7 @@ if (captions) textable << tableCaption; textable << QLatin1String("\\centering \n"); - textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); + textable << QLatin1String("\\begin{tabular}{") << (gridLines ?QLatin1String("|") : QLatin1String("")); for (int i = 0; i < columnsPerTable; ++i) textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); textable << QLatin1String("} \n"); @@ -2557,7 +2559,7 @@ if (captions) remainingTable << tableCaption; remainingTable << QLatin1String("\\centering \n"); - remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); + remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QLatin1String("|"):QLatin1String("")); for (int c = 0; c < remainingColumns; ++c) remainingTable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); remainingTable << QLatin1String("} \n"); @@ -2623,7 +2625,7 @@ if (captions) textable << tableCaption; textable << QLatin1String("\\centering \n"); - textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); + textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QLatin1String("|"):QLatin1String("")); for (int c = 0; c < cols; ++c) textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); textable << QLatin1String("} \n"); diff --git a/src/commonfrontend/widgets/DateTimeSpinBox.h b/src/commonfrontend/widgets/DateTimeSpinBox.h deleted file mode 100644 --- a/src/commonfrontend/widgets/DateTimeSpinBox.h +++ /dev/null @@ -1,74 +0,0 @@ -/*************************************************************************** - File : DateTimeSpinBox.h - Project : LabPlot - Description : widget for setting datetimes with a spinbox - -------------------------------------------------------------------- - Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@gmail.com) - - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the Free Software * - * Foundation, Inc., 51 Franklin Street, Fifth Floor, * - * Boston, MA 02110-1301 USA * - * * - ***************************************************************************/ - -#ifndef DATETIMESPINBOX_H -#define DATETIMESPINBOX_H - -#include - -class QRegularExpressionValidator; - - -// Assumption: Month has always 30 days -class DateTimeSpinBox: public QAbstractSpinBox -{ - - Q_OBJECT; -private: - enum Type { - year, - month, - day, - hour, - minute, - second, - millisecond - }; - -public: - DateTimeSpinBox(QWidget* parent); - void keyPressEvent(QKeyEvent *event) override; - void stepBy(int steps) override; - QAbstractSpinBox::StepEnabled stepEnabled() const override; - bool increaseValue(Type type, int step); - bool changeValue(qint64& thisType, Type nextTypeType, int step); - Type determineType(int cursorPos) const; - void writeValue(); - void setValue(qint64 increment); - qint64 value(); - void getValue(); - void setCursorPosition(Type type); - bool valid(); -private: - QRegularExpressionValidator *m_regularExpressionValidator; - qint64 m_year{0}, m_month{0}, m_day{0}, m_hour{0}, m_minute{0}, m_second{0}, m_millisecond{0}; -signals: - void valueChanged(); -}; - -#endif // DATETIMESPINBOX_H diff --git a/src/commonfrontend/widgets/DateTimeSpinBox.cpp b/src/commonfrontend/widgets/DateTimeSpinBox.cpp deleted file mode 100644 --- a/src/commonfrontend/widgets/DateTimeSpinBox.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/*************************************************************************** - File : DateTimeSpinBox.cpp - Project : LabPlot - Description : widget for setting datetimes with a spinbox - -------------------------------------------------------------------- - Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@gmail.com) - - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the Free Software * - * Foundation, Inc., 51 Franklin Street, Fifth Floor, * - * Boston, MA 02110-1301 USA * - * * - ***************************************************************************/ - -#include "DateTimeSpinBox.h" - -#include -#include -#include - -DateTimeSpinBox::DateTimeSpinBox(QWidget* parent) : QAbstractSpinBox(parent) { - lineEdit()->setText("0000.00.00 00:00:00.001"); - stepEnabled(); - - m_regularExpressionValidator = new QRegularExpressionValidator(); - - QRegularExpression regExp("([0-9]+)\\.(0[0-9]|1[0-2]|[0-9])\\.(0[0-9]|[0-2][0-9]|30|[0-9]) ([0-1][0-9]|2[0-3]|[0-9])\\:([0-5][0-9]|[0-9])\\:([0-5][0-9]|[0-9])\\.[0-9]{0,3}"); - m_regularExpressionValidator->setRegularExpression(regExp); - - lineEdit()->setValidator(m_regularExpressionValidator); -} - -void DateTimeSpinBox::keyPressEvent(QKeyEvent* event) { - if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9) { - int cursorPos = lineEdit()->cursorPosition(); - int textLenght = lineEdit()->text().length(); - QAbstractSpinBox::keyPressEvent(event); - getValue(); - if (lineEdit()->text().length() != textLenght) - lineEdit()->setCursorPosition(cursorPos + 1); - else - lineEdit()->setCursorPosition(cursorPos); - } else if (event->key() == Qt::Key_Up) { - Type type = determineType(lineEdit()->cursorPosition()); - increaseValue(type, 1); - writeValue(); - setCursorPosition(type); - } else if (event->key() == Qt::Key_Down) { - Type type = determineType(lineEdit()->cursorPosition()); - increaseValue(type, -1); - writeValue(); - setCursorPosition(type); - } else { - QAbstractSpinBox::keyPressEvent(event); - getValue(); - } -} - -QAbstractSpinBox::StepEnabled DateTimeSpinBox::stepEnabled() const { - return QAbstractSpinBox::StepEnabledFlag::StepUpEnabled | QAbstractSpinBox::StepEnabledFlag::StepDownEnabled; // for testing -} - -void DateTimeSpinBox::stepBy(int steps) { - Type type = determineType(lineEdit()->cursorPosition()); - increaseValue(type, steps); - writeValue(); - setCursorPosition(type); -} - -/*! - * Write value to lineEdit of the spinbox - */ -void DateTimeSpinBox::writeValue() { - lineEdit()->setText(QString::number(m_year) + "." + - QString("%1").arg(m_month, 2, 10, QLatin1Char('0')) + "." + - QString("%1").arg(m_day, 2, 10, QLatin1Char('0')) + " " + - QString("%1").arg(m_hour, 2, 10, QLatin1Char('0')) + ":" + - QString("%1").arg(m_minute, 2, 10, QLatin1Char('0')) + ":" + - QString("%1").arg(m_second, 2, 10, QLatin1Char('0')) + "." + - QString("%1").arg(m_millisecond, 3, 10, QLatin1Char('0'))); - emit valueChanged(); -} - -void DateTimeSpinBox::setValue(qint64 increment) { - qint64 divisor = qint64(12) * 30 * 24 * 60 * 60 * 1000; - qint64 rest; - m_year = increment / divisor; - rest = increment - m_year * divisor; - divisor = qint64(30) * 24 * 60 * 60 * 1000; - m_month = rest / divisor; - rest = rest - m_month * divisor; - divisor = qint64(24) * 60 * 60 * 1000; - m_day = rest / divisor; - rest = rest - m_day * divisor; - divisor = qint64(60) * 60 * 1000; - m_hour = rest / divisor; - rest -= m_hour * divisor; - divisor = qint64(60)* 1000; - m_minute = rest / divisor; - rest -= m_minute * divisor; - divisor = qint64(1000); - m_second = rest /divisor; - rest -= m_second * divisor; - m_millisecond = rest; - - writeValue(); -} - -qint64 DateTimeSpinBox::value() { - return m_millisecond - + 1000 * (m_second - + 60 * (m_minute - + 60 * (m_hour - + 24 * (m_day - + 30 * (m_month - + 12 * m_year))))); -} - -/*! - * Read value from lineEdit of the spinbox - */ -void DateTimeSpinBox::getValue() { - QString text = lineEdit()->text(); - - int counter = 0; - int startIndex = 0; - for (int i=0; i< text.length(); i++) { - if (text[i] == '.' || text[i] == ':' || text[i] == ' ' || i == text.length()-1) { - switch(counter) { - case Type::year: - m_year = text.mid(startIndex, i - startIndex).toInt(); - break; - case Type::month: - m_month = text.mid(startIndex, i - startIndex).toInt(); - break; - case Type::day: - m_day = text.mid(startIndex, i - startIndex).toInt(); - break; - case Type::hour: - m_hour = text.mid(startIndex, i - startIndex).toInt(); - break; - case Type::minute: - m_minute = text.mid(startIndex, i - startIndex).toInt(); - break; - case Type::second: - m_second = text.mid(startIndex, i - startIndex).toInt(); - break; - case Type::millisecond: - m_millisecond = text.mid(startIndex, i - startIndex + 1).toInt(); // because of the condition (i == text.length()-1) - break; - } - startIndex = i+1; - counter ++; - } - } - - emit valueChanged(); -} - -void DateTimeSpinBox::setCursorPosition(Type type) { - QString text = lineEdit()->text(); - int counter = 0; - for (int i = 0; i < text.length(); i++) { - if (text[i] == '.' || text[i] == ':' || text[i] == ' ') - counter ++; - - if (counter-1 == type) { - lineEdit()->setCursorPosition(i); - break; - } - } -} - -bool DateTimeSpinBox::valid() { - return true; -} - -// step can also be negative -bool DateTimeSpinBox::increaseValue(DateTimeSpinBox::Type type, int step) { - switch (type) { - - case Type::year: { - if (m_year + step < 0 && step < 0) { - if (m_year + step < 0) { - m_year = 0; - return false; - } - } - m_year += step; - return true; - } - break; - case Type::month: - return changeValue(m_month, Type::year, step); - break; - case Type::day: - return changeValue(m_day, Type::month, step); - break; - case Type::hour: - return changeValue(m_hour, Type::day, step); - break; - case Type::minute: - return changeValue(m_minute, Type::hour, step); - break; - case Type::second: - return changeValue(m_second, Type::minute, step); - break; - case Type::millisecond: - return changeValue(m_millisecond, Type::second, step); - break; - default: - return false; - break; - } -} - -bool DateTimeSpinBox::changeValue(qint64& thisType, DateTimeSpinBox::Type nextTypeType, int step) { - int maxValue = 1; - switch (nextTypeType) { - case (Type::year): - maxValue = 12; - break; - case (Type::month): - maxValue = 30; - break; - case (Type::day): - maxValue = 24; - break; - case (Type::hour): - maxValue = 60; - break; - case (Type::minute): - maxValue = 60; - break; - case (Type::second): - maxValue = 1000; - break; - case (Type::millisecond): - return false; - } - - int nextTypeCounter = step / maxValue; - step -= nextTypeCounter * maxValue; - if (thisType + step < 0 && step < 0) { - nextTypeCounter --; - if (increaseValue(nextTypeType, nextTypeCounter)) { - step += maxValue; - thisType += step; - return true; - } else { - thisType = 0; - return false; - } - } else if ( thisType + step > maxValue-1 && step > 0) { - step -= nextTypeCounter * maxValue; - if (thisType + step > maxValue-1) { - nextTypeCounter ++; - step -= maxValue; - thisType += step; - } else - thisType += step; - - - return increaseValue(nextTypeType, nextTypeCounter); - } - thisType += step; - return true; -} - -DateTimeSpinBox::Type DateTimeSpinBox::determineType(int cursorPos) const{ - QString text = lineEdit()->text(); - - if (cursorPos > text.length()) - cursorPos = text.length(); - - int counter = 0; - for (int i = 0; i < cursorPos; i++) { - if (text[i] == '.' || text[i] == ':' || text[i] == ' ') - counter ++; - } - - if (counter <= Type::millisecond) - return static_cast(counter); - - return Type::millisecond; -} diff --git a/src/commonfrontend/widgets/TreeViewComboBox.cpp b/src/commonfrontend/widgets/TreeViewComboBox.cpp --- a/src/commonfrontend/widgets/TreeViewComboBox.cpp +++ b/src/commonfrontend/widgets/TreeViewComboBox.cpp @@ -74,7 +74,7 @@ m_lineEdit->setClearButtonEnabled(true); m_lineEdit->setFocus(); - addItem(QString()); + addItem(""); setCurrentIndex(0); // signal activated() is platform dependent @@ -132,7 +132,7 @@ QModelIndex root = m_treeView->model()->index(0,0); showTopLevelOnly(root); - m_lineEdit->setText(QString()); + m_lineEdit->setText(""); m_groupBox->show(); m_groupBox->resize(this->width(), 250); m_groupBox->move(mapToGlobal( this->rect().topLeft() )); @@ -179,7 +179,7 @@ m_treeView->setCurrentIndex(QModelIndex()); setCurrentIndex(0); - QComboBox::setItemText(0, QString()); + QComboBox::setItemText(0, ""); emit currentModelIndexChanged(QModelIndex()); m_groupBox->hide(); } diff --git a/src/commonfrontend/worksheet/WorksheetView.h b/src/commonfrontend/worksheet/WorksheetView.h --- a/src/commonfrontend/worksheet/WorksheetView.h +++ b/src/commonfrontend/worksheet/WorksheetView.h @@ -182,19 +182,17 @@ QAction* fourTimesMagnificationAction; QAction* fiveTimesMagnificationAction; - QAction* plotsLockedAction; QAction* showPresenterMode; - //Actions for cartesian plots QAction* cartesianPlotApplyToSelectionAction; QAction* cartesianPlotApplyToAllAction; + QAction* cartesianPlotLockPosZoomAction; QAction* cartesianPlotSelectionModeAction; QAction* cartesianPlotZoomSelectionModeAction; QAction* cartesianPlotZoomXSelectionModeAction; QAction* cartesianPlotZoomYSelectionModeAction; QAction* addCurveAction; - QAction* addHistogramAction; QAction* addEquationCurveAction; QAction* addDataOperationCurveAction; QAction* addDataReductionCurveAction; @@ -262,7 +260,6 @@ void changeLayout(QAction*); void changeGrid(QAction*); void changeSnapToGrid(); - void plotsLockedActionChanged(bool checked); void deselectItem(QGraphicsItem*); void selectionChanged(); @@ -278,6 +275,7 @@ //SLOTs for cartesian plots void cartesianPlotActionModeChanged(QAction*); + void cartesianPlotLockPosZoomActionChanged(bool checked); void cartesianPlotMouseModeChanged(QAction*); void cartesianPlotNavigationChanged(QAction*); void cartesianPlotAddNew(QAction*); diff --git a/src/commonfrontend/worksheet/WorksheetView.cpp b/src/commonfrontend/worksheet/WorksheetView.cpp --- a/src/commonfrontend/worksheet/WorksheetView.cpp +++ b/src/commonfrontend/worksheet/WorksheetView.cpp @@ -247,13 +247,6 @@ connect(snapToGridAction, SIGNAL(triggered()), this, SLOT(changeSnapToGrid())); connect(showPresenterMode, SIGNAL(triggered()), this, SLOT(presenterMode())); - //worksheet control actions - plotsLockedAction = new QAction(i18n("Non-interactive Plots"), this); - plotsLockedAction->setToolTip(i18n("If activated, plots on the worksheet don't react on drag and mouse wheel events.")); - plotsLockedAction->setCheckable(true); - plotsLockedAction->setChecked(m_worksheet->plotsLocked()); - connect(plotsLockedAction, &QAction::triggered, this, &WorksheetView::plotsLockedActionChanged); - //action for cartesian plots auto* cartesianPlotActionModeActionGroup = new QActionGroup(this); cartesianPlotActionModeActionGroup->setExclusive(true); @@ -264,6 +257,12 @@ setCartesianPlotActionMode(m_worksheet->cartesianPlotActionMode()); connect(cartesianPlotActionModeActionGroup, SIGNAL(triggered(QAction*)), SLOT(cartesianPlotActionModeChanged(QAction*))); + cartesianPlotLockPosZoomAction = new QAction(i18n("Lock Plots position")); + cartesianPlotLockPosZoomAction->setCheckable(true); + if (m_worksheet->lockPlot()) + cartesianPlotLockPosZoomAction->setChecked(true); + connect(cartesianPlotLockPosZoomAction, &QAction::triggered, this, &WorksheetView::cartesianPlotLockPosZoomActionChanged); + auto* cartesianPlotMouseModeActionGroup = new QActionGroup(this); cartesianPlotMouseModeActionGroup->setExclusive(true); cartesianPlotSelectionModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Edit"), cartesianPlotMouseModeActionGroup); @@ -283,27 +282,26 @@ auto* cartesianPlotAddNewActionGroup = new QActionGroup(this); addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), cartesianPlotAddNewActionGroup); - addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), cartesianPlotAddNewActionGroup); - addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), cartesianPlotAddNewActionGroup); + addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve From a Mathematical Equation"), cartesianPlotAddNewActionGroup); // TODO: no own icons yet - addDataOperationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve from a Data Operation"), cartesianPlotAddNewActionGroup); + addDataOperationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve From a Data Operation"), cartesianPlotAddNewActionGroup); // addDataOperationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-data-operation-curve"), i18n("xy-curve From a Data Operation"), cartesianPlotAddNewActionGroup); - addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve from a Data Reduction"), cartesianPlotAddNewActionGroup); + addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve From a Data Reduction"), cartesianPlotAddNewActionGroup); // addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-data-reduction-curve"), i18n("xy-curve From a Data Reduction"), cartesianPlotAddNewActionGroup); - addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve from a Differentiation"), cartesianPlotAddNewActionGroup); + addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve From a Differentiation"), cartesianPlotAddNewActionGroup); // addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-differentiation-curve"), i18n("xy-curve From a Differentiation"), cartesianPlotAddNewActionGroup); - addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve from an Integration"), cartesianPlotAddNewActionGroup); + addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve From an Integration"), cartesianPlotAddNewActionGroup); // addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-integration-curve"), i18n("xy-curve From an Integration"), cartesianPlotAddNewActionGroup); - addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve from a (De-)Convolution"), cartesianPlotAddNewActionGroup); + addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve From a (De-)Convolution"), cartesianPlotAddNewActionGroup); // addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("xy-curve From a (De-)Convolution"), cartesianPlotAddNewActionGroup); - addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve from a Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); -// addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("xy-curve From a Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); + addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve From a Correlation"), cartesianPlotAddNewActionGroup); +// addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("xy-curve From a Correlation"), cartesianPlotAddNewActionGroup); addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("xy-curve From an Interpolation"), cartesianPlotAddNewActionGroup); - addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("xy-curve from a Smooth"), cartesianPlotAddNewActionGroup); - addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve from a Fit to Data"), cartesianPlotAddNewActionGroup); - addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("xy-curve from a Fourier Filter"), cartesianPlotAddNewActionGroup); - addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("xy-curve from a Fourier Transform"), cartesianPlotAddNewActionGroup); + addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("xy-curve From a Smooth"), cartesianPlotAddNewActionGroup); + addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve From a Fit to Data"), cartesianPlotAddNewActionGroup); + addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("xy-curve From a Fourier Filter"), cartesianPlotAddNewActionGroup); + addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("xy-curve From a Fourier Transform"), cartesianPlotAddNewActionGroup); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), cartesianPlotAddNewActionGroup); addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal Axis"), cartesianPlotAddNewActionGroup); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical Axis"), cartesianPlotAddNewActionGroup); @@ -321,8 +319,8 @@ // addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-integration-curve"), i18n("Integration"), cartesianPlotAddNewActionGroup); addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Convolution/Deconvolution"), cartesianPlotAddNewActionGroup); // addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("Convolution/Deconvolution"), cartesianPlotAddNewActionGroup); - addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); -// addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-correlation-curve"), i18n("Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); + addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Correlation"), cartesianPlotAddNewActionGroup); +// addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("Correlation"), cartesianPlotAddNewActionGroup); addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolation"), cartesianPlotAddNewActionGroup); addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), cartesianPlotAddNewActionGroup); @@ -440,10 +438,8 @@ m_cartesianPlotAddNewMenu = new QMenu(i18n("Add New"), this); m_cartesianPlotAddNewMenu->addAction(addCurveAction); - m_cartesianPlotAddNewMenu->addAction(addHistogramAction); m_cartesianPlotAddNewMenu->addAction(addEquationCurveAction); - m_cartesianPlotAddNewMenu->addSeparator(); -// m_cartesianPlotAddNewMenu->addAction(addDataOperationCurveAction); + m_cartesianPlotAddNewMenu->addAction(addDataOperationCurveAction); m_cartesianPlotAddNewMenu->addAction(addDataReductionCurveAction); m_cartesianPlotAddNewMenu->addAction(addDifferentiationCurveAction); m_cartesianPlotAddNewMenu->addAction(addIntegrationCurveAction); @@ -454,7 +450,6 @@ m_cartesianPlotAddNewMenu->addAction(addFourierTransformCurveAction); m_cartesianPlotAddNewMenu->addAction(addConvolutionCurveAction); m_cartesianPlotAddNewMenu->addAction(addCorrelationCurveAction); - m_cartesianPlotAddNewMenu->addSeparator(); m_cartesianPlotAddNewMenu->addAction(addLegendAction); m_cartesianPlotAddNewMenu->addSeparator(); m_cartesianPlotAddNewMenu->addAction(addHorizontalAxisAction); @@ -490,16 +485,16 @@ m_cartesianPlotMenu->addMenu(m_cartesianPlotZoomMenu); m_cartesianPlotMenu->addSeparator(); m_cartesianPlotMenu->addMenu(m_cartesianPlotActionModeMenu); - m_cartesianPlotMenu->addAction(plotsLockedAction); + m_cartesianPlotMenu->addAction(cartesianPlotLockPosZoomAction); // Data manipulation menu - m_dataManipulationMenu = new QMenu(i18n("Data Manipulation") ,this); + m_dataManipulationMenu = new QMenu(i18n("Data Manipulation")); m_dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); m_dataManipulationMenu->addAction(addDataOperationAction); m_dataManipulationMenu->addAction(addDataReductionAction); //themes menu - m_themeMenu = new QMenu(i18n("Apply Theme"), this); + m_themeMenu = new QMenu(i18n("Apply Theme")); auto* themeWidget = new ThemesWidget(nullptr); connect(themeWidget, SIGNAL(themeSelected(QString)), m_worksheet, SLOT(setTheme(QString))); connect(themeWidget, SIGNAL(themeSelected(QString)), m_themeMenu, SLOT(close())); @@ -540,8 +535,6 @@ menu->insertMenu(firstAction, m_gridMenu); menu->insertMenu(firstAction, m_themeMenu); menu->insertSeparator(firstAction); - menu->insertAction(firstAction, plotsLockedAction); - menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_cartesianPlotMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, showPresenterMode); @@ -607,7 +600,6 @@ toolBar->addAction(cartesianPlotZoomYSelectionModeAction); toolBar->addSeparator(); toolBar->addAction(addCurveAction); - toolBar->addAction(addHistogramAction); toolBar->addAction(addEquationCurveAction); // don't over-populate the tool bar // toolBar->addAction(addDifferentiationCurveAction); @@ -621,7 +613,6 @@ // toolBar->addAction(addFourierTransformCurveAction); // toolBar->addAction(addConvolutionCurveAction); // toolBar->addAction(addCorrelationCurveAction); - toolBar->addSeparator(); toolBar->addAction(addLegendAction); toolBar->addSeparator(); toolBar->addAction(addHorizontalAxisAction); @@ -660,7 +651,7 @@ } void WorksheetView::setPlotLock(bool lock) { - plotsLockedAction->setChecked(lock); + cartesianPlotLockPosZoomAction->setChecked(lock); } void WorksheetView::drawForeground(QPainter* painter, const QRectF& rect) { @@ -937,29 +928,29 @@ //to select curves having overlapping bounding boxes we need to check whether the cursor //is inside of item's shapes and not inside of the bounding boxes (Qt's default behaviour). - for (auto* item : items(event->pos())) { - if (!dynamic_cast(item)) - continue; +// for (auto* item : items(event->pos())) { +// if (!dynamic_cast(item)) +// continue; - if ( item->shape().contains(item->mapFromScene(mapToScene(event->pos()))) ) { - //deselect currently selected items - for (auto* selectedItem : scene()->selectedItems()) - selectedItem->setSelected(false); +// if ( item->shape().contains(item->mapFromScene(mapToScene(event->pos()))) ) { +// //deselect currently selected items +// for (auto* selectedItem : scene()->selectedItems()) +// selectedItem->setSelected(false); - //select the item under the cursor and update the current selection - item->setSelected(true); - selectionChanged(); +// //select the item under the cursor and update the current selection +// item->setSelected(true); +// selectionChanged(); - return; - } - } +// return; +// } +// } // select the worksheet in the project explorer if the view was clicked // and there is no selection currently. We need this for the case when // there is a single worksheet in the project and we change from the project-node // in the project explorer to the worksheet-node by clicking the view. - if ( scene()->selectedItems().isEmpty() ) - m_worksheet->setSelectedInView(true); +// if ( scene()->selectedItems().isEmpty() ) +// m_worksheet->setSelectedInView(true); QGraphicsView::mousePressEvent(event); } @@ -1525,7 +1516,6 @@ cartesianPlotZoomYSelectionModeAction->setEnabled(plot); addCurveAction->setEnabled(plot); - addHistogramAction->setEnabled(plot); addEquationCurveAction->setEnabled(plot); addDataOperationCurveAction->setEnabled(false); addDataReductionCurveAction->setEnabled(plot); @@ -1753,8 +1743,8 @@ handleCartesianPlotActions(); } -void WorksheetView::plotsLockedActionChanged(bool checked) { - m_worksheet->setPlotsLocked(checked); +void WorksheetView::cartesianPlotLockPosZoomActionChanged(bool checked) { + m_worksheet->setLockPlot(checked); } void WorksheetView::cartesianPlotMouseModeChanged(QAction* action) { @@ -1807,8 +1797,6 @@ DEBUG("WorksheetView::cartesianPlotAdd()"); if (action == addCurveAction) plot->addCurve(); - else if (action == addHistogramAction) - plot->addHistogram(); else if (action == addEquationCurveAction) plot->addEquationCurve(); else if (action == addDataReductionCurveAction) diff --git a/src/kdefrontend/GuiObserver.cpp b/src/kdefrontend/GuiObserver.cpp --- a/src/kdefrontend/GuiObserver.cpp +++ b/src/kdefrontend/GuiObserver.cpp @@ -117,15 +117,11 @@ and activates the corresponding dockwidgets, toolbars etc. */ void GuiObserver::selectedAspectsChanged(QList& selectedAspects) const { - auto clearDock = [&](){ + if (selectedAspects.isEmpty()) { if (m_mainWindow->stackedWidget->currentWidget()) m_mainWindow->stackedWidget->currentWidget()->hide(); m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); - }; - - if (selectedAspects.isEmpty()) { - clearDock(); return; } @@ -136,7 +132,10 @@ for (auto* aspect : selectedAspects) { className = aspect->metaObject()->className(); if (className != prevClassName && !prevClassName.isEmpty()) { - clearDock(); + if (m_mainWindow->stackedWidget->currentWidget()) + m_mainWindow->stackedWidget->currentWidget()->hide(); + + m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); return; } prevClassName = className; @@ -145,7 +144,7 @@ if (m_mainWindow->stackedWidget->currentWidget()) m_mainWindow->stackedWidget->currentWidget()->show(); - if (className == QStringLiteral("Spreadsheet")) { + if (className == "Spreadsheet") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Spreadsheet")); if (!m_mainWindow->spreadsheetDock) { @@ -160,7 +159,7 @@ m_mainWindow->spreadsheetDock->setSpreadsheets(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->spreadsheetDock); - } else if (className == QStringLiteral("Column")) { + } else if (className == "Column") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Column")); if (!m_mainWindow->columnDock) { @@ -175,7 +174,7 @@ m_mainWindow->columnDock->setColumns(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->columnDock); - } else if (className == QStringLiteral("Matrix")) { + } else if (className == "Matrix") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Matrix")); if (!m_mainWindow->matrixDock) { @@ -190,7 +189,7 @@ m_mainWindow->matrixDock->setMatrices(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->matrixDock); - } else if (className == QStringLiteral("Worksheet")) { + } else if (className == "Worksheet") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Worksheet")); if (!m_mainWindow->worksheetDock) { @@ -205,7 +204,7 @@ m_mainWindow->worksheetDock->setWorksheets(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->worksheetDock); - } else if (className == QStringLiteral("CartesianPlot")) { + } else if (className == "CartesianPlot") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Cartesian Plot")); if (!m_mainWindow->cartesianPlotDock) { @@ -220,7 +219,7 @@ m_mainWindow->cartesianPlotDock->setPlots(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->cartesianPlotDock); - } else if (className == QStringLiteral("CartesianPlotLegend")) { + } else if (className == "CartesianPlotLegend") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Legend")); if (!m_mainWindow->cartesianPlotLegendDock) { @@ -235,7 +234,7 @@ m_mainWindow->cartesianPlotLegendDock->setLegends(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->cartesianPlotLegendDock); - } else if (className == QStringLiteral("Axis")) { + } else if (className == "Axis") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Axis")); if (!m_mainWindow->axisDock) { @@ -250,7 +249,7 @@ m_mainWindow->axisDock->setAxes(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->axisDock); - } else if (className == QStringLiteral("XYCurve")) { + } else if (className == "XYCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "xy-Curve")); if (!m_mainWindow->xyCurveDock) { @@ -266,7 +265,7 @@ m_mainWindow->xyCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyCurveDock); - } else if (className == QStringLiteral("XYEquationCurve")) { + } else if (className == "XYEquationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "xy-Equation")); if (!m_mainWindow->xyEquationCurveDock) { @@ -282,7 +281,7 @@ m_mainWindow->xyEquationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyEquationCurveDock); - } else if (className == QStringLiteral("XYDataReductionCurve")) { + } else if (className == "XYDataReductionCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Data Reduction")); if (!m_mainWindow->xyDataReductionCurveDock) { @@ -298,7 +297,7 @@ m_mainWindow->xyDataReductionCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyDataReductionCurveDock); - } else if (className == QStringLiteral("XYDifferentiationCurve")) { + } else if (className == "XYDifferentiationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Differentiation")); if (!m_mainWindow->xyDifferentiationCurveDock) { @@ -314,7 +313,7 @@ m_mainWindow->xyDifferentiationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyDifferentiationCurveDock); - } else if (className == QStringLiteral("XYIntegrationCurve")) { + } else if (className == "XYIntegrationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Integration")); if (!m_mainWindow->xyIntegrationCurveDock) { @@ -330,7 +329,7 @@ m_mainWindow->xyIntegrationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyIntegrationCurveDock); - } else if (className == QStringLiteral("XYInterpolationCurve")) { + } else if (className == "XYInterpolationCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Interpolation")); if (!m_mainWindow->xyInterpolationCurveDock) { @@ -346,7 +345,7 @@ m_mainWindow->xyInterpolationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyInterpolationCurveDock); - } else if (className == QStringLiteral("XYSmoothCurve")) { + } else if (className == "XYSmoothCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Smoothing")); if (!m_mainWindow->xySmoothCurveDock) { @@ -362,7 +361,7 @@ m_mainWindow->xySmoothCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xySmoothCurveDock); - } else if (className == QStringLiteral("XYFitCurve")) { + } else if (className == "XYFitCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fit")); if (!m_mainWindow->xyFitCurveDock) { @@ -377,7 +376,7 @@ list << qobject_cast(aspect); m_mainWindow->xyFitCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyFitCurveDock); - } else if (className == QStringLiteral("XYFourierTransformCurve")) { + } else if (className == "XYFourierTransformCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fourier Transform")); if (!m_mainWindow->xyFourierTransformCurveDock) { @@ -394,7 +393,7 @@ m_mainWindow->xyFourierTransformCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyFourierTransformCurveDock); - } else if (className == QStringLiteral("XYFourierFilterCurve")) { + } else if (className == "XYFourierFilterCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Fourier Filter")); if (!m_mainWindow->xyFourierFilterCurveDock) { @@ -410,7 +409,7 @@ m_mainWindow->xyFourierFilterCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyFourierFilterCurveDock); - } else if (className == QStringLiteral("XYConvolutionCurve")) { + } else if (className == "XYConvolutionCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Convolution/Deconvolution")); if (!m_mainWindow->xyConvolutionCurveDock) { @@ -426,8 +425,8 @@ m_mainWindow->xyConvolutionCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyConvolutionCurveDock); - } else if (className == QStringLiteral("XYCorrelationCurve")) { - m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Auto-/Cross-Correlation")); + } else if (className == "XYCorrelationCurve") { + m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Correlation")); if (!m_mainWindow->xyCorrelationCurveDock) { m_mainWindow->xyCorrelationCurveDock = new XYCorrelationCurveDock(m_mainWindow->stackedWidget); @@ -442,7 +441,7 @@ m_mainWindow->xyCorrelationCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->xyCorrelationCurveDock); - } else if (className == QStringLiteral("Histogram")) { + } else if (className == "Histogram") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Histogram Properties")); if (!m_mainWindow->histogramDock) { @@ -457,7 +456,7 @@ m_mainWindow->histogramDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->histogramDock); - } else if (className == QStringLiteral("TextLabel")) { + } else if (className == "TextLabel") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Text Label")); if (!m_mainWindow->textLabelDock) { @@ -471,7 +470,7 @@ m_mainWindow->textLabelDock->setLabels(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->textLabelDock); - } else if (className == QStringLiteral("CustomPoint")) { + } else if (className == "CustomPoint") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Custom Point")); if (!m_mainWindow->customPointDock) { @@ -485,7 +484,7 @@ m_mainWindow->customPointDock->setPoints(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->customPointDock); - } else if (className == QStringLiteral("DatapickerCurve")) { + } else if (className == "DatapickerCurve") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Datapicker Curve")); if (!m_mainWindow->datapickerCurveDock) { @@ -499,7 +498,7 @@ m_mainWindow->datapickerCurveDock->setCurves(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->datapickerCurveDock); - } else if (className == QStringLiteral("Datapicker")) { + } else if (className == "Datapicker") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Datapicker")); if (!m_mainWindow->datapickerImageDock) { @@ -513,7 +512,7 @@ m_mainWindow->datapickerImageDock->setImages(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->datapickerImageDock); - } else if (className == QStringLiteral("Project")) { + } else if (className == "Project") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Project")); if (!m_mainWindow->projectDock) { @@ -524,7 +523,7 @@ m_mainWindow->projectDock->setProject(m_mainWindow->m_project); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->projectDock); - } else if (className == QStringLiteral("CantorWorksheet")) { + } else if (className == "CantorWorksheet") { #ifdef HAVE_CANTOR_LIBS if (!m_mainWindow->cantorWorksheetDock) { m_mainWindow->cantorWorksheetDock = new CantorWorksheetDock(m_mainWindow->stackedWidget); @@ -543,7 +542,7 @@ m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->cantorWorksheetDock); #endif - } else if (className == QStringLiteral("Notes")) { + } else if (className == "Notes") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Notes")); if (!m_mainWindow->notesDock) { @@ -559,7 +558,7 @@ m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->notesDock); } #ifdef HAVE_MQTT - else if (className == QStringLiteral("MQTTClient")) { + else if (className == QLatin1String("MQTTClient")) { m_mainWindow->m_propertiesDock->setWindowTitle(i18n("MQTT Data Source")); if (!m_mainWindow->m_liveDataDock) { @@ -568,10 +567,13 @@ m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } - m_mainWindow->m_liveDataDock->setMQTTClient(static_cast(selectedAspects.first())); + QList list; + for (auto* aspect : selectedAspects) + list << qobject_cast(aspect); + m_mainWindow->m_liveDataDock->setMQTTClients(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); - } else if (className == QStringLiteral("MQTTSubscription")) { + } else if (className == QLatin1String("MQTTSubscription")) { m_mainWindow->m_propertiesDock->setWindowTitle(i18n("MQTT Data Source")); if (!m_mainWindow->m_liveDataDock) { @@ -580,10 +582,23 @@ m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } - m_mainWindow->m_liveDataDock->setMQTTClient(static_cast(selectedAspects.first())->mqttClient()); + QList list; + for (auto* aspect : selectedAspects) { + const QString clientName = qobject_cast(aspect)->mqttClient()->name(); + bool found = false; + for (const auto* client : list) { + if (client->name() == clientName) { + found = true; + break; + } + } + if (!found) + list << qobject_cast(aspect)->mqttClient(); + } + m_mainWindow->m_liveDataDock->setMQTTClients(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); - } else if (className == QStringLiteral("MQTTTopic")) { + } else if (className == QLatin1String("MQTTTopic")) { m_mainWindow->m_propertiesDock->setWindowTitle(i18n("MQTT Data Source")); if (!m_mainWindow->m_liveDataDock) { @@ -591,12 +606,25 @@ m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } - m_mainWindow->m_liveDataDock->setMQTTClient(static_cast(selectedAspects.first())->mqttClient()); + QList list; + for (auto* aspect : selectedAspects) { + QString clientName = qobject_cast(qobject_cast(aspect)->mqttClient())->name(); + bool found = false; + for (const auto* client : list) { + if (client->name() == clientName) { + found = true; + break; + } + } + if (!found) + list << qobject_cast(qobject_cast(aspect)->mqttClient()); + } + m_mainWindow->m_liveDataDock->setMQTTClients(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); } #endif - else if (className == QStringLiteral("LiveDataSource")) { + else if (className == "LiveDataSource") { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Live Data Source")); if (!m_mainWindow->m_liveDataDock) { @@ -604,11 +632,16 @@ m_mainWindow->stackedWidget->addWidget(m_mainWindow->m_liveDataDock); } - m_mainWindow->m_liveDataDock->setLiveDataSource(static_cast(selectedAspects.first())); + QList list; + for (auto* aspect : selectedAspects) + list << qobject_cast(aspect); + m_mainWindow->m_liveDataDock->setLiveDataSources(list); m_mainWindow->stackedWidget->setCurrentWidget(m_mainWindow->m_liveDataDock); } else { - clearDock(); + m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); + if (m_mainWindow->stackedWidget->currentWidget()) + m_mainWindow->stackedWidget->currentWidget()->hide(); } } @@ -623,19 +656,19 @@ return; QString className = parent->metaObject()->className(); - if (className == QStringLiteral("Axis")) { + if (className == "Axis") { if (!m_mainWindow->axisDock) { m_mainWindow->axisDock = new AxisDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->axisDock); } m_mainWindow->axisDock->activateTitleTab(); - } else if (className == QStringLiteral("CartesianPlot")) { + } else if (className == "CartesianPlot") { if (!m_mainWindow->cartesianPlotDock) { m_mainWindow->cartesianPlotDock = new CartesianPlotDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotDock); } m_mainWindow->cartesianPlotDock->activateTitleTab(); - } else if (className == QStringLiteral("CartesianPlotLegend")) { + } else if (className == "CartesianPlotLegend") { if (!m_mainWindow->cartesianPlotLegendDock) { m_mainWindow->cartesianPlotLegendDock = new CartesianPlotLegendDock(m_mainWindow->stackedWidget); m_mainWindow->stackedWidget->addWidget(m_mainWindow->cartesianPlotLegendDock); diff --git a/src/kdefrontend/HistoryDialog.cpp b/src/kdefrontend/HistoryDialog.cpp --- a/src/kdefrontend/HistoryDialog.cpp +++ b/src/kdefrontend/HistoryDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : history dialog -------------------------------------------------------------------- - Copyright : (C) 2012-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2012-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -26,19 +26,15 @@ * * ***************************************************************************/ #include "HistoryDialog.h" - +#include +#include +#include +#include #include #include #include -#include -#include -#include - -#include -#include -#include #include - +#include /*! \class HistoryDialog \brief Display the content of project's undo stack. @@ -84,15 +80,12 @@ layout->addWidget(btnBox); setLayout(layout); - - //restore saved settings if available - create(); // ensure there's a window created + //restore saved dialog size if available KConfigGroup conf(KSharedConfig::openConfig(), "HistoryDialog"); - if (conf.exists()) { + if (conf.exists()) KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(500, 300).expandedTo(minimumSize())); + else + resize( QSize(500, 300).expandedTo(minimumSize()) ); } HistoryDialog::~HistoryDialog() { diff --git a/src/kdefrontend/LabPlot.cpp b/src/kdefrontend/LabPlot.cpp --- a/src/kdefrontend/LabPlot.cpp +++ b/src/kdefrontend/LabPlot.cpp @@ -58,7 +58,7 @@ KAboutData aboutData( QStringLiteral("labplot2"), QString("labplot2"), LVERSION, i18n("LabPlot2 is a KDE-application for interactive graphing and analysis of scientific data."), - KAboutLicense::GPL,i18n("(c) 2007-2019"), QString(), QStringLiteral("https://labplot.kde.org")); + KAboutLicense::GPL,i18n("(c) 2007-2018"), QString(), QStringLiteral("https://labplot.kde.org")); aboutData.addAuthor(i18n("Stefan Gerlach"), i18nc("@info:credit", "Developer"), "stefan.gerlach@uni.kn", nullptr); aboutData.addAuthor(i18n("Alexander Semke"), i18nc("@info:credit", "Developer"), "alexander.semke@web.de", nullptr); aboutData.addAuthor(i18n("Fábián Kristóf-Szabolcs"), i18nc("@info:credit", "Developer"), "f-kristof@hotmail.com", nullptr); @@ -102,7 +102,7 @@ i18n("Failed to Open")) == KMessageBox::Cancel) { exit(-1); //"Cancel" clicked -> exit the application } else { - filename.clear(); //Wrong file -> clear the file name and continue + filename = ""; //Wrong file -> clear the file name and continue } } } @@ -142,10 +142,8 @@ MainWin* window = new MainWin(nullptr, filename); window->show(); - if (splash) { + if (splash) splash->finish(window); - delete splash; - } if (parser.isSet(presenterOption)) window->showPresenter(); diff --git a/src/kdefrontend/MainWin.h b/src/kdefrontend/MainWin.h --- a/src/kdefrontend/MainWin.h +++ b/src/kdefrontend/MainWin.h @@ -217,8 +217,8 @@ bool warnModified(); void activateSubWindowForAspect(const AbstractAspect*) const; bool save(const QString&); -// void toggleShowWidget(QWidget* widget, bool showToRight); -// void toggleHideWidget(QWidget* widget, bool hideToLeft); + void toggleShowWidget(QWidget* widget, bool showToRight); + void toggleHideWidget(QWidget* widget, bool hideToLeft); Workbook* activeWorkbook() const; Spreadsheet* activeSpreadsheet() const; @@ -273,7 +273,6 @@ //Cantor #ifdef HAVE_CANTOR_LIBS void newCantorWorksheet(QAction* action); - void cantorSettingsDialog(); #endif void newFolder(); diff --git a/src/kdefrontend/MainWin.cpp b/src/kdefrontend/MainWin.cpp --- a/src/kdefrontend/MainWin.cpp +++ b/src/kdefrontend/MainWin.cpp @@ -101,9 +101,6 @@ #ifdef HAVE_CANTOR_LIBS #include -#include -#include -#include #endif /*! @@ -569,13 +566,6 @@ } connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged); - -#ifdef HAVE_CANTOR_LIBS - QAction* action = new QAction(QIcon::fromTheme(QLatin1String("cantor")), i18n("Configure CAS"), this); - connect(action, &QAction::triggered, this, &MainWin::cantorSettingsDialog); - if (settingsMenu) - settingsMenu->addAction(action); -#endif } void MainWin::colorSchemeChanged(QAction* action) { @@ -1025,7 +1015,7 @@ m_aspectTreeModel = nullptr; delete m_project; m_project = nullptr; - m_currentFileName.clear(); + m_currentFileName = ""; //update the UI if we're just closing a project //and not closing(quitting) the application @@ -1151,7 +1141,7 @@ if (part->printView()) statusBar()->showMessage(i18n("%1 printed", part->name())); else - statusBar()->showMessage(QString()); + statusBar()->showMessage(""); } void MainWin::printPreview() { @@ -1164,7 +1154,7 @@ if (part->printPreview()) statusBar()->showMessage(i18n("%1 printed", part->name())); else - statusBar()->showMessage(QString()); + statusBar()->showMessage(""); } /**************************************************************************************/ @@ -1631,22 +1621,17 @@ void MainWin::toggleDockWidget(QAction* action) { if (action->objectName() == "toggle_project_explorer_dock") { if (m_projectExplorerDock->isVisible()) - m_projectExplorerDock->hide(); -// toggleHideWidget(m_projectExplorerDock, true); + toggleHideWidget(m_projectExplorerDock, true); else - m_projectExplorerDock->show(); -// toggleShowWidget(m_projectExplorerDock, true); + toggleShowWidget(m_projectExplorerDock, true); } else if (action->objectName() == "toggle_properties_explorer_dock") { if (m_propertiesDock->isVisible()) - m_propertiesDock->hide(); -// toggleHideWidget(m_propertiesDock, false); + toggleHideWidget(m_propertiesDock, false); else - m_propertiesDock->show(); -// toggleShowWidget(m_propertiesDock, false); + toggleShowWidget(m_propertiesDock, false); } } -/* void MainWin::toggleHideWidget(QWidget* widget, bool hideToLeft) { auto* timeline = new QTimeLine(800, this); @@ -1703,7 +1688,7 @@ connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } -*/ + void MainWin::projectExplorerDockVisibilityChanged(bool visible) { m_toggleProjectExplorerDockAction->setChecked(visible); } @@ -1935,29 +1920,29 @@ dlg->importToMQTT(mqttClient); mqttClient->setName(mqttClient->clientHostName()); - QVector existingClients = m_project->children(AbstractAspect::Recursive); //doesn't make sense to have more MQTTClients connected to the same broker bool found = false; for (const auto* client : existingClients) { - if (client->clientHostName() == mqttClient->clientHostName() && client->clientPort() == mqttClient->clientPort()) { + if (client->clientHostName() == mqttClient->clientHostName()) { found = true; break; } } if (!found) - addAspectToProject(mqttClient); + this->addAspectToProject(mqttClient); else { delete mqttClient; - QMessageBox::warning(this, "Warning", "There already is a MQTTClient with this host!"); + QMessageBox::warning(this, "Warning", "There already is a MQTTClient with this host name!"); } #endif - } else { + } + else { LiveDataSource* dataSource = new LiveDataSource(i18n("Live data source%1", 1), false); dlg->importToLiveDataSource(dataSource, statusBar()); - addAspectToProject(dataSource); + this->addAspectToProject(dataSource); } } delete dlg; @@ -1986,16 +1971,3 @@ connect (dlg, &SettingsDialog::settingsChanged, this, &MainWin::handleSettingsChanges); dlg->exec(); } - -#ifdef HAVE_CANTOR_LIBS -void MainWin::cantorSettingsDialog() -{ - static KCoreConfigSkeleton* emptyConfig = new KCoreConfigSkeleton(); - KConfigDialog *cantorDialog = new KConfigDialog(this, QLatin1String("Cantor Settings"), emptyConfig); - for (auto* backend : Cantor::Backend::availableBackends()) - if (backend->config()) //It has something to configure, so add it to the dialog - cantorDialog->addPage(backend->settingsWidget(cantorDialog), backend->config(), backend->name(), backend->icon()); - - cantorDialog->show(); -} -#endif diff --git a/src/kdefrontend/SettingsDialog.cpp b/src/kdefrontend/SettingsDialog.cpp --- a/src/kdefrontend/SettingsDialog.cpp +++ b/src/kdefrontend/SettingsDialog.cpp @@ -2,9 +2,9 @@ File : SettingsDialog.cpp Project : LabPlot -------------------------------------------------------------------- - Copyright : (C) 2008-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2008-2017 by Alexander Semke (alexander.semke@web.de) Description : application settings dialog - + ***************************************************************************/ /*************************************************************************** @@ -33,7 +33,6 @@ #include #include -#include #include #include @@ -67,14 +66,8 @@ worksheetFrame->setIcon(QIcon::fromTheme(QLatin1String("labplot-worksheet"))); connect(m_worksheetPage, &SettingsWorksheetPage::settingsChanged, this, &SettingsDialog::changed); - //restore saved settings if available - create(); // ensure there's a window created - KConfigGroup conf(KSharedConfig::openConfig(), "SettingsDialog"); - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); + const KConfigGroup dialogConfig = KSharedConfig::openConfig()->group("SettingsDialog"); + KWindowConfig::restoreWindowSize(windowHandle(), dialogConfig); } SettingsDialog::~SettingsDialog() { diff --git a/src/kdefrontend/TemplateHandler.cpp b/src/kdefrontend/TemplateHandler.cpp --- a/src/kdefrontend/TemplateHandler.cpp +++ b/src/kdefrontend/TemplateHandler.cpp @@ -168,7 +168,7 @@ QLabel* label = new QLabel(i18n("New:"), frame); layout->addWidget(label); - QLineEdit* leFilename = new QLineEdit(QString(), frame); + QLineEdit* leFilename = new QLineEdit("", frame); layout->addWidget(leFilename); connect(leFilename, &QLineEdit::returnPressed, this, [=]() {saveNewSelected(leFilename->text());} ); connect(leFilename, &QLineEdit::returnPressed, &menu, &QMenu::close); diff --git a/src/kdefrontend/ThemeHandler.cpp b/src/kdefrontend/ThemeHandler.cpp --- a/src/kdefrontend/ThemeHandler.cpp +++ b/src/kdefrontend/ThemeHandler.cpp @@ -204,7 +204,7 @@ // QLabel* label = new QLabel(i18n("Enter name:"), frame); // layout->addWidget(label); // -// QLineEdit* leFilename = new QLineEdit(QString(), frame); +// QLineEdit* leFilename = new QLineEdit("", frame); // layout->addWidget(leFilename); // connect(leFilename, SIGNAL(returnPressed(QString)), this, SLOT(saveNewSelected(QString))); // connect(leFilename, SIGNAL(returnPressed(QString)), &menu, SLOT(close())); diff --git a/src/kdefrontend/datasources/AsciiOptionsWidget.h b/src/kdefrontend/datasources/AsciiOptionsWidget.h --- a/src/kdefrontend/datasources/AsciiOptionsWidget.h +++ b/src/kdefrontend/datasources/AsciiOptionsWidget.h @@ -39,7 +39,6 @@ public: explicit AsciiOptionsWidget(QWidget*); void showAsciiHeaderOptions(bool); - void showTimestampOptions(bool); void applyFilterSettings(AsciiFilter*) const; void loadSettings() const; void saveSettings(); diff --git a/src/kdefrontend/datasources/AsciiOptionsWidget.cpp b/src/kdefrontend/datasources/AsciiOptionsWidget.cpp --- a/src/kdefrontend/datasources/AsciiOptionsWidget.cpp +++ b/src/kdefrontend/datasources/AsciiOptionsWidget.cpp @@ -4,7 +4,7 @@ Description : widget providing options for the import of ascii data -------------------------------------------------------------------- Copyright : (C) 2009-2017 Stefan Gerlach (stefan.gerlach@uni.kn) - Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -118,20 +118,11 @@ connect(ui.chbHeader, &QCheckBox::stateChanged, this, &AsciiOptionsWidget::headerChanged); } -void AsciiOptionsWidget::showAsciiHeaderOptions(bool visible) { - DEBUG("AsciiOptionsWidget::showAsciiHeaderOptions(" << visible << ")"); - ui.chbHeader->setVisible(visible); - if (visible) { - ui.lVectorNames->setVisible(!ui.chbHeader->isChecked()); - ui.kleVectorNames->setVisible(!ui.chbHeader->isChecked()); - } else { - ui.lVectorNames->setVisible(false); - ui.kleVectorNames->setVisible(false); - } -} - -void AsciiOptionsWidget::showTimestampOptions(bool visible) { - ui.chbCreateTimestamp->setVisible(visible); +void AsciiOptionsWidget::showAsciiHeaderOptions(bool b) { + DEBUG("AsciiOptionsWidget::showAsciiHeaderOptions(" << b << ")"); + ui.chbHeader->setVisible(b); + ui.lVectorNames->setVisible(b); + ui.kleVectorNames->setVisible(b); } /*! @@ -139,19 +130,24 @@ Hides it otherwise. */ void AsciiOptionsWidget::headerChanged(int state) { - bool visible = (state != Qt::Checked); - ui.kleVectorNames->setVisible(visible); - ui.lVectorNames->setVisible(visible); + DEBUG("AsciiOptionsWidget::headerChanged(" << state << ")"); + if (state == Qt::Checked) { + ui.kleVectorNames->hide(); + ui.lVectorNames->hide(); + } else { + ui.kleVectorNames->show(); + ui.lVectorNames->show(); + } } void AsciiOptionsWidget::applyFilterSettings(AsciiFilter* filter) const { Q_ASSERT(filter); + filter->setCommentCharacter( ui.cbCommentCharacter->currentText() ); filter->setSeparatingCharacter( ui.cbSeparatingCharacter->currentText() ); filter->setNumberFormat( QLocale::Language(ui.cbNumberFormat->currentIndex()) ); filter->setDateTimeFormat(ui.cbDateTimeFormat->currentText()); filter->setCreateIndexEnabled( ui.chbCreateIndex->isChecked() ); - filter->setCreateTimestampEnabled( ui.chbCreateTimestamp->isChecked() ); filter->setSimplifyWhitespacesEnabled( ui.chbSimplifyWhitespaces->isChecked() ); filter->setNaNValueToZero( ui.chbConvertNaNToZero->isChecked() ); filter->setRemoveQuotesEnabled( ui.chbRemoveQuotes->isChecked() ); @@ -160,6 +156,7 @@ filter->setHeaderEnabled( ui.chbHeader->isChecked() ); } + void AsciiOptionsWidget::loadSettings() const { KConfigGroup conf(KSharedConfig::openConfig(), "ImportAscii"); @@ -169,7 +166,6 @@ 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.chbCreateTimestamp->setChecked(conf.readEntry("CreateTimestamp", true)); ui.chbSimplifyWhitespaces->setChecked(conf.readEntry("SimplifyWhitespaces", true)); ui.chbConvertNaNToZero->setChecked(conf.readEntry("ConvertNaNToZero", false)); ui.chbRemoveQuotes->setChecked(conf.readEntry("RemoveQuotes", false)); @@ -186,7 +182,6 @@ conf.writeEntry("NumberFormat", ui.cbNumberFormat->currentIndex()); conf.writeEntry("DateTimeFormat", ui.cbDateTimeFormat->currentText()); conf.writeEntry("CreateIndex", ui.chbCreateIndex->isChecked()); - conf.writeEntry("CreateTimestamp", ui.chbCreateTimestamp->isChecked()); conf.writeEntry("SimplifyWhitespaces", ui.chbSimplifyWhitespaces->isChecked()); conf.writeEntry("ConvertNaNToZero", ui.chbConvertNaNToZero->isChecked()); conf.writeEntry("RemoveQuotes", ui.chbRemoveQuotes->isChecked()); diff --git a/src/kdefrontend/datasources/DatabaseManagerDialog.h b/src/kdefrontend/datasources/DatabaseManagerDialog.h --- a/src/kdefrontend/datasources/DatabaseManagerDialog.h +++ b/src/kdefrontend/datasources/DatabaseManagerDialog.h @@ -49,6 +49,7 @@ private slots: void changed(); void save(); + void loadSettings(); }; #endif diff --git a/src/kdefrontend/datasources/DatabaseManagerDialog.cpp b/src/kdefrontend/datasources/DatabaseManagerDialog.cpp --- a/src/kdefrontend/datasources/DatabaseManagerDialog.cpp +++ b/src/kdefrontend/datasources/DatabaseManagerDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : dialog for managing database connections -------------------------------------------------------------------- - Copyright : (C) 2016-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -34,7 +34,7 @@ #include #include -#include +#include /*! \class DatabaseManagerDialog @@ -60,14 +60,14 @@ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - //restore saved settings if available - create(); // ensure there's a window created + QTimer::singleShot(0, this, &DatabaseManagerDialog::loadSettings); +} + +void DatabaseManagerDialog::loadSettings() { + //restore saved settings + QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "DatabaseManagerDialog"); - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); + KWindowConfig::restoreWindowSize(windowHandle(), conf); } QString DatabaseManagerDialog::connection() const { diff --git a/src/kdefrontend/datasources/DatabaseManagerWidget.h b/src/kdefrontend/datasources/DatabaseManagerWidget.h --- a/src/kdefrontend/datasources/DatabaseManagerWidget.h +++ b/src/kdefrontend/datasources/DatabaseManagerWidget.h @@ -30,13 +30,6 @@ #include "ui_databasemanagerwidget.h" -#ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING -#include -namespace KSyntaxHighlighting { - class SyntaxHighlighter; -} -#endif - class DatabaseManagerWidget : public QWidget { Q_OBJECT @@ -64,14 +57,9 @@ private: Ui::DatabaseManagerWidget ui; QList m_connections; - SQLConnection* m_current_connection = nullptr; bool m_initializing{false}; QString m_configPath; QString m_initConnName; -#ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING - KSyntaxHighlighting::SyntaxHighlighter* m_highlighter{nullptr}; - KSyntaxHighlighting::Repository m_repository; -#endif QString uniqueName(); void loadConnection(); diff --git a/src/kdefrontend/datasources/DatabaseManagerWidget.cpp b/src/kdefrontend/datasources/DatabaseManagerWidget.cpp --- a/src/kdefrontend/datasources/DatabaseManagerWidget.cpp +++ b/src/kdefrontend/datasources/DatabaseManagerWidget.cpp @@ -40,12 +40,6 @@ #include #include -#ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING -#include -#include -#include -#endif - /*! \class DatabaseManagerWidget \brief widget for managing database connections, embedded in \c DatabaseManagerDialog. @@ -106,34 +100,31 @@ if (m_initializing) return; - if (index == -1) { - m_current_connection = nullptr; + if (index == -1) return; - } - m_current_connection = &m_connections[index]; //show the settings for the selected connection m_initializing = true; - const QString& driver = m_current_connection->driver; - ui.leName->setText(m_current_connection->name); + const QString& driver = m_connections[index].driver; + ui.leName->setText(m_connections[index].name); ui.cbDriver->setCurrentIndex(ui.cbDriver->findText(driver)); - ui.leDatabase->setText(m_current_connection->dbName); + ui.leDatabase->setText(m_connections[index].dbName); //no host and port number required for file DB and ODBC connections if (!isFileDB(driver) || !isODBC(driver)) { - ui.leHost->setText(m_current_connection->hostName); - ui.sbPort->setValue(m_current_connection->port); + ui.leHost->setText(m_connections[index].hostName); + ui.sbPort->setValue(m_connections[index].port); } //no credentials required for file DB if (!isFileDB(driver)) { - ui.leUserName->setText(m_current_connection->userName); - ui.lePassword->setText(m_current_connection->password); + ui.leUserName->setText(m_connections[index].userName); + ui.lePassword->setText(m_connections[index].password); } if (isODBC(driver)) { - ui.chkCustomConnection->setChecked(m_current_connection->customConnectionEnabled); - ui.teCustomConnection->setPlainText(m_current_connection->customConnectionString); + ui.chkCustomConnection->setChecked(m_connections[index].customConnectionEnabled); + ui.teCustomConnection->setPlainText(m_connections[index].customConnectionString); } m_initializing = false; } @@ -152,14 +143,12 @@ } if (unique) { - ui.leName->setStyleSheet(QString()); - if (auto item = ui.lwConnections->currentItem()) { - item->setText(name); - - if (!m_initializing) { - m_current_connection->name = name; - emit changed(); - } + ui.leName->setStyleSheet(""); + ui.lwConnections->currentItem()->setText(name); + + if (!m_initializing) { + m_connections[ui.lwConnections->currentRow()].name = name; + emit changed(); } } else ui.leName->setStyleSheet("QLineEdit{background: red;}"); @@ -168,6 +157,13 @@ void DatabaseManagerWidget::driverChanged() { //hide non-relevant fields (like host name, etc.) for file DBs and ODBC const QString& driver = ui.cbDriver->currentText(); + const bool fileDB = isFileDB(driver); + ui.lHost->setVisible(!fileDB); + ui.leHost->setVisible(!fileDB); + ui.lPort->setVisible(!fileDB); + ui.sbPort->setVisible(!fileDB); + ui.bOpen->setVisible(fileDB); + ui.gbAuthentication->setVisible(!fileDB); if (isFileDB(driver)) { ui.lHost->hide(); @@ -194,23 +190,11 @@ const bool customConnection = ui.chkCustomConnection->isChecked(); ui.leDatabase->setEnabled(!customConnection); ui.teCustomConnection->setVisible(customConnection); - -#ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING - //syntax highlighting for custom ODBC string - if (!m_highlighter) { - m_highlighter = new KSyntaxHighlighting::SyntaxHighlighter(ui.teCustomConnection->document()); - m_highlighter->setDefinition(m_repository.definitionForName("INI Files")); - m_highlighter->setTheme( (palette().color(QPalette::Base).lightness() < 128) - ? m_repository.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme) - : m_repository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme) ); - } -#endif } else { ui.lHost->show(); ui.leHost->show(); ui.lPort->show(); ui.sbPort->show(); - ui.sbPort->setValue(defaultPort(driver)); ui.bOpen->hide(); ui.gbAuthentication->show(); ui.lDatabase->setText(i18n("Database:")); @@ -223,8 +207,7 @@ if (m_initializing) return; - if (m_current_connection) - m_current_connection->driver = driver; + m_connections[ui.lwConnections->currentRow()].driver = driver; emit changed(); } @@ -249,8 +232,7 @@ if (m_initializing) return; - if (m_current_connection) - m_current_connection->hostName = ui.leHost->text(); + m_connections[ui.lwConnections->currentRow()].hostName = ui.leHost->text(); //don't allow to try to connect if no hostname provided ui.bTestConnection->setEnabled( !ui.leHost->text().simplified().isEmpty() ); @@ -262,8 +244,7 @@ if (m_initializing) return; - if (m_current_connection) - m_current_connection->port = ui.sbPort->value(); + m_connections[ui.lwConnections->currentRow()].port = ui.sbPort->value(); emit changed(); } @@ -279,14 +260,14 @@ if (!dbName.isEmpty()) { bool fileExists = QFile::exists(dbName); if (fileExists) - ui.leDatabase->setStyleSheet(QString()); + ui.leDatabase->setStyleSheet(""); else - ui.leDatabase->setStyleSheet(QStringLiteral("QLineEdit{background:red;}")); + ui.leDatabase->setStyleSheet("QLineEdit{background:red;}"); } else { - ui.leDatabase->setStyleSheet(QString()); + ui.leDatabase->setStyleSheet(""); } } else { - ui.leDatabase->setStyleSheet(QString()); + ui.leDatabase->setStyleSheet(""); } //don't allow to try to connect if no database name was provided @@ -295,8 +276,7 @@ if (m_initializing) return; - if (m_current_connection) - m_current_connection->dbName = dbName; + m_connections[ui.lwConnections->currentRow()].dbName = dbName; emit changed(); } @@ -312,14 +292,12 @@ else ui.leDatabase->setFocus(); - if (m_current_connection) - m_current_connection->customConnectionEnabled = (state == Qt::Checked); + m_connections[ui.lwConnections->currentRow()].customConnectionEnabled = (state == Qt::Checked); emit changed(); } void DatabaseManagerWidget::customConnectionChanged() { - if (m_current_connection) - m_current_connection->customConnectionString = ui.teCustomConnection->toPlainText(); + m_connections[ui.lwConnections->currentRow()].customConnectionString = ui.teCustomConnection->toPlainText(); emit changed(); } @@ -327,8 +305,7 @@ if (m_initializing) return; - if (m_current_connection) - m_current_connection->userName = ui.leUserName->text(); + m_connections[ui.lwConnections->currentRow()].userName = ui.leUserName->text(); emit changed(); } @@ -336,8 +313,7 @@ if (m_initializing) return; - if (m_current_connection) - m_current_connection->password = ui.lePassword->text(); + m_connections[ui.lwConnections->currentRow()].password = ui.lePassword->text(); emit changed(); } @@ -382,13 +358,11 @@ return; //remove the current selected connection - int row = ui.lwConnections->currentRow(); - if (row != -1) { - m_connections.removeAt(row); - m_initializing = true; - delete ui.lwConnections->takeItem(row); - m_initializing = false; - } + m_connections.removeAt(ui.lwConnections->currentRow()); + m_initializing = true; + QListWidgetItem* item = ui.lwConnections->takeItem(ui.lwConnections->currentRow()); + if (item) delete item; + m_initializing = false; //show the connection for the item that was automatically selected afte the deletion connectionChanged(ui.lwConnections->currentRow()); @@ -502,8 +476,7 @@ } void DatabaseManagerWidget::testConnection() { - if (!m_current_connection) - return; + int row = ui.lwConnections->currentRow(); //don't allow to test the connection for file DBs if the file doesn't exist if (isFileDB(ui.cbDriver->currentText())) { @@ -515,43 +488,33 @@ #endif if (!QFile::exists(fileName)) { - KMessageBox::error(this, i18n("Failed to connect to the database '%1'.", m_current_connection->dbName), + KMessageBox::error(this, i18n("Failed to connect to the database '%1'.", m_connections[row].dbName), i18n("Connection Failed")); return; } } WAIT_CURSOR; - const QString& driver = m_current_connection->driver; + const QString& driver = m_connections[row].driver; QSqlDatabase db = QSqlDatabase::addDatabase(driver); - db.close(); - - //db name or custom connection string for ODBC, if available - if (isODBC(driver) && m_current_connection->customConnectionEnabled) - db.setDatabaseName(m_current_connection->customConnectionString); - else - db.setDatabaseName(m_current_connection->dbName); - - //host and port number, if required + db.setDatabaseName(m_connections[row].dbName); if (!isFileDB(driver) && !isODBC(driver)) { - db.setHostName(m_current_connection->hostName); - db.setPort(m_current_connection->port); + db.setHostName(m_connections[row].hostName); + db.setPort(m_connections[row].port); } - - //authentication, if required if (!isFileDB(driver)) { - db.setUserName(m_current_connection->userName); - db.setPassword(m_current_connection->password); + db.setUserName(m_connections[row].userName); + db.setPassword(m_connections[row].password); } if (db.isValid() && db.open() && db.isOpen()) { db.close(); RESET_CURSOR; - KMessageBox::information(this, i18n("Connection to the database '%1' was successful.", m_current_connection->dbName), + KMessageBox::information(this, i18n("Connection to the database '%1' was successful.", m_connections[row].dbName), i18n("Connection Successful")); } else { RESET_CURSOR; - KMessageBox::error(this, i18n("Failed to connect to the database '%1'.", m_current_connection->dbName) + + KMessageBox::error(this, i18n("Failed to connect to the database '%1'.", m_connections[row].dbName) + QLatin1String("\n\n") + db.lastError().databaseText(), i18n("Connection Failed")); } diff --git a/src/kdefrontend/datasources/FileInfoDialog.h b/src/kdefrontend/datasources/FileInfoDialog.h --- a/src/kdefrontend/datasources/FileInfoDialog.h +++ b/src/kdefrontend/datasources/FileInfoDialog.h @@ -3,7 +3,7 @@ Project : LabPlot Description : file info dialog -------------------------------------------------------------------- - Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2009-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2015-2016 Stefan-Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -44,6 +44,9 @@ private: QTextEdit m_textEditWidget; QString fileInfoString(const QString&) const; + +private slots: + void loadSettings(); }; #endif //IMPORTFILEDIALOG_H diff --git a/src/kdefrontend/datasources/FileInfoDialog.cpp b/src/kdefrontend/datasources/FileInfoDialog.cpp --- a/src/kdefrontend/datasources/FileInfoDialog.cpp +++ b/src/kdefrontend/datasources/FileInfoDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : import file data dialog -------------------------------------------------------------------- - Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2009-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2015-2018 Stefan-Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -37,7 +37,6 @@ #include #include #include -#include #include #include @@ -69,14 +68,13 @@ setLayout(layout); - //restore saved settings if available - create(); // ensure there's a window created + QTimer::singleShot(0, this, &FileInfoDialog::loadSettings); +} + +void FileInfoDialog::loadSettings() { + //restore saved settings KConfigGroup conf(KSharedConfig::openConfig(), "FileInfoDialog"); - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(300, 200).expandedTo(minimumSize())); + KWindowConfig::restoreWindowSize(windowHandle(), conf); } FileInfoDialog::~FileInfoDialog() { @@ -144,7 +142,7 @@ else { fileTypeString = proc->readLine(); if (fileTypeString.contains(i18n("cannot open"))) - fileTypeString.clear(); + fileTypeString = ""; else { fileTypeString.remove(fileTypeString.length() - 1, 1); // remove '\n' } diff --git a/src/kdefrontend/datasources/ImportFileDialog.h b/src/kdefrontend/datasources/ImportFileDialog.h --- a/src/kdefrontend/datasources/ImportFileDialog.h +++ b/src/kdefrontend/datasources/ImportFileDialog.h @@ -3,7 +3,7 @@ Project : LabPlot Description : import data dialog -------------------------------------------------------------------- - Copyright : (C) 2008-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2008-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2015 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -73,6 +73,7 @@ private slots: void toggleOptions(); void checkOnFitsTableToMatrix(const bool enable); + void loadSettings(); }; #endif //IMPORTFILEDIALOG_H diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : import file data dialog -------------------------------------------------------------------- - Copyright : (C) 2008-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2008-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2015 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -57,7 +57,6 @@ #include #include #include -#include /*! \class ImportFileDialog @@ -93,39 +92,37 @@ setWindowIcon(QIcon::fromTheme("document-import-database")); - //restore saved settings if available - create(); // ensure there's a window created + QTimer::singleShot(0, this, &ImportFileDialog::loadSettings); +} +void ImportFileDialog::loadSettings() { + //restore saved settings QApplication::processEvents(QEventLoop::AllEvents, 0); m_importFileWidget->loadSettings(); KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); - if (conf.exists()) { - m_showOptions = conf.readEntry("ShowOptions", false); - m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); - m_importFileWidget->showOptions(m_showOptions); - - //do the signal-slot connections after all settings were loaded in import file widget and check the OK button after this - connect(m_importFileWidget, &ImportFileWidget::checkedFitsTableToMatrix, this, &ImportFileDialog::checkOnFitsTableToMatrix); - connect(m_importFileWidget, static_cast(&ImportFileWidget::fileNameChanged), this, &ImportFileDialog::checkOkButton); - connect(m_importFileWidget, static_cast(&ImportFileWidget::sourceTypeChanged), this, &ImportFileDialog::checkOkButton); - connect(m_importFileWidget, &ImportFileWidget::hostChanged, this, &ImportFileDialog::checkOkButton); - connect(m_importFileWidget, &ImportFileWidget::portChanged, this, &ImportFileDialog::checkOkButton); - //TODO: do we really need to check the ok button when the preview was refreshed? - //If not, remove this together with the previewRefreshed signal in ImportFileWidget - //connect(m_importFileWidget, &ImportFileWidget::previewRefreshed, this, &ImportFileDialog::checkOkButton); + m_showOptions = conf.readEntry("ShowOptions", false); + m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); + m_importFileWidget->showOptions(m_showOptions); + + //do the signal-slot connections after all settings where loaded in import file widget and check the OK button after this + connect(m_importFileWidget, &ImportFileWidget::checkedFitsTableToMatrix, this, &ImportFileDialog::checkOnFitsTableToMatrix); + connect(m_importFileWidget, static_cast(&ImportFileWidget::fileNameChanged), this, &ImportFileDialog::checkOkButton); + connect(m_importFileWidget, static_cast(&ImportFileWidget::sourceTypeChanged), this, &ImportFileDialog::checkOkButton); + connect(m_importFileWidget, &ImportFileWidget::hostChanged, this, &ImportFileDialog::checkOkButton); + connect(m_importFileWidget, &ImportFileWidget::portChanged, this, &ImportFileDialog::checkOkButton); + //TODO: do we really need to check the ok button when the preview was refreshed? + //If not, remove this together with the previewRefreshed signal in ImportFileWidget + //connect(m_importFileWidget, &ImportFileWidget::previewRefreshed, this, &ImportFileDialog::checkOkButton); #ifdef HAVE_MQTT - connect(m_importFileWidget, &ImportFileWidget::subscriptionsChanged, this, &ImportFileDialog::checkOkButton); - connect(m_importFileWidget, &ImportFileWidget::checkFileType, this, &ImportFileDialog::checkOkButton); + connect(m_importFileWidget, &ImportFileWidget::subscriptionsChanged, this, &ImportFileDialog::checkOkButton); + connect(m_importFileWidget, &ImportFileWidget::checkFileType, this, &ImportFileDialog::checkOkButton); #endif - connect(m_optionsButton, &QPushButton::clicked, this, &ImportFileDialog::toggleOptions); - - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); + connect(m_optionsButton, &QPushButton::clicked, this, &ImportFileDialog::toggleOptions); checkOkButton(); + + KWindowConfig::restoreWindowSize(windowHandle(), conf); } ImportFileDialog::~ImportFileDialog() { @@ -218,16 +215,16 @@ timer.start(); if (aspect->inherits("Matrix")) { - DEBUG("ImportFileDialog::importTo(): to Matrix"); + DEBUG(" to Matrix"); auto* matrix = qobject_cast(aspect); filter->readDataFromFile(fileName, matrix, mode); } else if (aspect->inherits("Spreadsheet")) { - DEBUG("ImportFileDialog::importTo(): to Spreadsheet"); + DEBUG(" to Spreadsheet"); auto* spreadsheet = qobject_cast(aspect); - DEBUG(" Calling filter->readDataFromFile() with spreadsheet " << spreadsheet); + DEBUG(" Calling readDataFromFile() with spreadsheet " << spreadsheet); filter->readDataFromFile(fileName, spreadsheet, mode); } else if (aspect->inherits("Workbook")) { - DEBUG("ImportFileDialog::importTo(): to Workbook"); + DEBUG(" to Workbook"); auto* workbook = qobject_cast(aspect); QVector sheets = workbook->children(); diff --git a/src/kdefrontend/datasources/ImportFileWidget.h b/src/kdefrontend/datasources/ImportFileWidget.h --- a/src/kdefrontend/datasources/ImportFileWidget.h +++ b/src/kdefrontend/datasources/ImportFileWidget.h @@ -4,9 +4,8 @@ Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2017 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) - Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2009-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) - Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ @@ -37,7 +36,6 @@ #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" -class MQTTSubscriptionWidget; #endif #include @@ -140,44 +138,63 @@ #ifdef HAVE_MQTT private: - void unsubscribeFromTopic(const QString&, QVector children); + void updateSubscriptionCompleter(); + bool checkTopicContains(const QString&, const QString&) ; + QString checkCommonLevel(const QString&, const QString&); + int commonLevelIndex(const QString& first, const QString& second); + void unsubscribeFromTopic(const QString&); + void addSubscriptionChildren(QTreeWidgetItem*, QTreeWidgetItem*); + void findSubscriptionLeafChildren(QVector&, QTreeWidgetItem*); + int checkCommonChildCount(int levelIdx, int level, QStringList& namelist, QTreeWidgetItem* currentItem); + void manageCommonLevelSubscriptions(); + void updateSubscriptionTree(); + void restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level); QMqttClient* m_client{nullptr}; - QVector m_mqttSubscriptions; + QVector m_mqttSubscriptions; + QCompleter* m_topicCompleter{nullptr}; + QCompleter* m_subscriptionCompleter{nullptr}; + QStringList m_topicList; + bool m_searching{false}; + QTimer* m_searchTimer; QTimer* m_connectTimeoutTimer; + QMap m_messageArrived; QMap m_lastMessage; + bool m_mqttReadyForPreview{false}; QVector m_subscribedTopicNames; QVector m_addedTopics; QString m_configPath; bool m_initialisingMQTT{false}; bool m_connectionTimedOut{false}; MQTTClient::MQTTWill m_willSettings; - MQTTSubscriptionWidget* m_subscriptionWidget; - public: void saveMQTTSettings(MQTTClient*) const; bool isMqttValid(); signals: - void newTopic(const QString&); + void newTopic(QString); void subscriptionsChanged(); void checkFileType(); - void updateSubscriptionTree(const QVector&); - void MQTTClearTopics(); private slots: void mqttConnectionChanged(); - void onMqttConnect(); - void mqttSubscribe(const QString&, uint); - void mqttMessageReceived(const QByteArray&, const QMqttTopicName&); + void onMqttConnect(); + void mqttAvailableTopicDoubleClicked(QTreeWidgetItem*, int); + void mqttSubscribedTopicDoubleClicked(QTreeWidgetItem*, int); + void mqttSubscribe(); + void mqttUnsubscribe(); + void mqttMessageReceived(const QByteArray&, const QMqttTopicName&); + void setTopicCompleter(const QString&); + void topicTimeout(); void mqttSubscriptionMessageReceived(const QMqttMessage& ); void onMqttDisconnect(); - void mqttErrorChanged(QMqttClient::ClientError); + void mqttErrorChanged(QMqttClient::ClientError); + void scrollToTopicTreeItem(const QString&); + void scrollToSubsriptionTreeItem(const QString&); void mqttConnectTimeout(); void showMQTTConnectionManager(); void readMQTTConnections(); void showWillSettings(); - void enableWill(bool); #endif }; diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -6,7 +6,7 @@ Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) -Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) +Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ @@ -66,7 +66,7 @@ #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include "MQTTConnectionManagerDialog.h" -#include "MQTTSubscriptionWidget.h" +#include "MQTTConnectionManagerWidget.h" #include #include #include @@ -87,8 +87,8 @@ m_liveDataSource(liveDataSource) #ifdef HAVE_MQTT , - m_connectTimeoutTimer(new QTimer(this)), - m_subscriptionWidget(new MQTTSubscriptionWidget(this)) + m_searchTimer(new QTimer(this)), + m_connectTimeoutTimer(new QTimer(this)) #endif { ui.setupUi(this); @@ -135,6 +135,7 @@ ui.tabWidget->removeTab(2); #ifdef HAVE_MQTT + m_searchTimer->setInterval(10000); m_connectTimeoutTimer->setInterval(6000); #endif } @@ -172,21 +173,36 @@ ui.cbSourceType->addItem(QLatin1String("MQTT")); m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QLatin1String("MQTT_connections"); - //add subscriptions widget - layout = new QHBoxLayout; - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - layout->addWidget(m_subscriptionWidget); - ui.frameSubscriptions->setLayout(layout); - + const int size = ui.leTopics->height(); + ui.lTopicSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) ); + ui.lSubscriptionSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) ); + ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); + ui.bSubscribe->setToolTip(i18n("Subscribe selected topics")); + ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_ArrowLeft)); + ui.bUnsubscribe->setToolTip(i18n("Unsubscribe selected topics")); ui.bManageConnections->setIcon(QIcon::fromTheme(QLatin1String("network-server"))); ui.bManageConnections->setToolTip(i18n("Manage MQTT connections")); - QString info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); + QString info = i18n("Enter the name of the topic to navigate to it."); + ui.lTopicSearch->setToolTip(info); + ui.leTopics->setToolTip(info); + ui.lSubscriptionSearch->setToolTip(info); + ui.leSubscriptions->setToolTip(info); + + info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); + + info = i18n("Set the Quality of Service (QoS) for the subscription to define the guarantee of the message delivery:" + "
    " + "
  • 0 - deliver at most once
  • " + "
  • 1 - deliver at least once
  • " + "
  • 2 - deliver exactly once
  • " + "
"); + ui.cbQos->setToolTip(info); + #endif //TODO: implement save/load of user-defined settings later and activate these buttons again @@ -233,7 +249,6 @@ ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType").toInt()); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType").toInt()); - updateTypeChanged(ui.cbUpdateType->currentIndex()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues").toInt()); ui.lePort->setText(conf.readEntry("Port","")); @@ -265,8 +280,10 @@ initSlots(); //update the status of the widgets - fileTypeChanged(fileType); - sourceTypeChanged(currentSourceType()); + LiveDataSource::SourceType sourceType = currentSourceType(); + sourceTypeChanged(sourceType); + if (sourceType != LiveDataSource::FileOrPipe) + fileTypeChanged(fileType); // only for FileOrPipe this was indirectly called by sourceTypeChanged readingTypeChanged(ui.cbReadingType->currentIndex()); //all set now, refresh the preview @@ -301,10 +318,6 @@ conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); #ifdef HAVE_MQTT - - delete m_connectTimeoutTimer; - delete m_subscriptionWidget; - //MQTT related settings conf.writeEntry("Connection", ui.cbConnection->currentText()); conf.writeEntry("mqttWillMessageType", static_cast(m_willSettings.willMessageType)); @@ -357,16 +370,18 @@ #ifdef HAVE_MQTT connect(ui.cbConnection, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::mqttConnectionChanged); + connect(ui.bSubscribe, &QPushButton::clicked, this, &ImportFileWidget::mqttSubscribe); + connect(ui.bUnsubscribe, &QPushButton::clicked, this,&ImportFileWidget::mqttUnsubscribe); + connect(this, &ImportFileWidget::newTopic, this, &ImportFileWidget::setTopicCompleter); + connect(m_searchTimer, &QTimer::timeout, this, &ImportFileWidget::topicTimeout); connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); - connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() { - emit checkFileType(); - }); + connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() {emit checkFileType();}); + connect(ui.leTopics, &QLineEdit::textChanged, this, &ImportFileWidget::scrollToTopicTreeItem); + connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &ImportFileWidget::scrollToSubsriptionTreeItem); connect(ui.bManageConnections, &QPushButton::clicked, this, &ImportFileWidget::showMQTTConnectionManager); connect(ui.bLWT, &QPushButton::clicked, this, &ImportFileWidget::showWillSettings); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, this, &ImportFileWidget::mqttSubscribe); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::MQTTUnsubscribeFromTopic, this, &ImportFileWidget::unsubscribeFromTopic); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &ImportFileWidget::enableWill); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::subscriptionChanged, this, &ImportFileWidget::refreshPreview); + connect(ui.twTopics, &QTreeWidget::itemDoubleClicked, this, &ImportFileWidget::mqttAvailableTopicDoubleClicked); + connect(ui.twSubscriptions, &QTreeWidget::itemDoubleClicked, this, &ImportFileWidget::mqttSubscribedTopicDoubleClicked); #endif } @@ -762,22 +777,15 @@ ui.lConnections->setVisible(visible); ui.cbConnection->setVisible(visible); ui.bManageConnections->setVisible(visible); + ui.cbQos->setVisible(visible); //topics if (ui.cbConnection->currentIndex() != -1 && visible) { ui.lTopics->setVisible(true); - ui.frameSubscriptions->setVisible(true); -#ifdef HAVE_MQTT - m_subscriptionWidget->setVisible(true); - m_subscriptionWidget->makeVisible(true); -#endif + ui.gbManageSubscriptions->setVisible(true); } else { ui.lTopics->setVisible(false); - ui.frameSubscriptions->setVisible(false); -#ifdef HAVE_MQTT - m_subscriptionWidget->setVisible(false); - m_subscriptionWidget->makeVisible(false); -#endif + ui.gbManageSubscriptions->setVisible(false); } //will message @@ -795,7 +803,7 @@ return false; bool connected = (m_client->state() == QMqttClient::ClientState::Connected); - bool subscribed = (m_subscriptionWidget->subscriptionCount() > 0); + bool subscribed = (ui.twSubscriptions->topLevelItemCount() > 0); bool fileTypeOk = false; if (this->currentFileType() == AbstractFileFilter::FileType::Ascii) fileTypeOk = true; @@ -803,12 +811,172 @@ return connected && subscribed && fileTypeOk; } +/*! + *\brief Checks if a topic contains another one + * + * \param superior the name of a topic + * \param inferior the name of a topic + * \return true if superior is equal to or contains(if superior contains wildcards) inferior, + * false otherwise + */ +bool ImportFileWidget::checkTopicContains(const QString& superior, const QString& inferior) { + if (superior == inferior) + return true; + + if (!superior.contains('/')) + return false; + + const QStringList& superiorList = superior.split('/', QString::SkipEmptyParts); + const QStringList& inferiorList = inferior.split('/', QString::SkipEmptyParts); + + //a longer topic can't contain a shorter one + if (superiorList.size() > inferiorList.size()) + return false; + + bool ok = true; + for (int i = 0; i < superiorList.size(); ++i) { + if (superiorList.at(i) != inferiorList.at(i)) { + if ((superiorList.at(i) != '+') && + !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { + //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) + //then superior can't contain inferior + ok = false; + break; + } else if (i == superiorList.size() - 1 && (superiorList.at(i) == '+' && inferiorList.at(i) == "#") ) { + //if the two topics differ at the last level + //and the superior's current level is + while the inferior's is #(which can be only in the last position) + //then superior can't contain inferior + ok = false; + break; + } + } + } + return ok; +} + +/*! + *\brief Returns the '+' wildcard containing topic name, which includes the given topic names + * + * \param first the name of a topic + * \param second the name of a topic + * \return The name of the common topic, if it exists, otherwise "" + */ +QString ImportFileWidget::checkCommonLevel(const QString& first, const QString& second) { + const QStringList& firstList = first.split('/', QString::SkipEmptyParts); + if (firstList.isEmpty()) + return QString(); + + const QStringList& secondtList = second.split('/', QString::SkipEmptyParts); + QString commonTopic = ""; + + //the two topics have to be the same size and can't be identic + if (firstList.size() == secondtList.size() && (first != second)) { + + //the index where they differ + int differIndex = -1; + for (int i = 0; i < firstList.size(); ++i) { + if (firstList.at(i) != secondtList.at(i)) { + differIndex = i; + break; + } + } + + //they can differ at only one level + bool differ = false; + if (differIndex > 0) { + for (int j = differIndex + 1; j < firstList.size(); ++j) { + if (firstList.at(j) != secondtList.at(j)) { + differ = true; + break; + } + } + } else + differ = true; + + if (!differ) { + for (int i = 0; i < firstList.size(); ++i) { + if (i != differIndex) { + commonTopic.append(firstList.at(i)); + } else { + //we put '+' wildcard at the level where they differ + commonTopic.append('+'); + } + + if (i != firstList.size() - 1) + commonTopic.append('/'); + } + } + } + + qDebug() << "Common topic for " << first << " and " << second << " is: " << commonTopic; + return commonTopic; +} + +/*! + *\brief Returns the index of level where the two topic names differ, if there is a common topic for them + * + * \param first the name of a topic + * \param second the name of a topic + * \return The index of the unequal level, if there is a common topic, otherwise -1 + */ +int ImportFileWidget::commonLevelIndex(const QString& first, const QString& second) { + QStringList firstList = first.split('/', QString::SkipEmptyParts); + QStringList secondtList = second.split('/', QString::SkipEmptyParts); + QString commonTopic = ""; + int differIndex = -1; + + if (!firstList.isEmpty()) { + //the two topics have to be the same size and can't be identic + if (firstList.size() == secondtList.size() && (first != second)) { + + //the index where they differ + for (int i = 0; i < firstList.size(); ++i) { + if (firstList.at(i) != secondtList.at(i)) { + differIndex = i; + break; + } + } + + //they can differ at only one level + bool differ = false; + if (differIndex > 0) { + for (int j = differIndex + 1; j < firstList.size(); ++j) { + if (firstList.at(j) != secondtList.at(j)) { + differ = true; + break; + } + } + } + else + differ = true; + + if (!differ) { + for (int i = 0; i < firstList.size(); ++i) { + if (i != differIndex) + commonTopic.append(firstList.at(i)); + else + commonTopic.append('+'); + + if (i != firstList.size() - 1) + commonTopic.append('/'); + } + } + } + } + + //if there is a common topic we return the differIndex + if (!commonTopic.isEmpty()) + return differIndex; + else + return -1; +} + /*! *\brief Unsubscribes from the given topic, and removes any data connected to it * * \param topicName the name of a topic we want to unsubscribe from */ -void ImportFileWidget::unsubscribeFromTopic(const QString& topicName, QVector children) { +void ImportFileWidget::unsubscribeFromTopic(const QString& topicName) { if (topicName.isEmpty()) return; @@ -821,36 +989,411 @@ break; } + m_mqttReadyForPreview = false; + + QMapIterator i(m_messageArrived); + while (i.hasNext()) { + i.next(); + if (checkTopicContains(topicName, i.key().name())) + m_messageArrived.remove(i.key()); + } + QMapIterator j(m_lastMessage); while (j.hasNext()) { j.next(); - if (MQTTSubscriptionWidget::checkTopicContains(topicName, j.key().name())) + if (checkTopicContains(topicName, j.key().name())) m_lastMessage.remove(j.key()); } + for (int row = 0; row < ui.twSubscriptions->topLevelItemCount(); row++) { + if (ui.twSubscriptions->topLevelItem(row)->text(0) == topicName) { + ui.twSubscriptions->topLevelItem(row)->takeChildren(); + ui.twSubscriptions->takeTopLevelItem(row); + } + } + for (int i = 0; i < m_subscribedTopicNames.size(); ++i) { - if (MQTTSubscriptionWidget::checkTopicContains(topicName, m_subscribedTopicNames[i])) { + if (checkTopicContains(topicName, m_subscribedTopicNames[i])) { m_subscribedTopicNames.remove(i); i--; } } if (m_willSettings.willTopic == topicName) { - if (m_subscriptionWidget->subscriptionCount() > 0) + QVector children; + if (ui.twSubscriptions->topLevelItemCount() > 0) { + findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(0)); m_willSettings.willTopic = children[0]->text(0); - else - m_willSettings.willTopic.clear(); + } else + m_willSettings.willTopic = ""; } //signals that there was a change among the subscribed topics emit subscriptionsChanged(); refreshPreview(); } + +/*! + *\brief Adds to a # wildcard containing topic, every topic present in twTopics that the former topic contains + * + * \param topic pointer to the TreeWidgetItem which was selected before subscribing + * \param subscription pointer to the TreeWidgetItem which represents the new subscirption, + * we add all of the children to this item + */ +void ImportFileWidget::addSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription) { + //if the topic doesn't have any children we don't do anything + if (topic->childCount() <= 0) + return; + + for (int i = 0; i < topic->childCount(); ++i) { + QTreeWidgetItem* temp = topic->child(i); + QString name; + //if it has children, then we add it as a # wildcrad containing topic + if (topic->child(i)->childCount() > 0) { + name.append(temp->text(0) + "/#"); + while (temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + '/'); + } + } + + //if not then we simply add the topic itself + else { + name.append(temp->text(0)); + while (temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + '/'); + } + } + + QStringList nameList; + nameList.append(name); + QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); + subscription->addChild(childItem); + //we use the function recursively on the given item + addSubscriptionChildren(topic->child(i), childItem); + } +} + +/*! + *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) + * + * \param children vector of TreeWidgetItem pointers + * \param root pointer to a TreeWidgetItem of twSubscriptions + */ +void ImportFileWidget::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { + if (root->childCount() == 0) + children.push_back(root); + else + for (int i = 0; i < root->childCount(); ++i) + findSubscriptionLeafChildren(children, root->child(i)); +} + +/*! + *\brief Returns the amount of topics that the '+' wildcard will replace in the level position + * + * \param levelIdx the level currently being investigated + * \param level the level where the new + wildcard will be placed + * \param commonList the topic name split into levels + * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level + * represented by levelIdx + * \return returns the childCount, or -1 if some topics already represented by + wildcard have different + * amount of children + */ +int ImportFileWidget::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { + //we recursively check the number of children, until we get to level-1 + if (levelIdx < level - 1) { + if (commonList[levelIdx] != '+') { + for (int j = 0; j < currentItem->childCount(); ++j) { + if (currentItem->child(j)->text(0) == commonList[levelIdx]) { + //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively + return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); + } + } + } else { + int childCount = -1; + bool ok = true; + + //otherwise we check if every + wildcard represented topic has the same number of children, recursively + for (int j = 0; j < currentItem->childCount(); ++j) { + int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); + if ((j > 0) && (temp != childCount)) { + ok = false; + break; + } + childCount = temp; + } + + //if yes we return this number, otherwise -1 + if (ok) + return childCount; + else + return -1; + } + } else if (levelIdx == level - 1) { + if (commonList[levelIdx] != '+') { + for (int j = 0; j < currentItem->childCount(); ++j) { + if (currentItem->child(j)->text(0) == commonList[levelIdx]) { + //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item + return currentItem->child(j)->childCount(); + } + } + } else { + int childCount = -1; + bool ok = true; + + //otherwise we check if every + wildcard represented topic has the same number of children + for (int j = 0; j < currentItem->childCount(); ++j) { + if ((j > 0) && (currentItem->child(j)->childCount() != childCount)) { + ok = false; + break; + } + childCount = currentItem->child(j)->childCount(); + } + + //if yes we return this number, otherwise -1 + if (ok) + return childCount; + else + return -1; + } + + } else if (level == 1 && levelIdx == 1) + return currentItem->childCount(); + + return -1; +} + +/*! + *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. + * We do this until there are no topics to merge + */ +void ImportFileWidget::manageCommonLevelSubscriptions() { + bool foundEqual = false; + + do { + foundEqual = false; + QMap> equalTopicsMap; + QVector equalTopics; + + //compare the subscriptions present in the TreeWidget + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { + for (int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); + + //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) + if (!commonTopic.isEmpty()) { + if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) + equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); + + if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) + equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); + } + } + } + + if (!equalTopicsMap.isEmpty()) { + qDebug()<<"Manage common topics"; + + QVector commonTopics; + QMapIterator> topics(equalTopicsMap); + + //check for every map entry, if the found topics can be merged or not + while (topics.hasNext()) { + topics.next(); + + int level = commonLevelIndex(topics.value().last(), topics.value().first()); + QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); + QTreeWidgetItem* currentItem = nullptr; + //search the corresponding item to the common topics first level(root) + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { + currentItem = ui.twTopics->topLevelItem(i); + break; + } + } + + if (!currentItem) + break; + + //calculate the number of topics the new + wildcard could replace + int childCount = checkCommonChildCount(1, level, commonList, currentItem); + if (childCount > 0) { + //if the number of topics found and the calculated number of topics is equal, the topics can be merged + if (topics.value().size() == childCount) { + qDebug() << "Found common topic to manage: " << topics.key(); + foundEqual = true; + commonTopics.push_back(topics.key()); + } + } + } + + if (foundEqual) { + //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new '+' wildcard + int lowestLevel = INT_MAX; + int topicIdx = -1; + for (int i = 0; i < commonTopics.size(); ++i) { + int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); + if (level < lowestLevel) { + topicIdx = i; + lowestLevel = level; + } + } + qDebug() << "Manage: " << commonTopics[topicIdx]; + equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); + + //Add the common topic ("merging") + QString commonTopic; + commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); + QStringList nameList; + nameList.append(commonTopic); + QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); + ui.twSubscriptions->addTopLevelItem(newTopic); + QMqttTopicFilter filter {commonTopic}; + QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); + + if (temp_subscription) { + m_mqttSubscriptions.push_back(temp_subscription); + connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); + emit subscriptionsChanged(); + } + + //remove the "merged" topics + for (int i = 0; i < equalTopics.size(); ++i) { + for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + if (ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { + newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); + unsubscribeFromTopic(equalTopics[i]); + break; + } + } + } + + //remove any subscription that the new subscription contains + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && + commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { + unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); + i--; + } + } + } + } + } while (foundEqual); +} + +/*! + *\brief Fills twSubscriptions with the subscriptions made by the client + */ +void ImportFileWidget::updateSubscriptionTree() { + DEBUG("ImportFileWidget::updateSubscriptionTree()"); + ui.twSubscriptions->clear(); + + for (int i = 0; i < m_mqttSubscriptions.size(); ++i) { + QStringList name; + name.append(m_mqttSubscriptions[i]->topic().filter()); + + bool found = false; + for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + if (ui.twSubscriptions->topLevelItem(j)->text(0) == m_mqttSubscriptions[i]->topic().filter()) { + found = true; + break; + } + } + + if (!found) { + //Add the subscription to the tree widget + QTreeWidgetItem* newItem = new QTreeWidgetItem(name); + ui.twSubscriptions->addTopLevelItem(newItem); + name.clear(); + name = m_mqttSubscriptions[i]->topic().filter().split('/', QString::SkipEmptyParts); + + //find the corresponding "root" item in twTopics + QTreeWidgetItem* topic = nullptr; + for (int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { + if (ui.twTopics->topLevelItem(j)->text(0) == name[0]) { + topic = ui.twTopics->topLevelItem(j); + break; + } + } + + //restore the children of the subscription + if (topic != nullptr && topic->childCount() > 0) { + restoreSubscriptionChildren(topic, newItem, name, 1); + } + } + } + m_searching = false; +} + +/*! + *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards + * + * \param topic pointer to a top level item in twTopics which represents the root of the subscription topic + * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored + * \param list QStringList containing the levels of the subscription topic + * \param level the level's number which is being investigated + */ +void ImportFileWidget::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { + DEBUG("ImportFileWidget::restoreSubscriptionChildren"); + if (list[level] != '+' && list[level] != "#" && level < list.size() - 1) { + for (int i = 0; i < topic->childCount(); ++i) { + //if the current level isn't + or # wildcard we recursively continue with the next level + if (topic->child(i)->text(0) == list[level]) { + restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); + break; + } + } + } else if (list[level] == '+') { + for (int i = 0; i < topic->childCount(); ++i) { + //determine the name of the topic, contained by the subscription + QString name; + name.append(topic->child(i)->text(0)); + for (int j = level + 1; j < list.size(); ++j) { + name.append('/' + list[j]); + } + QTreeWidgetItem* temp = topic->child(i); + while (temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + '/'); + } + + //Add the topic as child of the subscription + QStringList nameList; + nameList.append(name); + QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); + subscription->addChild(newItem); + //Continue adding children recursively to the new item + restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); + } + } else if (list[level] == "#") { + //add the children of the # wildcard containing subscription + addSubscriptionChildren(topic, subscription); + } +} + +/*! + *\brief Updates the completer for leSubscriptions + */ +void ImportFileWidget::updateSubscriptionCompleter() { + QStringList subscriptionList; + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) + subscriptionList.append(ui.twSubscriptions->topLevelItem(i)->text(0)); + + if (!subscriptionList.isEmpty()) { + m_subscriptionCompleter = new QCompleter(subscriptionList, this); + m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); + m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); + ui.leSubscriptions->setCompleter(m_subscriptionCompleter); + } else + ui.leSubscriptions->setCompleter(nullptr); +} #endif /************** SLOTS **************************************************************/ -QString absolutePath(const QString& fileName) { +QString absolutePath(const QString& fileName) +{ #ifndef HAVE_WINDOWS // make absolute path // FIXME if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) @@ -870,7 +1413,7 @@ bool fileExists = QFile::exists(fileName); if (fileExists) - ui.leFileName->setStyleSheet(QString()); + ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); @@ -920,7 +1463,7 @@ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), - i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); + i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() @@ -956,8 +1499,7 @@ ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); - if (!m_liveDataSource) - ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); + ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); ui.lPreviewLines->show(); ui.sbPreviewLines->show(); @@ -979,7 +1521,7 @@ break; case AbstractFileFilter::ROOT: ui.tabWidget->removeTab(1); - // falls through + // falls through case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: @@ -1034,7 +1576,7 @@ //for file types other than ASCII and binary we support re-reading the whole file only //select "read whole file" and deactivate the combobox if (m_liveDataSource && (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary)) { - ui.cbReadingType->setCurrentIndex(LiveDataSource::ReadingType::WholeFile); + ui.cbReadingType->setCurrentIndex(3); ui.cbReadingType->setEnabled(false); } else ui.cbReadingType->setEnabled(true); @@ -1046,23 +1588,15 @@ void ImportFileWidget::initOptionsWidget() { DEBUG("ImportFileWidget::initOptionsWidget for " << ENUM_TO_STRING(AbstractFileFilter, FileType, currentFileType())); switch (currentFileType()) { - case AbstractFileFilter::Ascii: { + case AbstractFileFilter::Ascii: if (!m_asciiOptionsWidget) { QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); m_asciiOptionsWidget->loadSettings(); ui.swOptions->addWidget(asciiw); } - - //for MQTT topics we don't allow to set the vector names since the different topics - //can have different number of columns - bool isMQTT = (currentSourceType() == LiveDataSource::MQTT); - m_asciiOptionsWidget->showAsciiHeaderOptions(!isMQTT); - m_asciiOptionsWidget->showTimestampOptions(isMQTT); - ui.swOptions->setCurrentWidget(m_asciiOptionsWidget->parentWidget()); break; - } case AbstractFileFilter::Binary: if (!m_binaryOptionsWidget) { QWidget* binaryw = new QWidget(); @@ -1118,7 +1652,6 @@ } else m_jsonOptionsWidget->clearModel(); ui.swOptions->setCurrentWidget(m_jsonOptionsWidget->parentWidget()); - showJsonModel(true); break; case AbstractFileFilter::ROOT: if (!m_rootOptionsWidget) { @@ -1186,10 +1719,10 @@ } void ImportFileWidget::refreshPreview() { + DEBUG("ImportFileWidget::refreshPreview()"); if (m_suppressRefresh) return; - DEBUG("ImportFileWidget::refreshPreview()"); WAIT_CURSOR; QString fileName = absolutePath(ui.leFileName->text()); @@ -1202,8 +1735,8 @@ // generic table widget if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary - || fileType == AbstractFileFilter::JSON || fileType == AbstractFileFilter::NgspiceRawAscii - || fileType == AbstractFileFilter::NgspiceRawBinary) + || fileType == AbstractFileFilter::JSON || fileType == AbstractFileFilter::NgspiceRawAscii + || fileType == AbstractFileFilter::NgspiceRawBinary) m_twPreview->show(); else m_twPreview->hide(); @@ -1237,8 +1770,9 @@ DEBUG("Local socket: DISCONNECT PREVIEW"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) - } else + } else { DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); + } break; } @@ -1251,8 +1785,9 @@ importedStrings = filter->preview(tcpSocket); tcpSocket.disconnectFromHost(); - } else + } else { DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); + } break; } @@ -1274,15 +1809,16 @@ DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.disconnectFromHost(); - } else + } else { DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); + } break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; DEBUG(" Port: " << serialPort().toStdString() << ", Settings: " << baudRate() << ',' << sPort.dataBits() - << ',' << sPort.parity() << ',' << sPort.stopBits()); + << ',' << sPort.parity() << ',' << sPort.stopBits()); sPort.setPortName(serialPort()); sPort.setBaudRate(baudRate()); @@ -1293,22 +1829,30 @@ DEBUG(" ERROR: not ready for read after 2 sec"); sPort.close(); - } else + } else { DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); - + } break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT - //show the preview for the currently selected topic - auto* item = m_subscriptionWidget->currentItem(); - if (item && item->childCount() == 0) { //only preview if the lowest level (i.e. a topic) is selected - const QString& topicName = item->text(0); - auto i = m_lastMessage.find(topicName); - if (i != m_lastMessage.end()) - importedStrings = filter->preview(i.value().payload().data()); - else - importedStrings << QStringList{i18n("No data arrived yet for the selected topic")}; + qDebug()<<"Start MQTT preview, is it ready:"<vectorNames().clear(); + QMapIterator i(m_lastMessage); + while (i.hasNext()) { + i.next(); + filter->MQTTPreview(importedStrings, QString(i.value().payload().data()), i.key().name() ); + if (importedStrings.isEmpty()) + break; + } + + QMapIterator j(m_messageArrived); + while (j.hasNext()) { + j.next(); + m_messageArrived[j.key()] = false; + } + m_mqttReadyForPreview = false; } #endif break; @@ -1384,11 +1928,11 @@ lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(fileName)); importedStrings = filter->previewCurrentObject( - fileName, - m_rootOptionsWidget->startRow(), - qMin(m_rootOptionsWidget->startRow() + m_rootOptionsWidget->lines() - 1, - m_rootOptionsWidget->endRow()) - ); + fileName, + m_rootOptionsWidget->startRow(), + qMin(m_rootOptionsWidget->startRow() + m_rootOptionsWidget->lines() - 1, + m_rootOptionsWidget->endRow()) + ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names vectorNameList = importedStrings.last(); @@ -1458,8 +2002,9 @@ tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; - } else + } else { m_fileEmpty = true; + } emit previewRefreshed(); @@ -1467,7 +2012,7 @@ } void ImportFileWidget::updateContent(const QString& fileName) { - QDEBUG("ImportFileWidget::updateContent(): file name = " << fileName); + DEBUG("ImportFileWidget::updateContent()"); if (auto filter = currentFileFilter()) { switch (filter->type()) { case AbstractFileFilter::HDF5: @@ -1517,8 +2062,8 @@ const LiveDataSource::SourceType sourceType = currentSourceType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket - || sourceType == LiveDataSource::SourceType::SerialPort - || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { + || sourceType == LiveDataSource::SourceType::SerialPort + || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { @@ -1718,15 +2263,11 @@ //deactivate file types other than ascii and binary for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); - if (ui.cbFileType->currentIndex() > 1) - ui.cbFileType->setCurrentIndex(1); //"whole file" read option is available for file or pipe only, disable it typeModel = qobject_cast(ui.cbReadingType->model()); - QStandardItem* item = typeModel->item(LiveDataSource::WholeFile); + QStandardItem* item = typeModel->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); - if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) - ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); //"update options" groupbox can be deactivated for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. @@ -1759,7 +2300,9 @@ //disconnected from the broker that was selected before, if this is the case if (m_client && m_client->state() == QMqttClient::ClientState::Connected) { - emit MQTTClearTopics(); + ui.twTopics->clear(); + ui.twSubscriptions->clear(); + ui.twTopics->headerItem()->setText(0, i18n("Available")); disconnect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); QDEBUG("Disconnecting from " << m_client->hostname()); m_client->disconnectFromHost(); @@ -1802,9 +2345,7 @@ void ImportFileWidget::onMqttConnect() { if (m_client->error() == QMqttClient::NoError) { m_connectTimeoutTimer->stop(); - ui.frameSubscriptions->setVisible(true); - m_subscriptionWidget->setVisible(true); - m_subscriptionWidget->makeVisible(true); + ui.gbManageSubscriptions->setVisible(true); if (!m_client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); @@ -1819,34 +2360,194 @@ */ void ImportFileWidget::onMqttDisconnect() { DEBUG("Disconected from " << m_client->hostname().toStdString()); + m_searchTimer->stop(); m_connectTimeoutTimer->stop(); ui.lTopics->hide(); - ui.frameSubscriptions->hide(); + ui.gbManageSubscriptions->hide(); ui.lLWT->hide(); ui.bLWT->hide(); ui.cbConnection->setItemText(ui.cbConnection->currentIndex(), ui.cbConnection->currentText() + " " + i18n("(Disconnected)")); + m_mqttReadyForPreview = false; + m_searching = false; + delete m_topicCompleter; + delete m_subscriptionCompleter; emit subscriptionsChanged(); RESET_CURSOR; QMessageBox::critical(this, i18n("Disconnected"), - i18n("Disconnected from the broker '%1' before the connection was successful.", m_client->hostname())); + i18n("Disconnected from the broker '%1' before the connection was successful.", m_client->hostname())); +} + +/*! + *\brief When a leaf topic is double clicked in the topics tree widget we subscribe on that + */ +void ImportFileWidget::mqttAvailableTopicDoubleClicked(QTreeWidgetItem* item, int column) { + Q_UNUSED(column) + // Only for leaf topics + if (item->childCount() == 0) + mqttSubscribe(); +} + +/*! + *\brief When a leaf subscription is double clicked in the topics tree widget we unsubscribe + */ +void ImportFileWidget::mqttSubscribedTopicDoubleClicked(QTreeWidgetItem* item, int column) { + Q_UNUSED(column) + // Only for leaf subscriptions + if (item->childCount() == 0) + mqttUnsubscribe(); } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics */ -void ImportFileWidget::mqttSubscribe(const QString& name, uint QoS) { - const QMqttTopicFilter filter {name}; - QMqttSubscription* tempSubscription = m_client->subscribe(filter, static_cast(QoS) ); +void ImportFileWidget::mqttSubscribe() { + QTreeWidgetItem* item = ui.twTopics->currentItem(); + if (!item) { + QMessageBox::warning(this, i18n("Warning"), i18n("You didn't select any item from the Tree Widget")); + return; + } + + //determine the topic name that the current item represents + QTreeWidgetItem* tempItem = item; + QString name = item->text(0); + if (item->childCount() != 0) + name.append("/#"); - if (tempSubscription) { - m_mqttSubscriptions.push_back(tempSubscription); - connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); - emit subscriptionsChanged(); + while (tempItem->parent()) { + tempItem = tempItem->parent(); + name.prepend(tempItem->text(0) + '/'); } + + //check if the subscription already exists + const QList& topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); + if (topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { + qDebug() << "Subscribe to: " << name; + bool foundSuperior = false; + + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + //if the new subscirptions contains an already existing one, we remove the inferior one + if (checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); + --i; + continue; + } + + //if there is a subscription containing the new one we set foundSuperior true + if (checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + foundSuperior = true; + qDebug()<<"Can't continue subscribe. Found superior for " << name <<" : "<< ui.twSubscriptions->topLevelItem(i)->text(0); + break; + } + } + + //if there wasn't a superior subscription we can subscribe to the new topic + if (!foundSuperior) { + QStringList toplevelName; + toplevelName.push_back(name); + QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); + ui.twSubscriptions->addTopLevelItem(newTopLevelItem); + + const QMqttTopicFilter filter {name}; + QMqttSubscription *tempSubscription = m_client->subscribe(filter, static_cast(ui.cbQos->currentText().toUInt()) ); + + if (tempSubscription) { + m_mqttSubscriptions.push_back(tempSubscription); + connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); + emit subscriptionsChanged(); + } + + if (name.endsWith('#')) { + //adding every topic that the subscription contains to twSubscriptions + addSubscriptionChildren(item, newTopLevelItem); + + //if an already existing subscription contains a topic that the new subscription also contains + //we decompose the already existing subscription + //by unsubscribing from its topics, that are present in the new subscription as well + const QStringList nameList = name.split('/', QString::SkipEmptyParts); + const QString& root = nameList.first(); + QVector children; + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + children.clear(); + //get the "leaf" children of the inspected subscription + findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); + for (int j = 0; j < children.size(); ++j) { + if (checkTopicContains(name, children[j]->text(0))) { + //if the new subscription contains a topic, we unsubscribe from it + ui.twSubscriptions->setCurrentItem(children[j]); + mqttUnsubscribe(); + --i; + } + } + } + } + } + + manageCommonLevelSubscriptions(); + updateSubscriptionCompleter(); + + if (!ui.bLWT->isEnabled()) + ui.bLWT->setEnabled(true); + } else + QMessageBox::warning(this, i18n("Warning"), i18n("You already subscribed to a topic containing this one")); + } else + QMessageBox::warning(this, i18n("Warning"), i18n("You already subscribed to this topic")); +} + +/*! + *\brief called when the unsubscribe button is pressed + * unsubscribes from the topic represented by the current item of twSubscription + */ +void ImportFileWidget::mqttUnsubscribe() { + QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); + if (!unsubscribeItem) { + QMessageBox::warning(this, i18n("Warning"), i18n("You didn't select any item from the Tree Widget")); + return; + } + + qDebug() << "Unsubscribe from: " << unsubscribeItem->text(0); + //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) + //we can simply unsubscribe from it + if (unsubscribeItem->parent() == nullptr) + unsubscribeFromTopic(unsubscribeItem->text(0)); + + //otherwise we remove the selected item, but subscribe to every other topic, that was contained by + //the selected item's parent subscription(top level item of twSubscriptions) + else { + while (unsubscribeItem->parent() != nullptr) { + for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { + if (unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { + const QMqttTopicFilter filter {unsubscribeItem->parent()->child(i)->text(0)}; + QMqttSubscription *tempSubscription = m_client->subscribe(filter, static_cast(ui.cbQos->currentText().toUInt()) ); + + ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); + + if (tempSubscription) { + m_mqttSubscriptions.push_back(tempSubscription); + connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); + emit subscriptionsChanged(); + } + --i; + } + } + unsubscribeItem = unsubscribeItem->parent(); + } + unsubscribeFromTopic(unsubscribeItem->text(0)); + + //check if any common topics were subscribed, if possible merge them + manageCommonLevelSubscriptions(); + } + updateSubscriptionCompleter(); + + if (ui.twSubscriptions->topLevelItemCount() <= 0) + ui.bLWT->setEnabled(false); } /*! @@ -1860,7 +2561,7 @@ return; m_addedTopics.push_back(topic.name()); - m_subscriptionWidget->setTopicTreeText(i18n("Available (%1)", m_addedTopics.size())); + ui.twTopics->headerItem()->setText(0, i18n("Available (%1)", m_addedTopics.size())); QStringList name; QString rootName; const QChar sep = '/'; @@ -1873,8 +2574,8 @@ name.append(list.at(0)); int topItemIdx = -1; //check whether the first level of the topic can be found in twTopics - for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { - if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { topItemIdx = i; break; } @@ -1883,7 +2584,7 @@ //if not we simply add every level of the topic to the tree if (topItemIdx < 0) { QTreeWidgetItem* currentItem = new QTreeWidgetItem(name); - m_subscriptionWidget->addTopic(currentItem); + ui.twTopics->addTopLevelItem(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); @@ -1894,7 +2595,7 @@ //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { - QTreeWidgetItem* currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); + QTreeWidgetItem* currentItem = ui.twTopics->topLevelItem(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; @@ -1925,18 +2626,15 @@ } else { rootName = topic.name(); name.append(topic.name()); - m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); + ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions - for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { - const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + const QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); if (!subscriptionName.isEmpty()) { if (rootName == subscriptionName.first()) { - QVector subscriptions; - for(int i = 0; i < m_mqttSubscriptions.size(); ++i) - subscriptions.push_back(m_mqttSubscriptions[i]->topic().filter()); - emit updateSubscriptionTree(subscriptions); + updateSubscriptionTree(); break; } } @@ -1946,17 +2644,64 @@ emit newTopic(rootName); } +/*! + *\brief called when a new topic is added to the tree(twTopics) + * appends the topic's root to the topicList if it isn't in the list already + * then sets the completer for leTopics + */ +void ImportFileWidget::setTopicCompleter(const QString& topic) { + if (!m_topicList.contains(topic)) { + m_topicList.append(topic); + if (!m_searching) { + m_topicCompleter = new QCompleter(m_topicList, this); + m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); + m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); + ui.leTopics->setCompleter(m_topicCompleter); + } + } +} + +/*! + *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics + * enables updating the completer for le + */ +void ImportFileWidget::topicTimeout() { + m_searching = false; + m_searchTimer->stop(); +} + /*! *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) */ void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { - QDEBUG("message received from: " << msg.topic().name()); - - if (!m_subscribedTopicNames.contains(msg.topic().name())) + qDebug()<<"message received from: "< i(m_messageArrived); + while (i.hasNext()) { + i.next(); + if (i.value() == false ) { + check = false; + break; + } + } + + //if there is a message from every subscribed topic, we refresh the preview + if (check) { + m_mqttReadyForPreview = true; + refreshPreview(); + } } /*! @@ -1985,13 +2730,54 @@ case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ProtocolViolation: - case QMqttClient::Mqtt5SpecificError: break; default: break; } } +/*! + *\brief called when leTopics' text is changed + * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget + * + * \param rootName the current text of leTopics + */ +void ImportFileWidget::scrollToTopicTreeItem(const QString& rootName) { + m_searching = true; + m_searchTimer->start(); + + int topItemIdx = -1; + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) + if (ui.twTopics->topLevelItem(i)->text(0) == rootName) { + topItemIdx = i; + break; + } + + if (topItemIdx >= 0) + ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); +} + +/*! + *\brief called when leSubscriptions' text is changed + * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget + * + * \param rootName the current text of leSubscriptions + */ +void ImportFileWidget::scrollToSubsriptionTreeItem(const QString& rootName) { + m_searching = true; + m_searchTimer->start(); + + int topItemIdx = -1; + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) + if (ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { + topItemIdx = i; + break; + } + + if (topItemIdx >= 0) + ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); +} + /*! *\brief called when m_connectTimeoutTimer ticks, * meaning that the client couldn't connect to the broker in 5 seconds @@ -2061,8 +2847,8 @@ QMenu menu; QVector children; - for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) - MQTTSubscriptionWidget::findSubscriptionLeafChildren(children, m_subscriptionWidget->topLevelSubscription(i)); + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) + findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); QVector topics; for (int i = 0; i < children.size(); ++i) @@ -2082,12 +2868,4 @@ menu.exec(ui.bLWT->mapToGlobal(pos)); } -void ImportFileWidget::enableWill(bool enable) { - if(enable) { - if(!ui.bLWT->isEnabled()) - ui.bLWT->setEnabled(enable); - } else - ui.bLWT->setEnabled(enable); -} - #endif diff --git a/src/kdefrontend/datasources/ImportProjectDialog.h b/src/kdefrontend/datasources/ImportProjectDialog.h --- a/src/kdefrontend/datasources/ImportProjectDialog.h +++ b/src/kdefrontend/datasources/ImportProjectDialog.h @@ -3,7 +3,7 @@ Project : LabPlot Description : import project dialog -------------------------------------------------------------------- - Copyright : (C) 2017-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -67,6 +67,7 @@ bool isTopLevel(const AbstractAspect*) const; private slots: + void loadSettings(); void fileNameChanged(const QString&); void refreshPreview(); void selectionChanged(const QItemSelection&, const QItemSelection&); diff --git a/src/kdefrontend/datasources/ImportProjectDialog.cpp b/src/kdefrontend/datasources/ImportProjectDialog.cpp --- a/src/kdefrontend/datasources/ImportProjectDialog.cpp +++ b/src/kdefrontend/datasources/ImportProjectDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : import project dialog -------------------------------------------------------------------- - Copyright : (C) 2017-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -42,7 +42,6 @@ #include #include #include -#include #include #include @@ -124,14 +123,13 @@ setWindowTitle(title); setWindowIcon(QIcon::fromTheme("document-import")); - //restore saved settings if available - create(); // ensure there's a window created + QTimer::singleShot(0, this, &ImportProjectDialog::loadSettings); +} + +void ImportProjectDialog::loadSettings() { + //restore saved settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(300, 0).expandedTo(minimumSize())); + KWindowConfig::restoreWindowSize(windowHandle(), conf); QString lastImportedFile; switch (m_projectType) { @@ -392,7 +390,7 @@ bool fileExists = QFile::exists(fileName); if (fileExists) - ui.leFileName->setStyleSheet(QString()); + ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); diff --git a/src/kdefrontend/datasources/ImportSQLDatabaseDialog.h b/src/kdefrontend/datasources/ImportSQLDatabaseDialog.h --- a/src/kdefrontend/datasources/ImportSQLDatabaseDialog.h +++ b/src/kdefrontend/datasources/ImportSQLDatabaseDialog.h @@ -4,7 +4,7 @@ Description : import SQL database dialog -------------------------------------------------------------------- Copyright : (C) 2016 by Ankit Wagadre (wagadre.ankit@gmail.com) - Copyright : (C) 2016-2019 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -48,9 +48,10 @@ private: ImportSQLDatabaseWidget* importSQLDatabaseWidget; - protected slots: void checkOkButton() override; +private slots: + void loadSettings(); }; #endif //IMPORTSQLDATABASEDIALOG_H diff --git a/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp b/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp --- a/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp +++ b/src/kdefrontend/datasources/ImportSQLDatabaseDialog.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include @@ -72,14 +71,14 @@ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - //restore saved settings if available - create(); // ensure there's a window created + QTimer::singleShot(0, this, &ImportSQLDatabaseDialog::loadSettings); +} + +void ImportSQLDatabaseDialog::loadSettings() { + //restore saved settings + QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "ImportSQLDatabaseDialog"); - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); + KWindowConfig::restoreWindowSize(windowHandle(), conf); } ImportSQLDatabaseDialog::~ImportSQLDatabaseDialog() { diff --git a/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp b/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp --- a/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp +++ b/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp @@ -111,7 +111,7 @@ KConfigGroup config(KSharedConfig::openConfig(), "ImportSQLDatabaseWidget"); config.writeEntry("Connection", ui.cbConnection->currentText()); config.writeEntry("ImportFrom", ui.cbImportFrom->currentIndex()); - config.writeEntry("NumberFormat", ui.cbNumberFormat->currentIndex()); + config.writeEntry("NumberFormat", ui.cbNumberFormat->currentText()); config.writeEntry("DateTimeFormat", ui.cbDateTimeFormat->currentText()); config.writeEntry("SplitterMainSizes", ui.splitterMain->sizes()); config.writeEntry("SplitterPreviewSizes", ui.splitterPreview->sizes()); @@ -167,8 +167,6 @@ ui.teQuery->clear(); ui.lwTables->clear(); ui.twPreview->clear(); - ui.twPreview->setColumnCount(0); - ui.twPreview->setRowCount(0); if (ui.cbConnection->currentIndex() == -1) return; @@ -177,12 +175,6 @@ KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); - //close and remove the previos connection, if available - if (m_db.isOpen()) { - m_db.close(); - QSqlDatabase::removeDatabase(m_db.driverName()); - } - //open the selected connection const QString& driver = group.readEntry("Driver"); m_db = QSqlDatabase::addDatabase(driver); @@ -252,7 +244,7 @@ q.prepare(currentQuery(true)); q.setForwardOnly(true); q.exec(); - if (!q.isActive() || !q.next()) { // check if query was succesful and got to first record + if (!q.isActive()) { RESET_CURSOR; if (!q.lastError().databaseText().isEmpty()) KMessageBox::error(this, q.lastError().databaseText(), i18n("Unable to Execute Query")); @@ -272,6 +264,7 @@ bool numeric = true; const auto numberFormat = (QLocale::Language)ui.cbNumberFormat->currentIndex(); const QString& dateTimeFormat = ui.cbDateTimeFormat->currentText(); + q.next(); //go to the first record // ui.twPreview->setRowCount(1); //add the first row for the check boxes for (int i = 0; i < m_cols; ++i) { //name @@ -422,9 +415,7 @@ //preview the content of the currently selected table const QString& driver = m_db.driverName(); const QString& limit = QString::number(ui.sbPreviewLines->value()); - if ( (driver == QLatin1String("QSQLITE3")) || (driver == QLatin1String("QSQLITE")) - || (driver == QLatin1String("QMYSQL3")) || (driver == QLatin1String("QMYSQL")) - || (driver == QLatin1String("QPSQL")) ) + if ( (driver == QLatin1String("QSQLITE3")) || (driver == QLatin1String("QMYSQL3")) || (driver == QLatin1String("QPSQL")) || (driver == QLatin1String("QSQLITE")) || (driver == QLatin1String("QMYSQL")) ) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" LIMIT ") + limit; else if (driver == QLatin1String("QOCI")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" ROWNUM<=") + limit; @@ -433,8 +424,7 @@ else if (driver == QLatin1String("QIBASE")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" ROWS ") + limit; else - //for ODBC the DBMS is not known and it's not clear what syntax to use -> select all rows - query = QLatin1String("SELECT * FROM ") + tableName; + query = QLatin1String("SELECT TOP ") + limit + QLatin1String(" * FROM ") + tableName; } } else { //preview the result of a custom query @@ -456,13 +446,11 @@ m_initializing = true; ui.cbConnection->clear(); readConnections(); + m_initializing = false; //select the connection the user has selected in DatabaseManager const QString& conn = dlg->connection(); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conn)); - m_initializing = false; - - connectionChanged(); } delete dlg; @@ -470,9 +458,6 @@ void ImportSQLDatabaseWidget::setInvalid() { if (m_valid) { - ui.twPreview->setColumnCount(0); - ui.twPreview->setRowCount(0); - m_valid = false; emit stateChanged(); } diff --git a/src/kdefrontend/datasources/JsonOptionsWidget.cpp b/src/kdefrontend/datasources/JsonOptionsWidget.cpp --- a/src/kdefrontend/datasources/JsonOptionsWidget.cpp +++ b/src/kdefrontend/datasources/JsonOptionsWidget.cpp @@ -76,7 +76,6 @@ void JsonOptionsWidget::clearModel() { m_model->clear(); - m_filename.clear(); } void JsonOptionsWidget::loadSettings() const { @@ -105,11 +104,13 @@ m_filename = filename; KFilterDev device(m_filename); - if (!device.open(QIODevice::ReadOnly) || - (device.atEnd() && !device.isSequential()) || // empty file - !m_model->loadJson(device.readAll()) - ) - clearModel(); + if (!device.open(QIODevice::ReadOnly)) + return; + + if (device.atEnd() && !device.isSequential()) // empty file + return; + if (!m_model->loadJson(device.readAll())) + m_model->clear(); } QAbstractItemModel* JsonOptionsWidget::model() { diff --git a/src/kdefrontend/datasources/MQTTConnectionManagerDialog.h b/src/kdefrontend/datasources/MQTTConnectionManagerDialog.h --- a/src/kdefrontend/datasources/MQTTConnectionManagerDialog.h +++ b/src/kdefrontend/datasources/MQTTConnectionManagerDialog.h @@ -54,6 +54,8 @@ private slots: void changed(); void save(); + void loadSettings(); + #endif // HAVE_MQTT }; diff --git a/src/kdefrontend/datasources/MQTTConnectionManagerDialog.cpp b/src/kdefrontend/datasources/MQTTConnectionManagerDialog.cpp --- a/src/kdefrontend/datasources/MQTTConnectionManagerDialog.cpp +++ b/src/kdefrontend/datasources/MQTTConnectionManagerDialog.cpp @@ -36,7 +36,7 @@ #include #include -#include +#include /*! \class MQTTConnectionManagerDialog @@ -64,14 +64,17 @@ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - //restore saved settings if available - create(); // ensure there's a window created + QTimer::singleShot(0, this, &MQTTConnectionManagerDialog::loadSettings); +} + +/*! + * \brief Loads the settings for the dialog + */ +void MQTTConnectionManagerDialog::loadSettings() { + //restore saved settings + QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "MQTTConnectionManagerDialog"); - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); + KWindowConfig::restoreWindowSize(windowHandle(), conf); } /*! diff --git a/src/kdefrontend/datasources/MQTTConnectionManagerWidget.h b/src/kdefrontend/datasources/MQTTConnectionManagerWidget.h --- a/src/kdefrontend/datasources/MQTTConnectionManagerWidget.h +++ b/src/kdefrontend/datasources/MQTTConnectionManagerWidget.h @@ -4,7 +4,6 @@ Description : widget for managing MQTT connections -------------------------------------------------------------------- Copyright : (C) 2018 Ferencz Kovacs (kferike98@gmail.com) -Copyright : (C) 2018-2019 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -42,7 +41,6 @@ public: explicit MQTTConnectionManagerWidget(QWidget*, const QString&); - ~MQTTConnectionManagerWidget() override; struct MQTTConnection { QString name; @@ -64,13 +62,12 @@ private: Ui::MQTTConnectionManagerWidget ui; QList m_connections; - MQTTConnection* m_currentConnection = nullptr; bool m_initializing{false}; QString m_configPath; QString m_initConnName; - QMqttClient* m_client{nullptr}; - bool m_testing{false}; - QTimer* m_testTimer{nullptr}; + QMqttClient* m_client; + bool m_testing {false}; + QTimer* m_testTimer; QString uniqueName(); void loadConnection(); @@ -84,7 +81,7 @@ void connectionChanged(int); void nameChanged(const QString&); void hostChanged(const QString&); - void portChanged(const QString&); + void portChanged(); void userNameChanged(const QString&); void passwordChanged(const QString&); void clientIdChanged(const QString&); diff --git a/src/kdefrontend/datasources/MQTTConnectionManagerWidget.cpp b/src/kdefrontend/datasources/MQTTConnectionManagerWidget.cpp --- a/src/kdefrontend/datasources/MQTTConnectionManagerWidget.cpp +++ b/src/kdefrontend/datasources/MQTTConnectionManagerWidget.cpp @@ -4,7 +4,6 @@ Description : widget for managing MQTT connections -------------------------------------------------------------------- Copyright : (C) 2018 Ferencz Kovacs (kferike98@gmail.com) -Copyright : (C) 2018-2019 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -37,7 +36,6 @@ #include #include #include - #include #include #include @@ -50,20 +48,27 @@ \ingroup kdefrontend */ MQTTConnectionManagerWidget::MQTTConnectionManagerWidget(QWidget* parent, const QString& conn) : QWidget(parent), - m_initConnName(conn) { + m_initConnName(conn), + m_client(new QMqttClient), + m_testTimer(new QTimer(this)) { ui.setupUi(this); - m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QStringLiteral("MQTT_connections"); + m_testTimer->setInterval(5000); + + m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + "MQTT_connections"; ui.lePort->setValidator(new QIntValidator(ui.lePort)); - ui.bAdd->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); - ui.bRemove->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); + ui.bAdd->setIcon(QIcon::fromTheme("list-add")); + ui.bRemove->setIcon(QIcon::fromTheme("list-remove")); ui.bAdd->setToolTip(i18n("Add new MQTT connection")); ui.bRemove->setToolTip(i18n("Remove selected MQTT connection")); - ui.bTest->setIcon(QIcon::fromTheme(QStringLiteral("network-connect"))); + ui.bTest->setIcon(QIcon::fromTheme("network-connect")); //SIGNALs/SLOTs + connect(m_testTimer, &QTimer::timeout, this, &MQTTConnectionManagerWidget::testTimeout); + connect(m_client, &QMqttClient::connected, this, &MQTTConnectionManagerWidget::onConnect); + connect(m_client, &QMqttClient::disconnected, this, &MQTTConnectionManagerWidget::onDisconnect); connect(ui.leName, &QLineEdit::textChanged, this, &MQTTConnectionManagerWidget::nameChanged); connect(ui.lwConnections, &QListWidget::currentRowChanged, this, &MQTTConnectionManagerWidget::connectionChanged); connect(ui.bAdd, &QPushButton::clicked, this, &MQTTConnectionManagerWidget::addConnection); @@ -80,21 +85,17 @@ ui.lePassword->hide(); ui.lPassword->hide(); - ui.lePassword->setToolTip(i18n("Please set a password.")); + ui.lePassword->setToolTip("Please set a password"); ui.leUserName->hide(); ui.lUsername->hide(); - ui.leUserName->setToolTip(i18n("Please set a username.")); + ui.leUserName->setToolTip("Please set a username"); ui.leID->hide(); ui.lID->hide(); - ui.leID->setToolTip(i18n("Please set a client ID.")); - ui.leHost->setToolTip(i18n("Please set a valid host name.")); - ui.leHost->setToolTip(i18n("Please set a valid name.")); - - QTimer::singleShot(100, this, SLOT(loadConnections())); -} + ui.leID->setToolTip("Please set a client ID"); + ui.leHost->setToolTip("Please set a valid host name"); + ui.leHost->setToolTip("Please set a valid name"); -MQTTConnectionManagerWidget::~MQTTConnectionManagerWidget() { - delete m_client; + QTimer::singleShot( 100, this, SLOT(loadConnections())); } /*! @@ -115,33 +116,30 @@ if (m_initializing) return; - if (index == -1) { - m_currentConnection = nullptr; + if (index == -1) return; - } m_initializing = true; - m_currentConnection = &m_connections[index]; //show the settings for the selected connection - ui.leName->setText(m_currentConnection->name); - ui.leHost->setText(m_currentConnection->hostName); - ui.lePort->setText(QString::number(m_currentConnection->port)); + ui.leName->setText(m_connections[index].name); + ui.leHost->setText(m_connections[index].hostName); + ui.lePort->setText(QString::number(m_connections[index].port)); - if (m_currentConnection->useAuthentication) { + if (m_connections[index].useAuthentication) { ui.chbAuthentication->setChecked(true); - ui.leUserName->setText(m_currentConnection->userName); - ui.lePassword->setText(m_currentConnection->password); + ui.leUserName->setText(m_connections[index].userName); + ui.lePassword->setText(m_connections[index].password); } else ui.chbAuthentication->setChecked(false); - if (m_currentConnection->useID) { + if (m_connections[index].useID) { ui.chbID->setChecked(true); - ui.leID->setText(m_currentConnection->clientID); + ui.leID->setText(m_connections[index].clientID); } else ui.chbID->setChecked(false); - ui.chbRetain->setChecked(m_currentConnection->retain); + ui.chbRetain->setChecked(m_connections[index].retain); m_initializing = false; } @@ -152,9 +150,12 @@ */ void MQTTConnectionManagerWidget::nameChanged(const QString &name) { if (name.isEmpty()) { - ui.leName->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leHost->setToolTip(i18n("Please set a valid name.")); + ui.leName->setStyleSheet("QLineEdit{background: red;}"); + ui.leHost->setToolTip("Please set a valid name"); } else { + ui.leName->setStyleSheet(""); + ui.leName->setToolTip(""); + //check uniqueness of the provided name bool unique = true; for (int i = 0; i < ui.lwConnections->count(); ++i) { @@ -168,17 +169,17 @@ } if (unique) { - ui.leName->setStyleSheet(QString()); - ui.leName->setToolTip(QString()); + ui.leName->setStyleSheet(""); + ui.leName->setToolTip(""); ui.lwConnections->currentItem()->setText(name); if (!m_initializing) { - m_currentConnection->name = name; + m_connections[ui.lwConnections->currentRow()].name = name; emit changed(); } } else { - ui.leName->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leHost->setToolTip(i18n("Please provide a unique name.")); + ui.leName->setStyleSheet("QLineEdit{background: red;}"); + ui.leHost->setToolTip("There can't be more identical names"); } } } @@ -189,35 +190,36 @@ */ void MQTTConnectionManagerWidget::hostChanged(const QString& hostName) { if (hostName.isEmpty()) { - ui.leHost->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leHost->setToolTip(i18n("Please set a valid host name.")); + ui.leHost->setStyleSheet("QLineEdit{background: red;}"); + ui.leHost->setToolTip("Please set a valid host name"); } else { - m_currentConnection->hostName = hostName; + ui.leHost->setStyleSheet(""); + ui.leHost->setToolTip(""); + //check uniqueness of the provided host name bool unique = true; - for (auto & c : m_connections) { - if (m_currentConnection == &c) + for (int i = 0; i < m_connections.size(); ++i) { + if (ui.lwConnections->currentRow() == i) continue; - if (m_currentConnection->hostName == c.hostName && m_currentConnection->port == c.port) { + if (hostName == m_connections[i].hostName) { unique = false; break; } } if (!unique) { - ui.leHost->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leHost->setToolTip(i18n("Host name and port must be unique.")); - ui.lePort->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.lePort->setToolTip(i18n("Host name and port must be unique.")); + ui.leHost->setStyleSheet("QLineEdit{background: red;}"); + ui.leHost->setToolTip("There can't be more identical hostnames"); } else { - ui.leHost->setStyleSheet(QString()); - ui.leHost->setToolTip(QString()); - ui.lePort->setStyleSheet(QString()); - ui.lePort->setToolTip(QString()); + ui.leHost->setStyleSheet(""); + ui.leHost->setToolTip(""); - if (!m_initializing) - emit changed(); + if (m_initializing) + return; + + m_connections[ui.lwConnections->currentRow()].hostName = hostName; + emit changed(); } } } @@ -226,40 +228,12 @@ * \brief Called when the port is changed * Sets the port for the current connection */ -void MQTTConnectionManagerWidget::portChanged(const QString& portString) { - if (portString.isEmpty()) { - ui.leHost->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leHost->setToolTip(i18n("Please set a valid port.")); - } else { - m_currentConnection->port = portString.simplified().toInt(); - //check uniqueness of the provided host name - bool unique = true; - for (auto & c : m_connections) { - if (m_currentConnection == &c) - continue; - - if (m_currentConnection->hostName == c.hostName && m_currentConnection->port == c.port) { - unique = false; - break; - } - } - - if (!unique) { - ui.leHost->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leHost->setToolTip(i18n("Host name and port must be unique.")); - ui.lePort->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.lePort->setToolTip(i18n("Host name and port must be unique.")); - } else { - ui.leHost->setStyleSheet(QString()); - ui.leHost->setToolTip(QString()); - ui.lePort->setStyleSheet(QString()); - ui.lePort->setToolTip(QString()); - - if (!m_initializing) - emit changed(); - } - } +void MQTTConnectionManagerWidget::portChanged() { + if (m_initializing) + return; + m_connections[ui.lwConnections->currentRow()].port = ui.lePort->text().simplified().toInt(); + emit changed(); } /*! @@ -275,11 +249,13 @@ ui.lUsername->show(); ui.leUserName->show(); - if (m_currentConnection) { - ui.lePassword->setText(m_currentConnection->password); - ui.leUserName->setText(m_currentConnection->userName); - if (!m_initializing) - m_currentConnection->useAuthentication = true; + if (m_connections.size() > 0) { + ui.lePassword->setText(m_connections[ui.lwConnections->currentRow()].password); + ui.leUserName->setText(m_connections[ui.lwConnections->currentRow()].userName); + } + + if (!m_initializing) { + m_connections[ui.lwConnections->currentRow()].useAuthentication = true; } } else if (state == Qt::CheckState::Unchecked) { @@ -290,8 +266,8 @@ ui.leUserName->hide(); ui.leUserName->clear(); - if (m_currentConnection && !m_initializing) { - m_currentConnection->useAuthentication = false; + if (!m_initializing) { + m_connections[ui.lwConnections->currentRow()].useAuthentication = false; } } @@ -308,10 +284,12 @@ ui.lID->show(); ui.leID->show(); - if (m_currentConnection) { - ui.leID->setText(m_currentConnection->clientID); - if (!m_initializing) - m_currentConnection->useID = true; + if (m_connections.size() > 0) { + ui.leID->setText(m_connections[ui.lwConnections->currentRow()].clientID); + } + + if (!m_initializing) { + m_connections[ui.lwConnections->currentRow()].useID = true; } } else if (state == Qt::CheckState::Unchecked) { @@ -319,8 +297,8 @@ ui.leID->hide(); ui.leID->clear(); - if (m_currentConnection && !m_initializing) { - m_currentConnection->useID = false; + if (!m_initializing) { + m_connections[ui.lwConnections->currentRow()].useID = false; } } @@ -336,12 +314,10 @@ if (m_initializing) return; - if (m_currentConnection) { - if (state == Qt::CheckState::Checked) { - m_currentConnection->retain = true; - } else if (state == Qt::CheckState::Unchecked) { - m_currentConnection->retain = false; - } + if (state == Qt::CheckState::Checked) { + m_connections[ui.lwConnections->currentRow()].retain = true; + } else if (state == Qt::CheckState::Unchecked) { + m_connections[ui.lwConnections->currentRow()].retain = false; } emit changed(); } @@ -352,18 +328,17 @@ */ void MQTTConnectionManagerWidget::userNameChanged(const QString& userName) { if (userName.isEmpty()) { - ui.leUserName->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leUserName->setToolTip(i18n("Please set a username.")); + ui.leUserName->setStyleSheet("QLineEdit{background: red;}"); + ui.leUserName->setToolTip("Please set a username"); } else { - ui.leUserName->setStyleSheet(QString()); - ui.leUserName->setToolTip(QString()); + ui.leUserName->setStyleSheet(""); + ui.leUserName->setToolTip(""); } if (m_initializing) return; - if (m_currentConnection) - m_currentConnection->userName = userName; + m_connections[ui.lwConnections->currentRow()].userName = userName; emit changed(); } @@ -373,18 +348,17 @@ */ void MQTTConnectionManagerWidget::passwordChanged(const QString& password) { if (password.isEmpty()) { - ui.lePassword->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.lePassword->setToolTip(i18n("Please set a password.")); + ui.lePassword->setStyleSheet("QLineEdit{background: red;}"); + ui.lePassword->setToolTip("Please set a password"); } else { - ui.lePassword->setStyleSheet(QString()); - ui.lePassword->setToolTip(QString()); + ui.lePassword->setStyleSheet(""); + ui.lePassword->setToolTip(""); } if (m_initializing) return; - if (m_currentConnection) - m_currentConnection->password = password; + m_connections[ui.lwConnections->currentRow()].password = password; emit changed(); } @@ -394,18 +368,17 @@ */ void MQTTConnectionManagerWidget::clientIdChanged(const QString& clientID) { if (clientID.isEmpty()) { - ui.leID->setStyleSheet(QStringLiteral("QLineEdit{background: red;}")); - ui.leID->setToolTip(i18n("Please set a client ID.")); + ui.leID->setStyleSheet("QLineEdit{background: red;}"); + ui.leID->setToolTip("Please set a client ID"); } else { - ui.leID->setStyleSheet(QString()); - ui.leID->setToolTip(QString()); + ui.leID->setStyleSheet(""); + ui.leID->setToolTip(""); } if (m_initializing) return; - if (m_currentConnection) - m_currentConnection->clientID = clientID; + m_connections[ui.lwConnections->currentRow()].clientID = clientID; emit changed(); } @@ -416,15 +389,14 @@ qDebug() << "Adding new connection"; MQTTConnection conn; conn.name = uniqueName(); - conn.hostName = QStringLiteral("localhost"); + conn.hostName = QLatin1String("localhost"); conn.port = 1883; conn.useAuthentication = false; conn.useID = false; m_connections.append(conn); - m_currentConnection = &m_connections.back(); ui.lwConnections->addItem(conn.hostName); - ui.lwConnections->setCurrentRow(m_connections.size() - 1); + ui.lwConnections->setCurrentRow(m_connections.size()-1); //we have now more than one connection, enable widgets ui.bRemove->setEnabled(true); @@ -450,14 +422,16 @@ //remove the current selected connection m_connections.removeAt(ui.lwConnections->currentRow()); m_initializing = true; - delete ui.lwConnections->takeItem(ui.lwConnections->currentRow()); + QListWidgetItem* item = ui.lwConnections->takeItem(ui.lwConnections->currentRow()); + if (item) + delete item; m_initializing = false; //show the connection for the item that was automatically selected after the deletion connectionChanged(ui.lwConnections->currentRow()); //disable widgets if there are no connections anymore - if (!m_currentConnection) { + if (m_connections.size() == 0) { m_initializing = true; ui.leName->clear(); ui.leName->setEnabled(false); @@ -510,13 +484,13 @@ } //show the first connection if available, create a new connection otherwise - if (!m_connections.empty()) { + if (m_connections.size()) { if (!m_initConnName.isEmpty()) { - auto items = ui.lwConnections->findItems(m_initConnName, Qt::MatchExactly); - if (items.empty()) - ui.lwConnections->setCurrentRow(ui.lwConnections->count() - 1); + QListWidgetItem* item = ui.lwConnections->findItems(m_initConnName, Qt::MatchExactly).constFirst(); + if (item) + ui.lwConnections->setCurrentItem(item); else - ui.lwConnections->setCurrentItem(items.constFirst()); + ui.lwConnections->setCurrentRow(ui.lwConnections->count() - 1); } else { ui.lwConnections->setCurrentRow(ui.lwConnections->count() - 1); } @@ -566,14 +540,13 @@ bool connectionsOk = true; for (int i = 0; i < m_connections.size(); ++i) { - auto & c1 = m_connections[i]; - QList equalNames = ui.lwConnections->findItems(c1.name, Qt::MatchExactly); - bool nameOK = (!c1.name.isEmpty()) && (equalNames.size() == 1); - - bool authenticationUsed = c1.useAuthentication; - bool idUsed = c1.useID; - bool authenticationFilled = !c1.userName.isEmpty() && !c1.password.isEmpty(); - bool idFilled = !c1.clientID.isEmpty(); + QList equalNames = ui.lwConnections->findItems(m_connections[i].name, Qt::MatchExactly); + bool nameOK = (!m_connections[i].name.isEmpty()) && (equalNames.size() == 1); + + bool authenticationUsed = m_connections[i].useAuthentication; + bool idUsed = m_connections[i].useID; + bool authenticationFilled = !m_connections[i].userName.isEmpty() && !m_connections[i].password.isEmpty(); + bool idFilled = !m_connections[i].clientID.isEmpty(); bool authenticationOK = (!authenticationUsed || authenticationFilled); bool idOK = (!idUsed || idFilled); @@ -581,21 +554,20 @@ for (int j = 0; j < m_connections.size(); ++j) { if (i == j) continue; - auto & c2 = m_connections[j]; - if (c2.hostName == c1.hostName && c2.port == c1.port) { + if (m_connections[j].hostName == m_connections[i].hostName) { uniqueHost = false; break; } } - bool hostOK = (!c1.hostName.isEmpty()) && uniqueHost; + bool hostOK = (!m_connections[i].hostName.isEmpty()) && uniqueHost; bool allOk = authenticationOK && idOK && hostOK && nameOK; if (!allOk) { connectionsOk = false; - ui.lwConnections->item(i)->setBackgroundColor(Qt::red); + ui.lwConnections->item(i)->setBackground(QBrush(Qt::red)); } else - ui.lwConnections->item(i)->setBackground(QBrush()); + ui.lwConnections->item(i)->setBackground(QBrush(Qt::white)); } return connectionsOk; } @@ -621,7 +593,7 @@ base.chop(1); if (lastNonDigit >=0 && base[lastNonDigit].category() != QChar::Separator_Space) - base.append(' '); + base.append(" "); int newNr = name.rightRef(name.size() - base.size()).toInt(); QString newName; @@ -636,31 +608,20 @@ * \brief Tests the currently selected connection */ void MQTTConnectionManagerWidget::testConnection() { - if (!m_currentConnection) - return; - WAIT_CURSOR; m_testing = true; + const int index = ui.lwConnections->currentRow(); - if (!m_client) { - m_client = new QMqttClient; - m_testTimer = new QTimer(this); - m_testTimer->setInterval(5000); - connect(m_client, &QMqttClient::connected, this, &MQTTConnectionManagerWidget::onConnect); - connect(m_client, &QMqttClient::disconnected, this, &MQTTConnectionManagerWidget::onDisconnect); - connect(m_testTimer, &QTimer::timeout, this, &MQTTConnectionManagerWidget::testTimeout); - } - - m_client->setHostname(m_currentConnection->hostName); - m_client->setPort(m_currentConnection->port); + m_client->setHostname(m_connections[index].hostName); + m_client->setPort(m_connections[index].port); - if (m_currentConnection->useID) - m_client->setClientId(m_currentConnection->clientID); + if (m_connections[index].useID) + m_client->setClientId(m_connections[index].clientID); - if (m_currentConnection->useAuthentication) { - m_client->setUsername(m_currentConnection->userName); - m_client->setPassword(m_currentConnection->password); + if (m_connections[index].useAuthentication) { + m_client->setUsername(m_connections[index].userName); + m_client->setPassword(m_connections[index].password); } m_testTimer->start(); @@ -674,9 +635,9 @@ RESET_CURSOR; m_testTimer->stop(); - KMessageBox::information(this, i18n("Connection to the broker '%1:%2' was successful.", - m_currentConnection->hostName, m_currentConnection->port), - i18n("Connection Successful")); + const QString& hostName = m_connections[ui.lwConnections->currentRow()].hostName; + KMessageBox::information(this, i18n("Connection to the broker '%1' was successful.", hostName), + i18n("Connection Successful")); m_client->disconnectFromHost(); } @@ -688,9 +649,9 @@ RESET_CURSOR; m_testTimer->stop(); - KMessageBox::error(this, i18n("Failed to connect to the broker '%1:%2'.", - m_currentConnection->hostName, m_currentConnection->port), - i18n("Connection Failed")); + const QString& hostName = m_connections[ui.lwConnections->currentRow()].hostName; + KMessageBox::error(this, i18n("Failed to connect to the broker '%1'.", hostName), + i18n("Connection Failed")); m_client->disconnectFromHost(); } @@ -701,9 +662,9 @@ void MQTTConnectionManagerWidget::onDisconnect() { RESET_CURSOR; if (m_testTimer->isActive()) { - KMessageBox::error(this, i18n("Disconnected from the broker '%1:%2' before the connection was successful.", - m_currentConnection->hostName, m_currentConnection->port), - i18n("Connection Failed")); + const QString& hostName = m_connections[ui.lwConnections->currentRow()].hostName; + KMessageBox::error(this, i18n("Disconnected from the broker '%1' before the connection was successful.", hostName), + i18n("Connection Failed")); m_testTimer->stop(); } } diff --git a/src/kdefrontend/datasources/MQTTErrorWidget.cpp b/src/kdefrontend/datasources/MQTTErrorWidget.cpp --- a/src/kdefrontend/datasources/MQTTErrorWidget.cpp +++ b/src/kdefrontend/datasources/MQTTErrorWidget.cpp @@ -75,7 +75,6 @@ case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ProtocolViolation: - case QMqttClient::Mqtt5SpecificError: close = true; break; default: @@ -121,7 +120,6 @@ case QMqttClient::ServerUnavailable: case QMqttClient::UnknownError: case QMqttClient::ProtocolViolation: - case QMqttClient::Mqtt5SpecificError: break; default: break; diff --git a/src/kdefrontend/datasources/MQTTSubscriptionWidget.h b/src/kdefrontend/datasources/MQTTSubscriptionWidget.h deleted file mode 100644 --- a/src/kdefrontend/datasources/MQTTSubscriptionWidget.h +++ /dev/null @@ -1,112 +0,0 @@ -/*************************************************************************** -File : MQTTSubscriptionWidget.h -Project : LabPlot -Description : manage topics and subscribing --------------------------------------------------------------------- -Copyright : (C) 2019 by Kovacs Ferencz (kferike98@gmail.com) -***************************************************************************/ - -/*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -* This program is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* GNU General Public License for more details. * -* * -* You should have received a copy of the GNU General Public License * -* along with this program; if not, write to the Free Software * -* Foundation, Inc., 51 Franklin Street, Fifth Floor, * -* Boston, MA 02110-1301 USA * -* * -***************************************************************************/ - -#ifndef MQTTSUBSCRIPTIONWIDGET_H -#define MQTTSUBSCRIPTIONWIDGET_H - -#include -#include - -#ifdef HAVE_MQTT -#include "ui_mqttsubscriptionwidget.h" -class QMqttSubscription; -#endif - -class MQTTSubscriptionWidget : public QWidget { -#ifdef HAVE_MQTT - Q_OBJECT - -public: - explicit MQTTSubscriptionWidget(QWidget* parent = nullptr); - ~MQTTSubscriptionWidget() override; - enum MQTTParentWidget { - ImportFileWidget = 0, - LiveDataDock = 1 - }; - - void setTopicList (QStringList topicList); - QStringList getTopicList(); - - int subscriptionCount(); - QTreeWidgetItem* topLevelTopic(int); - QTreeWidgetItem* topLevelSubscription(int); - QTreeWidgetItem* currentItem() const; - void addTopic(QTreeWidgetItem*); - int topicCount(); - void setTopicTreeText(const QString&); - void makeVisible(bool); - void testSubscribe(QTreeWidgetItem*); - void testUnsubscribe(QTreeWidgetItem*); - - static bool checkTopicContains(const QString&, const QString&); - static void findSubscriptionLeafChildren(QVector&, QTreeWidgetItem*); - -signals: - void subscriptionChanged(); - void makeSubscription(const QString& name, quint8 QoS); - void MQTTUnsubscribeFromTopic(const QString&, QVector children); - void removeMQTTSubscription(const QString&); - void addBeforeRemoveSubscription(const QString&, quint8); - void reparentTopic(const QString& topic, const QString& parent); - void enableWill(bool); - -private: - Ui::MQTTSubscriptionWidget ui; - MQTTParentWidget m_parent; - QWidget* m_parentWidget; - QCompleter* m_subscriptionCompleter{nullptr}; - QCompleter* m_topicCompleter{nullptr}; - QStringList m_topicList; - bool m_searching{false}; - QTimer* m_searchTimer; - - void unsubscribeFromTopic(const QString&); - void manageCommonLevelSubscriptions(); - void updateSubscriptionCompleter(); - - static void addSubscriptionChildren(QTreeWidgetItem*, QTreeWidgetItem*); - static void restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level); - static int checkCommonChildCount(int levelIdx, int level, QStringList& namelist, QTreeWidgetItem* currentItem); - static int commonLevelIndex(const QString& first, const QString& second); - static QString checkCommonLevel(const QString&, const QString&); - -private slots: - void mqttAvailableTopicDoubleClicked(QTreeWidgetItem* item, int column); - void mqttSubscribedTopicDoubleClicked(QTreeWidgetItem* item, int column); - void mqttSubscribe(); - void mqttUnsubscribe(); - void setTopicCompleter(const QString&); - void scrollToTopicTreeItem(const QString& rootName); - void scrollToSubsriptionTreeItem(const QString& rootName); - void topicTimeout(); - void updateSubscriptionTree(const QVector&); - void clearWidgets(); - void onDisconnect(); -#endif // HAVE_MQTT -}; - -#endif // MQTTSUBSCRIPTIONWIDGET_H diff --git a/src/kdefrontend/datasources/MQTTSubscriptionWidget.cpp b/src/kdefrontend/datasources/MQTTSubscriptionWidget.cpp deleted file mode 100644 --- a/src/kdefrontend/datasources/MQTTSubscriptionWidget.cpp +++ /dev/null @@ -1,1018 +0,0 @@ -/*************************************************************************** -File : MQTTSubscriptionWidget.cpp -Project : LabPlot -Description : Widget for managing topics and subscribing --------------------------------------------------------------------- -Copyright : (C) 2019 by Kovacs Ferencz (kferike98@gmail.com) -***************************************************************************/ - -/*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -* This program is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* GNU General Public License for more details. * -* * -* You should have received a copy of the GNU General Public License * -* along with this program; if not, write to the Free Software * -* Foundation, Inc., 51 Franklin Street, Fifth Floor, * -* Boston, MA 02110-1301 USA * -* * -***************************************************************************/ - - -#include "MQTTSubscriptionWidget.h" - -#ifdef HAVE_MQTT -#include "backend/datasources/MQTTClient.h" -#include "ImportFileWidget.h" -#include "kdefrontend/dockwidgets/LiveDataDock.h" - -#include -#include -#include -#include -#include -#include - -/*! - \class MQTTSubscriptionWidget - \brief Widget for managing topics and subscribing. - - \ingroup kdefrontend -*/ -MQTTSubscriptionWidget::MQTTSubscriptionWidget( QWidget* parent): QWidget(parent), - m_parentWidget(parent), - m_searchTimer(new QTimer(this)) { - if(dynamic_cast(parent) != nullptr) - m_parent = MQTTParentWidget::ImportFileWidget; - else - m_parent = MQTTParentWidget::LiveDataDock; - - ui.setupUi(this); - - m_searchTimer->setInterval(10000); - const int size = ui.leTopics->height(); - ui.lTopicSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) ); - ui.lSubscriptionSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) ); - ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); - ui.bSubscribe->setToolTip(i18n("Subscribe selected topics")); - ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_ArrowLeft)); - ui.bUnsubscribe->setToolTip(i18n("Unsubscribe selected topics")); - - //subscribe/unsubscribe buttons only enabled if something was selected - ui.bSubscribe->setEnabled(false); - ui.bUnsubscribe->setEnabled(false); - - QString info = i18n("Enter the name of the topic to navigate to it."); - QString placeholder = i18n("Enter the name of the topic"); - ui.lTopicSearch->setToolTip(info); - ui.leTopics->setToolTip(info); - ui.leTopics->setPlaceholderText(placeholder); - ui.lSubscriptionSearch->setToolTip(info); - ui.leSubscriptions->setToolTip(info); - ui.leSubscriptions->setPlaceholderText(placeholder); - - info = i18n("Set the Quality of Service (QoS) for the subscription to define the guarantee of the message delivery:" - "
    " - "
  • 0 - deliver at most once
  • " - "
  • 1 - deliver at least once
  • " - "
  • 2 - deliver exactly once
  • " - "
"); - ui.cbQos->setToolTip(info); - - if(m_parent == MQTTParentWidget::ImportFileWidget) { - connect(dynamic_cast(m_parentWidget), &ImportFileWidget::newTopic, this, &MQTTSubscriptionWidget::setTopicCompleter); - connect(dynamic_cast(m_parentWidget), &ImportFileWidget::updateSubscriptionTree, this, &MQTTSubscriptionWidget::updateSubscriptionTree); - connect(dynamic_cast(m_parentWidget), &ImportFileWidget::MQTTClearTopics, this, &MQTTSubscriptionWidget::clearWidgets); - } else { - connect(dynamic_cast(m_parentWidget), &LiveDataDock::MQTTClearTopics, this, &MQTTSubscriptionWidget::clearWidgets); - connect(dynamic_cast(m_parentWidget), &LiveDataDock::newTopic, this, &MQTTSubscriptionWidget::setTopicCompleter); - connect(dynamic_cast(m_parentWidget), &LiveDataDock::updateSubscriptionTree, this, &MQTTSubscriptionWidget::updateSubscriptionTree); - } - - connect(ui.bSubscribe, &QPushButton::clicked, this, &MQTTSubscriptionWidget::mqttSubscribe); - connect(ui.bUnsubscribe, &QPushButton::clicked, this,&MQTTSubscriptionWidget::mqttUnsubscribe); - - connect(m_searchTimer, &QTimer::timeout, this, &MQTTSubscriptionWidget::topicTimeout); - connect(ui.leTopics, &QLineEdit::textChanged, this, &MQTTSubscriptionWidget::scrollToTopicTreeItem); - connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &MQTTSubscriptionWidget::scrollToSubsriptionTreeItem); - connect(ui.twTopics, &QTreeWidget::itemDoubleClicked, this, &MQTTSubscriptionWidget::mqttAvailableTopicDoubleClicked); - connect(ui.twSubscriptions, &QTreeWidget::itemDoubleClicked, this, &MQTTSubscriptionWidget::mqttSubscribedTopicDoubleClicked); - connect(ui.twSubscriptions, &QTreeWidget::currentItemChanged, this, &MQTTSubscriptionWidget::subscriptionChanged); - - connect(ui.twTopics, &QTreeWidget::itemSelectionChanged, this, [=]() { - ui.bSubscribe->setEnabled(!ui.twTopics->selectedItems().isEmpty()); - }); - - connect(ui.twSubscriptions, &QTreeWidget::itemSelectionChanged, this, [=]() { - ui.bUnsubscribe->setEnabled(!ui.twSubscriptions->selectedItems().isEmpty()); - }); -} - -MQTTSubscriptionWidget::~MQTTSubscriptionWidget() { - m_searchTimer->stop(); - delete m_searchTimer; -} - -void MQTTSubscriptionWidget::setTopicList(QStringList topicList) { - m_topicList = topicList; -} - -QStringList MQTTSubscriptionWidget::getTopicList() { - return m_topicList; -} - -int MQTTSubscriptionWidget::subscriptionCount() { - return ui.twSubscriptions->topLevelItemCount(); -} - -QTreeWidgetItem* MQTTSubscriptionWidget::topLevelTopic(int index) { - return ui.twTopics->topLevelItem(index); -} - -QTreeWidgetItem* MQTTSubscriptionWidget::topLevelSubscription(int index) { - return ui.twSubscriptions->topLevelItem(index); -} - -void MQTTSubscriptionWidget::addTopic(QTreeWidgetItem* item) { - ui.twTopics->addTopLevelItem(item); -} - -int MQTTSubscriptionWidget::topicCount() { - return ui.twTopics->topLevelItemCount(); -} - -void MQTTSubscriptionWidget::setTopicTreeText(const QString &text) { - ui.twTopics->headerItem()->setText(0, text); -} - -QTreeWidgetItem* MQTTSubscriptionWidget::currentItem() const { - return ui.twSubscriptions->currentItem(); -} - -void MQTTSubscriptionWidget::makeVisible(bool visible) { - ui.cbQos->setVisible(visible); - ui.twTopics->setVisible(visible); - ui.twSubscriptions->setVisible(visible); - ui.leTopics->setVisible(visible); - ui.leSubscriptions->setVisible(visible); - ui.bSubscribe->setVisible(visible); - ui.bUnsubscribe->setVisible(visible); - ui.lTopicSearch->setVisible(visible); - ui.lSubscriptionSearch->setVisible(visible); -} - -void MQTTSubscriptionWidget::testSubscribe(QTreeWidgetItem *item) { - ui.twTopics->setCurrentItem(item); - mqttSubscribe(); -} - -void MQTTSubscriptionWidget::testUnsubscribe(QTreeWidgetItem *item) { - ui.twTopics->setCurrentItem(item); - mqttUnsubscribe(); -} - -/*! - *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) - * - * \param children vector of TreeWidgetItem pointers - * \param root pointer to a TreeWidgetItem of twSubscriptions - */ -void MQTTSubscriptionWidget::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { - if (root->childCount() == 0) - children.push_back(root); - else - for (int i = 0; i < root->childCount(); ++i) - findSubscriptionLeafChildren(children, root->child(i)); -} - -/*! - *\brief Checks if a topic contains another one - * - * \param superior the name of a topic - * \param inferior the name of a topic - * \return true if superior is equal to or contains(if superior contains wildcards) inferior, - * false otherwise - */ -bool MQTTSubscriptionWidget::checkTopicContains(const QString& superior, const QString& inferior) { - if (superior == inferior) - return true; - - if (!superior.contains('/')) - return false; - - const QStringList& superiorList = superior.split('/', QString::SkipEmptyParts); - const QStringList& inferiorList = inferior.split('/', QString::SkipEmptyParts); - - //a longer topic can't contain a shorter one - if (superiorList.size() > inferiorList.size()) - return false; - - bool ok = true; - for (int i = 0; i < superiorList.size(); ++i) { - if (superiorList.at(i) != inferiorList.at(i)) { - if ((superiorList.at(i) != "+") && - !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { - //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) - //then superior can't contain inferior - ok = false; - break; - } else if (i == superiorList.size() - 1 && (superiorList.at(i) == "+" && inferiorList.at(i) == "#") ) { - //if the two topics differ at the last level - //and the superior's current level is + while the inferior's is #(which can be only in the last position) - //then superior can't contain inferior - ok = false; - break; - } - } - } - return ok; -} - -/*! - *\brief Starts unsubscribing from the given topic, and signals to ImportFileWidget for further actions - * - * \param topicName the name of a topic we want to unsubscribe from - */ -void MQTTSubscriptionWidget::unsubscribeFromTopic(const QString& topicName) { - if (topicName.isEmpty()) - return; - - QVector children; - findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(0)); - - //signals for ImportFileWidget - emit MQTTUnsubscribeFromTopic(topicName, children); - - for (int row = 0; row < ui.twSubscriptions->topLevelItemCount(); row++) { - if (ui.twSubscriptions->topLevelItem(row)->text(0) == topicName) { - ui.twSubscriptions->topLevelItem(row)->takeChildren(); - ui.twSubscriptions->takeTopLevelItem(row); - } - } -} - -/*! - *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. - * We do this until there are no topics to merge - */ -void MQTTSubscriptionWidget::manageCommonLevelSubscriptions() { - bool foundEqual = false; - - do { - foundEqual = false; - QMap> equalTopicsMap; - QVector equalTopics; - - //compare the subscriptions present in the TreeWidget - for (int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { - for (int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { - QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); - - //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) - if (!commonTopic.isEmpty()) { - if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) - equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); - - if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) - equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); - } - } - } - - if (!equalTopicsMap.isEmpty()) { - DEBUG("Manage common topics"); - - QVector commonTopics; - QMapIterator> topics(equalTopicsMap); - - //check for every map entry, if the found topics can be merged or not - while (topics.hasNext()) { - topics.next(); - - int level = commonLevelIndex(topics.value().last(), topics.value().first()); - QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); - QTreeWidgetItem* currentItem = nullptr; - - //search the corresponding item to the common topics first level(root) - for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { - if (ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { - currentItem = ui.twTopics->topLevelItem(i); - break; - } - } - - if (!currentItem) - break; - - //calculate the number of topics the new + wildcard could replace - int childCount = checkCommonChildCount(1, level, commonList, currentItem); - if (childCount > 0) { - //if the number of topics found and the calculated number of topics is equal, the topics can be merged - if (topics.value().size() == childCount) { - QDEBUG("Found common topic to manage: " << topics.key()); - foundEqual = true; - commonTopics.push_back(topics.key()); - } - } - } - - if (foundEqual) { - //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new '+' wildcard - int lowestLevel = INT_MAX; - int topicIdx = -1; - for (int i = 0; i < commonTopics.size(); ++i) { - int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); - if (level < lowestLevel) { - topicIdx = i; - lowestLevel = level; - } - } - QDEBUG("Manage: " << commonTopics[topicIdx]); - equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); - - //Add the common topic ("merging") - QString commonTopic; - commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); - QStringList nameList; - nameList.append(commonTopic); - QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); - ui.twSubscriptions->addTopLevelItem(newTopic); - - if(m_parent == MQTTParentWidget::ImportFileWidget) - emit makeSubscription(commonTopic, static_cast (ui.cbQos->currentText().toUInt())); - - //remove the "merged" topics - for (int i = 0; i < equalTopics.size(); ++i) { - for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { - if (ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { - newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); - - if(m_parent == MQTTParentWidget::ImportFileWidget) - unsubscribeFromTopic(equalTopics[i]); - - break; - } - } - } - - //remove any subscription that the new subscription contains - for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - if (checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && - commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { - if(m_parent == MQTTParentWidget::ImportFileWidget) - unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); - else { - ui.twSubscriptions->topLevelItem(i)->takeChildren(); - ui.twSubscriptions->takeTopLevelItem(i); - } - i--; - } - } - - if(m_parent == MQTTParentWidget::LiveDataDock) - emit makeSubscription(commonTopic, static_cast (ui.cbQos->currentText().toUInt())); - } - } - } while (foundEqual); -} - -/*! - *\brief Fills twSubscriptions with the subscriptions made by the client - */ -void MQTTSubscriptionWidget::updateSubscriptionTree(const QVector& mqttSubscriptions) { - DEBUG("ImportFileWidget::updateSubscriptionTree()"); - ui.twSubscriptions->clear(); - - for (int i = 0; i < mqttSubscriptions.size(); ++i) { - QStringList name; - name.append(mqttSubscriptions[i]); - - bool found = false; - for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { - if (ui.twSubscriptions->topLevelItem(j)->text(0) == mqttSubscriptions[i]) { - found = true; - break; - } - } - - if (!found) { - //Add the subscription to the tree widget - QTreeWidgetItem* newItem = new QTreeWidgetItem(name); - ui.twSubscriptions->addTopLevelItem(newItem); - name.clear(); - name = mqttSubscriptions[i].split('/', QString::SkipEmptyParts); - - //find the corresponding "root" item in twTopics - QTreeWidgetItem* topic = nullptr; - for (int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { - if (ui.twTopics->topLevelItem(j)->text(0) == name[0]) { - topic = ui.twTopics->topLevelItem(j); - break; - } - } - - //restore the children of the subscription - if (topic != nullptr && topic->childCount() > 0) - restoreSubscriptionChildren(topic, newItem, name, 1); - } - } - m_searching = false; -} - -/*! - *\brief Adds to a # wildcard containing topic, every topic present in twTopics that the former topic contains - * - * \param topic pointer to the TreeWidgetItem which was selected before subscribing - * \param subscription pointer to the TreeWidgetItem which represents the new subscirption, - * we add all of the children to this item - */ -void MQTTSubscriptionWidget::addSubscriptionChildren(QTreeWidgetItem* topic, QTreeWidgetItem* subscription) { - //if the topic doesn't have any children we don't do anything - if (topic->childCount() <= 0) - return; - - for (int i = 0; i < topic->childCount(); ++i) { - QTreeWidgetItem* temp = topic->child(i); - QString name; - //if it has children, then we add it as a # wildcrad containing topic - if (topic->child(i)->childCount() > 0) { - name.append(temp->text(0) + "/#"); - while (temp->parent() != nullptr) { - temp = temp->parent(); - name.prepend(temp->text(0) + '/'); - } - } - - //if not then we simply add the topic itself - else { - name.append(temp->text(0)); - while (temp->parent() != nullptr) { - temp = temp->parent(); - name.prepend(temp->text(0) + '/'); - } - } - - QStringList nameList; - nameList.append(name); - QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); - subscription->addChild(childItem); - //we use the function recursively on the given item - addSubscriptionChildren(topic->child(i), childItem); - } -} - -/*! - *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards - * - * \param topic pointer to a top level item in twTopics which represents the root of the subscription topic - * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored - * \param list QStringList containing the levels of the subscription topic - * \param level the level's number which is being investigated - */ -void MQTTSubscriptionWidget::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { - if (list[level] != "+" && list[level] != "#" && level < list.size() - 1) { - for (int i = 0; i < topic->childCount(); ++i) { - //if the current level isn't + or # wildcard we recursively continue with the next level - if (topic->child(i)->text(0) == list[level]) { - restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); - break; - } - } - } else if (list[level] == "+") { - for (int i = 0; i < topic->childCount(); ++i) { - //determine the name of the topic, contained by the subscription - QString name; - name.append(topic->child(i)->text(0)); - for (int j = level + 1; j < list.size(); ++j) - name.append('/' + list[j]); - - QTreeWidgetItem* temp = topic->child(i); - while (temp->parent() != nullptr) { - temp = temp->parent(); - name.prepend(temp->text(0) + '/'); - } - - //Add the topic as child of the subscription - QStringList nameList; - nameList.append(name); - QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); - subscription->addChild(newItem); - //Continue adding children recursively to the new item - restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); - } - } else if (list[level] == "#") { - //add the children of the # wildcard containing subscription - addSubscriptionChildren(topic, subscription); - } -} - -/*! - *\brief Returns the amount of topics that the '+' wildcard will replace in the level position - * - * \param levelIdx the level currently being investigated - * \param level the level where the new + wildcard will be placed - * \param commonList the topic name split into levels - * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level - * represented by levelIdx - * \return returns the childCount, or -1 if some topics already represented by + wildcard have different - * amount of children - */ -int MQTTSubscriptionWidget::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { - //we recursively check the number of children, until we get to level-1 - if (levelIdx < level - 1) { - if (commonList[levelIdx] != "+") { - for (int j = 0; j < currentItem->childCount(); ++j) { - if (currentItem->child(j)->text(0) == commonList[levelIdx]) { - //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively - return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); - } - } - } else { - int childCount = -1; - bool ok = true; - - //otherwise we check if every + wildcard represented topic has the same number of children, recursively - for (int j = 0; j < currentItem->childCount(); ++j) { - int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); - if ((j > 0) && (temp != childCount)) { - ok = false; - break; - } - childCount = temp; - } - - //if yes we return this number, otherwise -1 - if (ok) - return childCount; - else - return -1; - } - } else if (levelIdx == level - 1) { - if (commonList[levelIdx] != "+") { - for (int j = 0; j < currentItem->childCount(); ++j) { - if (currentItem->child(j)->text(0) == commonList[levelIdx]) { - //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item - return currentItem->child(j)->childCount(); - } - } - } else { - int childCount = -1; - bool ok = true; - - //otherwise we check if every + wildcard represented topic has the same number of children - for (int j = 0; j < currentItem->childCount(); ++j) { - if ((j > 0) && (currentItem->child(j)->childCount() != childCount)) { - ok = false; - break; - } - childCount = currentItem->child(j)->childCount(); - } - - //if yes we return this number, otherwise -1 - if (ok) - return childCount; - else - return -1; - } - - } else if (level == 1 && levelIdx == 1) - return currentItem->childCount(); - - return -1; -} - - -/*! - *\brief Returns the index of level where the two topic names differ, if there is a common topic for them - * - * \param first the name of a topic - * \param second the name of a topic - * \return The index of the unequal level, if there is a common topic, otherwise -1 - */ -int MQTTSubscriptionWidget::commonLevelIndex(const QString& first, const QString& second) { - QStringList firstList = first.split('/', QString::SkipEmptyParts); - QStringList secondtList = second.split('/', QString::SkipEmptyParts); - QString commonTopic; - int differIndex = -1; - - if (!firstList.isEmpty()) { - //the two topics have to be the same size and can't be identic - if (firstList.size() == secondtList.size() && (first != second)) { - - //the index where they differ - for (int i = 0; i < firstList.size(); ++i) { - if (firstList.at(i) != secondtList.at(i)) { - differIndex = i; - break; - } - } - - //they can differ at only one level - bool differ = false; - if (differIndex > 0) { - for (int j = differIndex + 1; j < firstList.size(); ++j) { - if (firstList.at(j) != secondtList.at(j)) { - differ = true; - break; - } - } - } else - differ = true; - - if (!differ) { - for (int i = 0; i < firstList.size(); ++i) { - if (i != differIndex) - commonTopic.append(firstList.at(i)); - else - commonTopic.append('+'); - - if (i != firstList.size() - 1) - commonTopic.append('/'); - } - } - } - } - - //if there is a common topic we return the differIndex - if (!commonTopic.isEmpty()) - return differIndex; - else - return -1; -} - -/*! - *\brief Returns the '+' wildcard containing topic name, which includes the given topic names - * - * \param first the name of a topic - * \param second the name of a topic - * \return The name of the common topic, if it exists, otherwise "" - */ -QString MQTTSubscriptionWidget::checkCommonLevel(const QString& first, const QString& second) { - const QStringList& firstList = first.split('/', QString::SkipEmptyParts); - if (firstList.isEmpty()) - return QString(); - - const QStringList& secondtList = second.split('/', QString::SkipEmptyParts); - QString commonTopic; - - //the two topics have to be the same size and can't be identic - if (firstList.size() == secondtList.size() && (first != second)) { - - //the index where they differ - int differIndex = -1; - for (int i = 0; i < firstList.size(); ++i) { - if (firstList.at(i) != secondtList.at(i)) { - differIndex = i; - break; - } - } - - //they can differ at only one level - bool differ = false; - if (differIndex > 0) { - for (int j = differIndex + 1; j < firstList.size(); ++j) { - if (firstList.at(j) != secondtList.at(j)) { - differ = true; - break; - } - } - } else - differ = true; - - if (!differ) { - for (int i = 0; i < firstList.size(); ++i) { - if (i != differIndex) - commonTopic.append(firstList.at(i)); - else { - //we put '+' wildcard at the level where they differ - commonTopic.append('+'); - } - - if (i != firstList.size() - 1) - commonTopic.append('/'); - } - } - } - -// qDebug() << "Common topic for " << first << " and " << second << " is: " << commonTopic; - return commonTopic; -} - -/************** SLOTS **************************************************************/ - -/*! - *\brief When a leaf topic is double clicked in the topics tree widget we subscribe on that - */ -void MQTTSubscriptionWidget::mqttAvailableTopicDoubleClicked(QTreeWidgetItem* item, int column) { - Q_UNUSED(column) - // Only for leaf topics - if (item->childCount() == 0) - mqttSubscribe(); -} - -/*! - *\brief When a leaf subscription is double clicked in the topics tree widget we unsubscribe - */ -void MQTTSubscriptionWidget::mqttSubscribedTopicDoubleClicked(QTreeWidgetItem* item, int column) { - Q_UNUSED(column) - // Only for leaf subscriptions - if (item->childCount() == 0) - mqttUnsubscribe(); -} - -/*! - *\brief called when the subscribe button is pressed - * subscribes to the topic represented by the current item of twTopics - */ -void MQTTSubscriptionWidget::mqttSubscribe() { - QTreeWidgetItem* item = ui.twTopics->currentItem(); - if (!item) - return; //should never happen - - //determine the topic name that the current item represents - QTreeWidgetItem* tempItem = item; - QString name = item->text(0); - if (item->childCount() != 0) - name.append("/#"); - - while (tempItem->parent()) { - tempItem = tempItem->parent(); - name.prepend(tempItem->text(0) + '/'); - } - - //check if the subscription already exists - const QList& topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); - if (topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { - QDEBUG("Subscribe to: " << name); - bool foundSuperior = false; - - for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - //if the new subscirptions contains an already existing one, we remove the inferior one - if (checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) - && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { - if(m_parent == MQTTParentWidget::ImportFileWidget) - unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); - else { - ui.twSubscriptions->topLevelItem(i)->takeChildren(); - ui.twSubscriptions->takeTopLevelItem(i); - } - --i; - continue; - } - - //if there is a subscription containing the new one we set foundSuperior true - if (checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) - && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { - foundSuperior = true; - QDEBUG("Can't continue subscribing. Found superior for " << name << " : " << ui.twSubscriptions->topLevelItem(i)->text(0)); - break; - } - } - - //if there wasn't a superior subscription we can subscribe to the new topic - if (!foundSuperior) { - QStringList toplevelName; - toplevelName.push_back(name); - QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); - ui.twSubscriptions->addTopLevelItem(newTopLevelItem); - - if (name.endsWith('#')) { - //adding every topic that the subscription contains to twSubscriptions - addSubscriptionChildren(item, newTopLevelItem); - } - - emit makeSubscription(name, static_cast(ui.cbQos->currentText().toUInt())); - - if (name.endsWith('#')) { - //if an already existing subscription contains a topic that the new subscription also contains - //we decompose the already existing subscription - //by unsubscribing from its topics, that are present in the new subscription as well - const QStringList nameList = name.split('/', QString::SkipEmptyParts); - const QString& root = nameList.first(); - QVector children; - for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - if (ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) - && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { - children.clear(); - //get the "leaf" children of the inspected subscription - findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); - for (int j = 0; j < children.size(); ++j) { - if (checkTopicContains(name, children[j]->text(0))) { - //if the new subscription contains a topic, we unsubscribe from it - if(m_parent == MQTTParentWidget::ImportFileWidget) { - ui.twSubscriptions->setCurrentItem(children[j]); - mqttUnsubscribe(); - --i; - } else { - QTreeWidgetItem* unsubscribeItem = children[j]; - while (unsubscribeItem->parent() != nullptr) { - for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { - const QString& childText = unsubscribeItem->parent()->child(i)->text(0); - if (unsubscribeItem->text(0) != childText) { - //add topic as subscription - quint8 qos = static_cast(ui.cbQos->currentText().toUInt()); - emit addBeforeRemoveSubscription(childText, qos); - //also add it to twSubscriptions - ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); - --i; - } else { - //before we remove the topic, we reparent it to the new subscription - //so no data is lost - emit reparentTopic(unsubscribeItem->text(0), name); - } - } - unsubscribeItem = unsubscribeItem->parent(); - } - - qDebug()<<"Remove: "<text(0); - emit removeMQTTSubscription(unsubscribeItem->text(0)); - - ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); - } - } - } - } - } - } - - - //implementalj es ird at liveDataDock addsubscription!!!!! - manageCommonLevelSubscriptions(); - updateSubscriptionCompleter(); - - emit enableWill(true); - } else - QMessageBox::warning(this, i18n("Warning"), i18n("You already subscribed to a topic containing this one")); - } else - QMessageBox::warning(this, i18n("Warning"), i18n("You already subscribed to this topic")); -} - -/*! - *\brief Updates the completer for leSubscriptions - */ -void MQTTSubscriptionWidget::updateSubscriptionCompleter() { - QStringList subscriptionList; - for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) - subscriptionList.append(ui.twSubscriptions->topLevelItem(i)->text(0)); - - if (!subscriptionList.isEmpty()) { - m_subscriptionCompleter = new QCompleter(subscriptionList, this); - m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); - m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); - ui.leSubscriptions->setCompleter(m_subscriptionCompleter); - } else - ui.leSubscriptions->setCompleter(nullptr); -} - - -/*! - *\brief called when the unsubscribe button is pressed - * unsubscribes from the topic represented by the current item of twSubscription - */ -void MQTTSubscriptionWidget::mqttUnsubscribe() { - QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); - if (!unsubscribeItem) - return; //should never happen - - QDEBUG("Unsubscribe from: " << unsubscribeItem->text(0)); - //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) - //we can simply unsubscribe from it - if (unsubscribeItem->parent() == nullptr) { - if(m_parent == MQTTParentWidget::ImportFileWidget) - unsubscribeFromTopic(unsubscribeItem->text(0)); - else { - emit removeMQTTSubscription(unsubscribeItem->text(0)); - ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); - } - } - - //otherwise we remove the selected item, but subscribe to every other topic, that was contained by - //the selected item's parent subscription(top level item of twSubscriptions) - else { - while (unsubscribeItem->parent() != nullptr) { - for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { - const QString& childText = unsubscribeItem->parent()->child(i)->text(0); - if (unsubscribeItem->text(0) != childText) { - quint8 qos = static_cast(ui.cbQos->currentText().toUInt()); - if(m_parent == MQTTParentWidget::ImportFileWidget) - emit makeSubscription(childText, qos); - else - emit addBeforeRemoveSubscription(childText, qos); - - ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); - --i; - } - } - unsubscribeItem = unsubscribeItem->parent(); - } - - if(m_parent == MQTTParentWidget::ImportFileWidget) - unsubscribeFromTopic(unsubscribeItem->text(0)); - else { - emit removeMQTTSubscription(unsubscribeItem->text(0)); - ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); - } - - //check if any common topics were subscribed, if possible merge them - manageCommonLevelSubscriptions(); - } - updateSubscriptionCompleter(); - - if (ui.twSubscriptions->topLevelItemCount() <= 0) - emit enableWill(false); -} - -/*! - *\brief called when a new topic is added to the tree(twTopics) - * appends the topic's root to the topicList if it isn't in the list already - * then sets the completer for leTopics - */ -void MQTTSubscriptionWidget::setTopicCompleter(const QString& topic) { - if (!m_searching) { - const QStringList& list = topic.split('/', QString::SkipEmptyParts); - QString tempTopic; - if (!list.isEmpty()) - tempTopic = list.at(0); - else - tempTopic = topic; - - if (!m_topicList.contains(tempTopic)) { - m_topicList.append(tempTopic); - m_topicCompleter = new QCompleter(m_topicList, this); - m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); - m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); - ui.leTopics->setCompleter(m_topicCompleter); - } - } -} - -/*! - *\brief called when leTopics' text is changed - * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget - * - * \param rootName the current text of leTopics - */ -void MQTTSubscriptionWidget::scrollToTopicTreeItem(const QString& rootName) { - m_searching = true; - m_searchTimer->start(); - - int topItemIdx = -1; - for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) - if (ui.twTopics->topLevelItem(i)->text(0) == rootName) { - topItemIdx = i; - break; - } - - if (topItemIdx >= 0) - ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), - QAbstractItemView::ScrollHint::PositionAtTop); -} - -/*! - *\brief called when leSubscriptions' text is changed - * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget - * - * \param rootName the current text of leSubscriptions - */ -void MQTTSubscriptionWidget::scrollToSubsriptionTreeItem(const QString& rootName) { - int topItemIdx = -1; - for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) - if (ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { - topItemIdx = i; - break; - } - - if (topItemIdx >= 0) - ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), - QAbstractItemView::ScrollHint::PositionAtTop); -} - -/*! - *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics - * enables updating the completer for le - */ -void MQTTSubscriptionWidget::topicTimeout() { - m_searching = false; - m_searchTimer->stop(); -} - -void MQTTSubscriptionWidget::clearWidgets() { - ui.twTopics->clear(); - ui.twSubscriptions->clear(); - ui.twTopics->headerItem()->setText(0, i18n("Available")); -} - -void MQTTSubscriptionWidget::onDisconnect() { - m_searchTimer->stop(); - m_searching = false; - delete m_topicCompleter; - delete m_subscriptionCompleter; -} -#endif diff --git a/src/kdefrontend/dockwidgets/AxisDock.h b/src/kdefrontend/dockwidgets/AxisDock.h --- a/src/kdefrontend/dockwidgets/AxisDock.h +++ b/src/kdefrontend/dockwidgets/AxisDock.h @@ -38,7 +38,6 @@ class TreeViewComboBox; class AspectTreeModel; class AbstractColumn; -class DateTimeSpinBox; class AxisDock : public QWidget{ Q_OBJECT @@ -67,13 +66,6 @@ void load(); void loadConfig(KConfig&); - // own created widgets - DateTimeSpinBox* dtsbMajorTicksIncrement {nullptr}; - DateTimeSpinBox* dtsbMinorTicksIncrement {nullptr}; - - int determineDecimals(double diff); - double determineStep(double diff, int decimal); - private slots: void init(); diff --git a/src/kdefrontend/dockwidgets/AxisDock.cpp b/src/kdefrontend/dockwidgets/AxisDock.cpp --- a/src/kdefrontend/dockwidgets/AxisDock.cpp +++ b/src/kdefrontend/dockwidgets/AxisDock.cpp @@ -37,7 +37,6 @@ #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/widgets/LabelWidget.h" -#include "commonfrontend/widgets/DateTimeSpinBox.h" #include #include @@ -66,13 +65,10 @@ //"Ticks"-tab auto* layout = static_cast(ui.tabTicks->layout()); cbMajorTicksColumn = new TreeViewComboBox(ui.tabTicks); - layout->addWidget(cbMajorTicksColumn, 7, 2); + layout->addWidget(cbMajorTicksColumn, 5, 2); + cbMinorTicksColumn = new TreeViewComboBox(ui.tabTicks); - layout->addWidget(cbMinorTicksColumn, 21, 2); - dtsbMajorTicksIncrement = new DateTimeSpinBox(ui.tabTicks); - layout->addWidget(dtsbMajorTicksIncrement, 6, 2); - dtsbMinorTicksIncrement = new DateTimeSpinBox(ui.tabTicks); - layout->addWidget(dtsbMinorTicksIncrement, 20, 2); + layout->addWidget(cbMinorTicksColumn, 18, 2); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { @@ -118,9 +114,7 @@ connect( ui.cbMajorTicksDirection, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksDirectionChanged(int)) ); connect( ui.cbMajorTicksType, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksTypeChanged(int)) ); connect( ui.sbMajorTicksNumber, SIGNAL(valueChanged(int)), this, SLOT(majorTicksNumberChanged(int)) ); - connect( ui.sbMajorTicksIncrementNumeric, SIGNAL(valueChanged(double)), this, SLOT(majorTicksIncrementChanged()) ); - connect( dtsbMajorTicksIncrement, SIGNAL(valueChanged()), this, SLOT(majorTicksIncrementChanged()) ); - //connect( ui.sbMajorTicksIncrementNumeric, &QDoubleSpinBox::valueChanged, this, &AxisDock::majorTicksIncrementChanged); + connect( ui.leMajorTicksIncrement, SIGNAL(textChanged(QString)), this, SLOT(majorTicksIncrementChanged()) ); connect( cbMajorTicksColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(majorTicksColumnChanged(QModelIndex)) ); connect( ui.cbMajorTicksLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksLineStyleChanged(int)) ); connect( ui.kcbMajorTicksColor, SIGNAL(changed(QColor)), this, SLOT(majorTicksColorChanged(QColor)) ); @@ -132,8 +126,7 @@ connect( ui.cbMinorTicksDirection, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksDirectionChanged(int)) ); connect( ui.cbMinorTicksType, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksTypeChanged(int)) ); connect( ui.sbMinorTicksNumber, SIGNAL(valueChanged(int)), this, SLOT(minorTicksNumberChanged(int)) ); - connect( ui.sbMajorTicksIncrementNumeric, SIGNAL(valueChanged(double)), this, SLOT(minorTicksIncrementChanged()) ); - connect( dtsbMinorTicksIncrement, SIGNAL(valueChanged()), this, SLOT(minorTicksIncrementChanged()) ); + connect( ui.leMinorTicksIncrement, SIGNAL(textChanged(QString)), this, SLOT(minorTicksIncrementChanged()) ); connect( cbMinorTicksColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(minorTicksColumnChanged(QModelIndex)) ); connect( ui.cbMinorTicksLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksLineStyleChanged(int)) ); connect( ui.kcbMinorTicksColor, SIGNAL(changed(QColor)), this, SLOT(minorTicksColorChanged(QColor)) ); @@ -193,6 +186,9 @@ ui.leZeroOffset->setValidator( new QDoubleValidator(ui.leZeroOffset) ); ui.leScalingFactor->setValidator( new QDoubleValidator(ui.leScalingFactor) ); + ui.leMajorTicksIncrement->setValidator( new QDoubleValidator(ui.leMajorTicksIncrement) ); + ui.leMinorTicksIncrement->setValidator( new QDoubleValidator(ui.leMinorTicksIncrement) ); + //TODO move this stuff to retranslateUI() ui.cbPosition->addItem(i18n("Top")); ui.cbPosition->addItem(i18n("Bottom")); @@ -382,8 +378,8 @@ ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); cbMajorTicksColumn->setCurrentModelIndex(QModelIndex()); cbMinorTicksColumn->setCurrentModelIndex(QModelIndex()); } @@ -793,13 +789,10 @@ ui.cbMajorTicksType->setEnabled(b); ui.lMajorTicksNumber->setEnabled(b); ui.sbMajorTicksNumber->setEnabled(b); - ui.lMajorTicksIncrementNumeric->setEnabled(b); - ui.sbMajorTicksIncrementNumeric->setEnabled(b); - ui.lMajorTicksIncrementDateTime->setEnabled(b); - dtsbMajorTicksIncrement->setEnabled(b); + ui.lMajorTicksIncrement->setEnabled(b); + ui.leMajorTicksIncrement->setEnabled(b); ui.lMajorTicksLineStyle->setEnabled(b); ui.cbMajorTicksLineStyle->setEnabled(b); - dtsbMinorTicksIncrement->setEnabled(b); if (b) { auto penStyle = Qt::PenStyle(ui.cbMajorTicksLineStyle->currentIndex()); b = (penStyle != Qt::NoPen); @@ -825,51 +818,26 @@ Shows/hides the corresponding widgets. */ void AxisDock::majorTicksTypeChanged(int index) { - if (!m_axis) // If elements are added to the combobox 'cbMajorTicksType' (at init of this class), then this function is called, which is a problem if no axis are available - return; - auto type = Axis::TicksType(index); - if (type == Axis::TicksTotalNumber) { + if ( type == Axis::TicksTotalNumber) { ui.lMajorTicksNumber->show(); ui.sbMajorTicksNumber->show(); - ui.lMajorTicksIncrementNumeric->hide(); - ui.sbMajorTicksIncrementNumeric->hide(); - ui.lMajorTicksIncrementDateTime->hide(); - dtsbMajorTicksIncrement->hide(); + ui.lMajorTicksIncrement->hide(); + ui.leMajorTicksIncrement->hide(); ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); - } else if (type == Axis::TicksIncrement) { + } else if ( type == Axis::TicksIncrement) { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); - ui.lMajorTicksIncrementNumeric->show(); - - const auto* plot = dynamic_cast(m_axis->parentAspect()); - bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - if (numeric) { - ui.lMajorTicksIncrementDateTime->hide(); - dtsbMajorTicksIncrement->hide(); - ui.lMajorTicksIncrementNumeric->show(); - ui.sbMajorTicksIncrementNumeric->show(); - } else { - ui.lMajorTicksIncrementDateTime->show(); - dtsbMajorTicksIncrement->show(); - ui.lMajorTicksIncrementNumeric->hide(); - ui.sbMajorTicksIncrementNumeric->hide(); - } - + ui.lMajorTicksIncrement->show(); + ui.leMajorTicksIncrement->show(); ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); - - // Check if Increment is not to small - majorTicksIncrementChanged(); } else { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); - ui.lMajorTicksIncrementNumeric->hide(); - ui.sbMajorTicksIncrementNumeric->hide(); - dtsbMajorTicksIncrement->hide(); - dtsbMajorTicksIncrement->hide(); + ui.lMajorTicksIncrement->hide(); + ui.leMajorTicksIncrement->hide(); ui.lMajorTicksColumn->show(); cbMajorTicksColumn->show(); } @@ -893,34 +861,8 @@ if (m_initializing) return; - const auto* plot = dynamic_cast(m_axis->parentAspect()); - - bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - - double value = numeric ? ui.sbMajorTicksIncrementNumeric->value() : dtsbMajorTicksIncrement->value(); - double diff = m_axis->end() - m_axis->start(); - - if (value == 0 || diff / value > 100 || value < 0) { // maximum of 100 ticks - - if (value == 0) - value = diff / ui.sbMajorTicksNumber->value(); - - if (diff / value > 100) - value = diff / 100; - - // determine stepsize and number of decimals - m_initializing = true; - if (numeric) { - int decimal = determineDecimals(value * 10); - ui.sbMajorTicksIncrementNumeric->setDecimals(decimal); - ui.sbMajorTicksIncrementNumeric->setSingleStep(determineStep(diff, decimal)); - ui.sbMajorTicksIncrementNumeric->setValue(value); - } else - dtsbMajorTicksIncrement->setValue(value); - m_initializing = false; - } - + double value = ui.leMajorTicksIncrement->text().toDouble(); + if (value < 0) value = -1.*value; //don't allow negative values for (auto* axis : m_axesList) axis->setMajorTicksIncrement(value); } @@ -1019,10 +961,8 @@ ui.cbMinorTicksType->setEnabled(b); ui.lMinorTicksNumber->setEnabled(b); ui.sbMinorTicksNumber->setEnabled(b); - ui.lMinorTicksIncrementNumeric->setEnabled(b); - ui.sbMinorTicksIncrementNumeric->setEnabled(b); - ui.lMinorTicksIncrementDateTime->setEnabled(b); - dtsbMinorTicksIncrement->setEnabled(b); + ui.lMinorTicksIncrement->setEnabled(b); + ui.leMinorTicksIncrement->setEnabled(b); ui.lMinorTicksLineStyle->setEnabled(b); ui.cbMinorTicksLineStyle->setEnabled(b); if (b) { @@ -1046,50 +986,26 @@ } void AxisDock::minorTicksTypeChanged(int index) { - if (!m_axis) // If elements are added to the combobox 'cbMajorTicksType' (at init of this class), then this function is called, which is a problem if no axis are available - return; - auto type = Axis::TicksType(index); - if (type == Axis::TicksTotalNumber) { + if ( type == Axis::TicksTotalNumber) { ui.lMinorTicksNumber->show(); ui.sbMinorTicksNumber->show(); - ui.lMinorTicksIncrementNumeric->hide(); - ui.sbMinorTicksIncrementNumeric->hide(); + ui.lMinorTicksIncrement->hide(); + ui.leMinorTicksIncrement->hide(); ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); - ui.lMinorTicksIncrementDateTime->hide(); - dtsbMinorTicksIncrement->hide(); } else if ( type == Axis::TicksIncrement) { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); - - const auto* plot = dynamic_cast(m_axis->parentAspect()); - bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - if (numeric) { - ui.lMinorTicksIncrementNumeric->show(); - ui.sbMinorTicksIncrementNumeric->show(); - ui.lMinorTicksIncrementDateTime->hide(); - dtsbMinorTicksIncrement->hide(); - } else { - ui.lMinorTicksIncrementNumeric->hide(); - ui.sbMinorTicksIncrementNumeric->hide(); - ui.lMinorTicksIncrementDateTime->show(); - dtsbMinorTicksIncrement->show(); - } - + ui.lMinorTicksIncrement->show(); + ui.leMinorTicksIncrement->show(); ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); - - // Check if Increment is not to small - minorTicksIncrementChanged(); } else { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); - ui.lMinorTicksIncrementNumeric->hide(); - ui.sbMinorTicksIncrementNumeric->hide(); - ui.lMinorTicksIncrementDateTime->hide(); - dtsbMinorTicksIncrement->hide(); + ui.lMinorTicksIncrement->hide(); + ui.leMinorTicksIncrement->hide(); ui.lMinorTicksColumn->show(); cbMinorTicksColumn->show(); } @@ -1113,38 +1029,8 @@ if (m_initializing) return; - const auto* plot = dynamic_cast(m_axis->parentAspect()); - - bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - - double value = numeric ? ui.sbMinorTicksIncrementNumeric->value() : dtsbMinorTicksIncrement->value(); - double numberTicks = 0.0; - - if (value > 0) - numberTicks = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / value -1; // recal - - if (value == 0 || numberTicks > 100 || value < 0) { - if (value == 0) - value = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / (ui.sbMinorTicksNumber->value() + 1); - - numberTicks = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / value -1; // recalculate number of ticks - - if (numberTicks > 100) // maximum 100 minor ticks - value = (m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1) / (100 + 1); - - // determine stepsize and number of decimals - m_initializing = true; - if (numeric) { - int decimal = determineDecimals(value * 10); - ui.sbMinorTicksIncrementNumeric->setDecimals(decimal); - ui.sbMinorTicksIncrementNumeric->setSingleStep(determineStep((m_axis->end() - m_axis->start()) / (m_axis->majorTicksNumber() - 1), decimal)); - ui.sbMinorTicksIncrementNumeric->setValue(value); - } else - dtsbMinorTicksIncrement->setValue(value); - m_initializing = false; - } - + double value = ui.leMinorTicksIncrement->text().toDouble(); + if (value < 0) value = -1. * value; //don't allow negative values for (auto* axis : m_axesList) axis->setMinorTicksIncrement(value); } @@ -1532,12 +1418,6 @@ m_initializing = true; ui.leStart->setText( QString::number(value) ); ui.dateTimeEditStart->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); - - // determine stepsize and number of decimals - double diff = m_axis->end() - m_axis->start(); - int decimal = determineDecimals(diff); - ui.sbMajorTicksIncrementNumeric->setDecimals(decimal); - ui.sbMajorTicksIncrementNumeric->setSingleStep(determineStep(diff, decimal)); m_initializing = false; } @@ -1545,13 +1425,6 @@ m_initializing = true; ui.leEnd->setText( QString::number(value) ); ui.dateTimeEditEnd->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); - ui.sbMajorTicksIncrementNumeric->setSingleStep(floor(m_axis->end() - m_axis->start())/10); - - // determine stepsize and number of decimals - double diff = m_axis->end() - m_axis->start(); - int decimal = determineDecimals(diff); - ui.sbMajorTicksIncrementNumeric->setDecimals(decimal); - ui.sbMajorTicksIncrementNumeric->setSingleStep(determineStep(diff, decimal)); m_initializing = false; } @@ -1619,17 +1492,7 @@ } void AxisDock::axisMajorTicksIncrementChanged(qreal increment) { m_initializing = true; - const auto* plot = dynamic_cast(m_axis->parentAspect()); - if (plot) { - bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - - if (numeric) - ui.sbMajorTicksIncrementNumeric->setValue(increment); - else { - dtsbMajorTicksIncrement->setValue(increment); - } - } + ui.leMajorTicksIncrement->setText( QString::number(increment)); m_initializing = false; } void AxisDock::axisMajorTicksPenChanged(const QPen& pen) { @@ -1668,17 +1531,7 @@ } void AxisDock::axisMinorTicksIncrementChanged(qreal increment) { m_initializing = true; - const auto* plot = dynamic_cast(m_axis->parentAspect()); - if (plot) { - bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - - if (numeric) - ui.sbMinorTicksIncrementNumeric->setValue(increment); - else { - dtsbMinorTicksIncrement->setValue(increment); - } - } + ui.leMinorTicksIncrement->setText( QString::number(increment)); m_initializing = false; } void AxisDock::axisMinorTicksPenChanged(const QPen& pen) { @@ -1818,10 +1671,6 @@ ui.leStart->setText( QString::number(m_axis->start()) ); ui.leEnd->setText( QString::number(m_axis->end()) ); - - ui.sbMajorTicksIncrementNumeric->setDecimals(0); - ui.sbMajorTicksIncrementNumeric->setSingleStep(m_axis->majorTicksIncrement()); - //depending on range format of the axis (numeric vs. datetime), show/hide the corresponding widgets const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { @@ -1857,7 +1706,6 @@ } ui.dateTimeEditStart->setDateTime(QDateTime::fromMSecsSinceEpoch(m_axis->start())); ui.dateTimeEditEnd->setDateTime(QDateTime::fromMSecsSinceEpoch(m_axis->end())); - } } @@ -1877,6 +1725,7 @@ ui.cbMajorTicksDirection->setCurrentIndex( (int) m_axis->majorTicksDirection() ); ui.cbMajorTicksType->setCurrentIndex( (int) m_axis->majorTicksType() ); ui.sbMajorTicksNumber->setValue( m_axis->majorTicksNumber() ); + ui.leMajorTicksIncrement->setText( QString::number(m_axis->majorTicksIncrement()) ); ui.cbMajorTicksLineStyle->setCurrentIndex( (int) m_axis->majorTicksPen().style() ); ui.kcbMajorTicksColor->setColor( m_axis->majorTicksPen().color() ); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksPen().widthF(),Worksheet::Point) ); @@ -1887,6 +1736,7 @@ ui.cbMinorTicksDirection->setCurrentIndex( (int) m_axis->minorTicksDirection() ); ui.cbMinorTicksType->setCurrentIndex( (int) m_axis->minorTicksType() ); ui.sbMinorTicksNumber->setValue( m_axis->minorTicksNumber() ); + ui.leMinorTicksIncrement->setText( QString::number( m_axis->minorTicksIncrement()) ); ui.cbMinorTicksLineStyle->setCurrentIndex( (int) m_axis->minorTicksPen().style() ); ui.kcbMinorTicksColor->setColor( m_axis->minorTicksPen().color() ); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksPen().widthF(),Worksheet::Point) ); @@ -1924,6 +1774,7 @@ ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorGridPen().widthF(),Worksheet::Point) ); ui.sbMinorGridOpacity->setValue( round(m_axis->minorGridOpacity()*100.0) ); + m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); this->majorTicksTypeChanged(ui.cbMajorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, ui.kcbMajorTicksColor->color()); @@ -1931,46 +1782,7 @@ GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, ui.kcbMinorTicksColor->color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, ui.kcbMajorGridColor->color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, ui.kcbMinorGridColor->color()); -} - -/*! - * Determine the number of decimals for using in a QDoubleSpinBox - * \param diff - * \return - */ -int AxisDock::determineDecimals(double diff) { - diff /= 10; // step one decimal before - double power10 = 1; - for (int i = 0; i < 10; i++) { - double nearest = round(diff * power10) / power10; - if (nearest > 0) { - return i; - } - power10 *= 10; - } - - return 10; -} - -/*! - * Determine the step in a QDoubleSpinBox with specific decimals and diff - * \param diff Difference between the largest value and smallest value - * \param decimal - * \return - */ -double AxisDock::determineStep(double diff, int decimal) { - double ten = 1; - if (decimal == 0) { - for (unsigned int i = 1; i < 1000000000; i++) { - if (diff/ten <= 10) { - return ten/10; // use one decimal before - } - ten *= 10; - } - return 1; - } - - return static_cast(1)/(pow(10,decimal)); + m_initializing = false; } void AxisDock::loadConfigFromTemplate(KConfig& config) { @@ -1996,13 +1808,6 @@ void AxisDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "Axis" ); - bool numeric = false; - const auto* plot = dynamic_cast(m_axis->parentAspect()); - if (plot) { - numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - } - //General ui.cbOrientation->setCurrentIndex( group.readEntry("Orientation", (int) m_axis->orientation()) ); @@ -2037,10 +1842,7 @@ ui.cbMajorTicksDirection->setCurrentIndex( group.readEntry("MajorTicksDirection", (int) m_axis->majorTicksDirection()) ); ui.cbMajorTicksType->setCurrentIndex( group.readEntry("MajorTicksType", (int) m_axis->majorTicksType()) ); ui.sbMajorTicksNumber->setValue( group.readEntry("MajorTicksNumber", m_axis->majorTicksNumber()) ); - if (numeric) - ui.sbMajorTicksIncrementNumeric->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); - else - dtsbMajorTicksIncrement->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); + ui.leMajorTicksIncrement->setText( QString::number( group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())) ); ui.cbMajorTicksLineStyle->setCurrentIndex( group.readEntry("MajorTicksLineStyle", (int) m_axis->majorTicksPen().style()) ); ui.kcbMajorTicksColor->setColor( group.readEntry("MajorTicksColor", m_axis->majorTicksPen().color()) ); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksWidth", m_axis->majorTicksPen().widthF()),Worksheet::Point) ); @@ -2051,10 +1853,7 @@ ui.cbMinorTicksDirection->setCurrentIndex( group.readEntry("MinorTicksDirection", (int) m_axis->minorTicksDirection()) ); ui.cbMinorTicksType->setCurrentIndex( group.readEntry("MinorTicksType", (int) m_axis->minorTicksType()) ); ui.sbMinorTicksNumber->setValue( group.readEntry("MinorTicksNumber", m_axis->minorTicksNumber()) ); - if (numeric) - ui.sbMinorTicksIncrementNumeric->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); - else - dtsbMinorTicksIncrement->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())); + ui.leMinorTicksIncrement->setText( QString::number( group.readEntry("MinorTicksIncrement", m_axis->minorTicksIncrement())) ); ui.cbMinorTicksLineStyle->setCurrentIndex( group.readEntry("MinorTicksLineStyle", (int) m_axis->minorTicksPen().style()) ); ui.kcbMinorTicksColor->setColor( group.readEntry("MinorTicksColor", m_axis->minorTicksPen().color()) ); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksWidth", m_axis->minorTicksPen().widthF()),Worksheet::Point) ); @@ -2106,14 +1905,6 @@ void AxisDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Axis" ); - bool numeric = false; - const auto* plot = dynamic_cast(m_axis->parentAspect()); - if (plot) { - numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) - || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); - } - - //General group.writeEntry("Orientation", ui.cbOrientation->currentIndex()); @@ -2149,10 +1940,7 @@ group.writeEntry("MajorTicksDirection", ui.cbMajorTicksDirection->currentIndex()); group.writeEntry("MajorTicksType", ui.cbMajorTicksType->currentIndex()); group.writeEntry("MajorTicksNumber", ui.sbMajorTicksNumber->value()); - if (numeric) - group.writeEntry("MajorTicksIncrement", QString::number(ui.sbMajorTicksIncrementNumeric->value())); - else - group.writeEntry("MajorTicksIncrement", QString::number(dtsbMajorTicksIncrement->value())); + group.writeEntry("MajorTicksIncrement", ui.leMajorTicksIncrement->text()); group.writeEntry("MajorTicksLineStyle", ui.cbMajorTicksLineStyle->currentIndex()); group.writeEntry("MajorTicksColor", ui.kcbMajorTicksColor->color()); group.writeEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(ui.sbMajorTicksWidth->value(),Worksheet::Point)); @@ -2163,10 +1951,7 @@ group.writeEntry("MinorTicksDirection", ui.cbMinorTicksDirection->currentIndex()); group.writeEntry("MinorTicksType", ui.cbMinorTicksType->currentIndex()); group.writeEntry("MinorTicksNumber", ui.sbMinorTicksNumber->value()); - if (numeric) - group.writeEntry("MinorTicksIncrement", QString::number(ui.sbMinorTicksIncrementNumeric->value())); - else - group.writeEntry("MinorTicksIncrement", QString::number(dtsbMinorTicksIncrement->value())); + group.writeEntry("MinorTicksIncrement", ui.leMinorTicksIncrement->text()); group.writeEntry("MinorTicksLineStyle", ui.cbMinorTicksLineStyle->currentIndex()); group.writeEntry("MinorTicksColor", ui.kcbMinorTicksColor->color()); group.writeEntry("MinorTicksWidth", Worksheet::convertFromSceneUnits(ui.sbMinorTicksWidth->value(),Worksheet::Point)); diff --git a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp --- a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp @@ -77,7 +77,9 @@ //"Background"-tab ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); - ui.leBackgroundFileName->setCompleter(new QCompleter(new QDirModel, this)); + auto* completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui.leBackgroundFileName->setCompleter(completer); //"Title"-tab auto* hboxLayout = new QHBoxLayout(ui.tabTitle); @@ -278,8 +280,8 @@ ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } //show the properties of the first plot @@ -364,17 +366,11 @@ ui.cbXScaling->addItem( i18n("log(x)") ); ui.cbXScaling->addItem( i18n("log2(x)") ); ui.cbXScaling->addItem( i18n("ln(x)") ); - ui.cbXScaling->addItem( i18n("log(abs(x))") ); - ui.cbXScaling->addItem( i18n("log2(abs(x))") ); - ui.cbXScaling->addItem( i18n("ln(abs(x))") ); ui.cbYScaling->addItem( i18n("linear") ); ui.cbYScaling->addItem( i18n("log(y)") ); ui.cbYScaling->addItem( i18n("log2(y)") ); ui.cbYScaling->addItem( i18n("ln(y)") ); - ui.cbYScaling->addItem( i18n("log(abs(y))") ); - ui.cbYScaling->addItem( i18n("log2(abs(y))") ); - ui.cbYScaling->addItem( i18n("ln(abs(y))") ); //scale breakings ui.cbXBreakStyle->addItem( i18n("Simple") ); @@ -728,9 +724,9 @@ m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->xRangeBreaks().list.at(index); - QString str = std::isnan(rangeBreak.start) ? QString() : QString::number(rangeBreak.start); + QString str = std::isnan(rangeBreak.start) ? "" : QString::number(rangeBreak.start); ui.leXBreakStart->setText(str); - str = std::isnan(rangeBreak.end) ? QString() : QString::number(rangeBreak.end); + str = std::isnan(rangeBreak.end) ? "" : QString::number(rangeBreak.end); ui.leXBreakEnd->setText(str); ui.sbXBreakPosition->setValue(rangeBreak.position*100); ui.cbXBreakStyle->setCurrentIndex((int)rangeBreak.style); @@ -849,9 +845,9 @@ m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->yRangeBreaks().list.at(index); - QString str = std::isnan(rangeBreak.start) ? QString() : QString::number(rangeBreak.start); + QString str = std::isnan(rangeBreak.start) ? "" : QString::number(rangeBreak.start); ui.leYBreakStart->setText(str); - str = std::isnan(rangeBreak.end) ? QString() : QString::number(rangeBreak.end); + str = std::isnan(rangeBreak.end) ? "" : QString::number(rangeBreak.end); ui.leYBreakEnd->setText(str); ui.sbYBreakPosition->setValue(rangeBreak.position*100); ui.cbYBreakStyle->setCurrentIndex((int)rangeBreak.style); @@ -1074,7 +1070,7 @@ if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else - ui.leBackgroundFileName->setStyleSheet(QString()); + ui.leBackgroundFileName->setStyleSheet(""); for (auto* plot : m_plotList) plot->plotArea()->setBackgroundFileName(fileName); @@ -1151,33 +1147,17 @@ void CartesianPlotDock::horizontalPaddingChanged(double value) { if (m_initializing) return; - double padding = Worksheet::convertToSceneUnits(value, Worksheet::Centimeter); - for (auto* plot : m_plotList) { - if (plot->rect().width() > 2*padding) { - plot->setHorizontalPadding(padding); - } else { // preventing that padding is bigger than the size of the plot - m_initializing = true; - ui.sbPaddingHorizontal->setValue(Worksheet::convertFromSceneUnits(plot->horizontalPadding(), Worksheet::Centimeter)); - m_initializing = false; - } - } + + for (auto* plot : m_plotList) + plot->setHorizontalPadding(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } void CartesianPlotDock::verticalPaddingChanged(double value) { if (m_initializing) return; - // TODO: find better solution (set spinbox range). When plot->rect().width() does change? - double padding = Worksheet::convertToSceneUnits(value, Worksheet::Centimeter); - for (auto* plot : m_plotList) { - if (plot->rect().height() > 2 * padding) { - plot->setVerticalPadding(padding); - } else { // preventing that padding is bigger than the size of the plot - m_initializing = true; - ui.sbPaddingVertical->setValue(Worksheet::convertFromSceneUnits(plot->verticalPadding(), Worksheet::Centimeter)); - m_initializing = false; - } - } + for (auto* plot : m_plotList) + plot->setVerticalPadding(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } //************************************************************* @@ -1517,7 +1497,7 @@ if (!m_plot->plotArea()->backgroundFileName().isEmpty() && !QFile::exists(m_plot->plotArea()->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else - ui.leBackgroundFileName->setStyleSheet(QString()); + ui.leBackgroundFileName->setStyleSheet(""); //Padding ui.sbPaddingHorizontal->setValue( Worksheet::convertFromSceneUnits(m_plot->horizontalPadding(), Worksheet::Centimeter) ); diff --git a/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp --- a/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp @@ -61,7 +61,9 @@ //"Background"-tab ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); - ui.leBackgroundFileName->setCompleter(new QCompleter(new QDirModel, this)); + auto completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui.leBackgroundFileName->setCompleter(completer); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { @@ -155,8 +157,8 @@ ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } //show the properties of the first curve diff --git a/src/kdefrontend/dockwidgets/ColumnDock.cpp b/src/kdefrontend/dockwidgets/ColumnDock.cpp --- a/src/kdefrontend/dockwidgets/ColumnDock.cpp +++ b/src/kdefrontend/dockwidgets/ColumnDock.cpp @@ -96,8 +96,8 @@ ui.leName->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } //show the properties of the first column diff --git a/src/kdefrontend/dockwidgets/CustomPointDock.cpp b/src/kdefrontend/dockwidgets/CustomPointDock.cpp --- a/src/kdefrontend/dockwidgets/CustomPointDock.cpp +++ b/src/kdefrontend/dockwidgets/CustomPointDock.cpp @@ -129,8 +129,8 @@ ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } //show the properties of the first custom point diff --git a/src/kdefrontend/dockwidgets/HistogramDock.cpp b/src/kdefrontend/dockwidgets/HistogramDock.cpp --- a/src/kdefrontend/dockwidgets/HistogramDock.cpp +++ b/src/kdefrontend/dockwidgets/HistogramDock.cpp @@ -77,7 +77,9 @@ ui.cbFillingColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bFillingOpen->setIcon( QIcon::fromTheme("document-open") ); - ui.leFillingFileName->setCompleter(new QCompleter(new QDirModel, this)); + auto* completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui.leFillingFileName->setCompleter(completer); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { @@ -313,7 +315,7 @@ QList list; list << "Folder" << "Workbook" << "Datapicker" << "DatapickerCurve" << "Spreadsheet" - << "LiveDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYFitCurve" << "CantorWorksheet"; + << "FileDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYFitCurve" << "CantorWorksheet"; cbDataColumn->setTopLevelClasses(list); cbValuesColumn->setTopLevelClasses(list); @@ -361,8 +363,8 @@ cbDataColumn->setCurrentModelIndex(QModelIndex()); cbValuesColumn->setCurrentModelIndex(QModelIndex()); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } //show the properties of the first curve @@ -376,7 +378,7 @@ ui.leBinRangesMax->setText( QString::number(m_curve->binRangesMax()) ); ui.chkVisible->setChecked( m_curve->isVisible() ); - KConfig config(QString(), KConfig::SimpleConfig); + KConfig config("", KConfig::SimpleConfig); loadConfig(config); //Slots @@ -570,10 +572,8 @@ } void HistogramDock::binRangesMinChanged(const QString& value) { - DEBUG("HistogramDock::binRangesMinChanged() value = " << value.toDouble()); if (m_initializing) return; - DEBUG(" set value") const double min = value.toDouble(); for (auto* hist : m_curvesList) @@ -1403,13 +1403,13 @@ void HistogramDock::curveBinRangesMinChanged(double value) { m_initializing = true; - ui.leBinRangesMin->setText(QString::number(value)); + ui.leBinRangesMin->setText( QString::number(value) ); m_initializing = false; } void HistogramDock::curveBinRangesMaxChanged(double value) { m_initializing = true; - ui.leBinRangesMax->setText(QString::number(value)); + ui.leBinRangesMax->setText( QString::number(value) ); m_initializing = false; } diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.h b/src/kdefrontend/dockwidgets/LiveDataDock.h --- a/src/kdefrontend/dockwidgets/LiveDataDock.h +++ b/src/kdefrontend/dockwidgets/LiveDataDock.h @@ -4,7 +4,6 @@ Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) -Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** @@ -34,8 +33,6 @@ #include #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/MQTTClient.h" - -class MQTTSubscriptionWidget; #endif #include @@ -54,12 +51,12 @@ public: explicit LiveDataDock(QWidget *parent = nullptr); - void setLiveDataSource(LiveDataSource* const source); + void setLiveDataSources(const QList& sources); ~LiveDataDock() override; private: Ui::LiveDataDock ui; - LiveDataSource* m_liveDataSource{nullptr}; + QList m_liveDataSources; bool m_paused{false}; @@ -67,7 +64,6 @@ void continueReading(); private slots: - void nameChanged(const QString&); void updateTypeChanged(int); void readingTypeChanged(int); void sampleSizeChanged(int); @@ -79,7 +75,7 @@ #ifdef HAVE_MQTT public: - void setMQTTClient(MQTTClient* const client); + void setMQTTClients(const QList& clients); bool testSubscribe(const QString&); bool testUnsubscribe(const QString&); @@ -93,37 +89,46 @@ void willUpdateTypeChanged(int); void willUpdateNow(); void willUpdateIntervalChanged(int); - void statisticsChanged(MQTTClient::WillStatisticsType); + void statisticsChanged(MQTTClient::WillStatisticsType); + void addSubscription(); + void removeSubscription(); void onMQTTConnect(); void mqttMessageReceived(const QByteArray&, const QMqttTopicName&); - void mqttMessageReceivedInBackground(const QByteArray&, const QMqttTopicName&); - void removeClient(const QString&, quint16); + void mqttMessageReceivedInBackground(const QByteArray&, const QMqttTopicName&); + void setTopicCompleter(const QString&); + void topicTimeout(); + void fillSubscriptions(); + void scrollToTopicTreeItem(const QString&); + void scrollToSubsriptionTreeItem(const QString&); + void removeClient(const QString&); void showWillSettings(); - void enableWill(bool enable); signals: void newTopic(const QString&); - void MQTTClearTopics(); - void updateSubscriptionTree(const QVector&); private: - void addTopicToTree(const QString&); - - struct MQTTHost { - int count; - QMqttClient* client; - QStringList topicList; - QVector addedTopics; - }; - - MQTTClient* m_mqttClient{nullptr}; + void updateSubscriptionCompleter(); + void addTopicToTree(const QString&); + bool checkTopicContains(const QString& superior, const QString& inferior); + QString checkCommonLevel(const QString& first, const QString& second); + void findSubscriptionLeafChildren(QVector&, QTreeWidgetItem*); + int checkCommonChildCount(int levelIdx, int level, QStringList& namelist, QTreeWidgetItem* currentItem); + void manageCommonLevelSubscriptions(); + int commonLevelIndex(const QString& first, const QString& second); + void addSubscriptionChildren(QTreeWidgetItem* topic, QTreeWidgetItem* subscription); + void restoreSubscriptionChildren(QTreeWidgetItem* topic, QTreeWidgetItem* subscription, const QStringList&, int level); + + QList m_mqttClients; + QMap m_clients; + QCompleter* m_topicCompleter{nullptr}; + QCompleter* m_subscriptionCompleter{nullptr}; + QMap m_topicList; + bool m_searching{true}; + QTimer* m_searchTimer; + bool m_interpretMessage{true}; const MQTTClient* m_previousMQTTClient{nullptr}; - QMap, MQTTHost> m_hosts; - MQTTHost* m_currentHost{nullptr}; - MQTTHost* m_previousHost{nullptr}; - bool m_interpretMessage{true}; - MQTTSubscriptionWidget* m_subscriptionWidget; - QMetaObject::Connection m_updateSubscriptionConn; + QString m_mqttUnsubscribeName; + QMap> m_addedTopics; #endif }; diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -4,7 +4,7 @@ Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) -Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) +Copyright : (C) 2018 by Kovacs Ferencz (kferike98@gmail.com) Copyright : (C) 2018 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -36,7 +36,6 @@ #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" -#include "kdefrontend/datasources/MQTTSubscriptionWidget.h" #include #include #include @@ -44,15 +43,14 @@ LiveDataDock::LiveDataDock(QWidget* parent) : QWidget(parent) #ifdef HAVE_MQTT - , - m_subscriptionWidget(new MQTTSubscriptionWidget(this)) + , + m_searchTimer(new QTimer(this)) #endif { ui.setupUi(this); ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); - connect(ui.leName, &QLineEdit::textChanged, this, &LiveDataDock::nameChanged); connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading); connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow); connect(ui.sbUpdateInterval, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged); @@ -63,30 +61,58 @@ connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged); #ifdef HAVE_MQTT - connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); - connect(ui.bLWT, &QPushButton::clicked, this, &LiveDataDock::showWillSettings); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &LiveDataDock::enableWill); - - ui.swSubscriptions->addWidget(m_subscriptionWidget); - ui.swSubscriptions->setCurrentWidget(m_subscriptionWidget); - + m_searchTimer->setInterval(10000); + + const int size = ui.leTopics->height(); + ui.lTopicSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) ); + ui.lSubscriptionSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) ); + + connect(this, &LiveDataDock::newTopic, this, &LiveDataDock::setTopicCompleter); + connect(m_searchTimer, &QTimer::timeout, this, &LiveDataDock::topicTimeout); + connect(ui.bSubscribe, &QPushButton::clicked, this, &LiveDataDock::addSubscription); + connect(ui.bUnsubscribe, &QPushButton::clicked, this, &LiveDataDock::removeSubscription); + connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); + connect(ui.leTopics, &QLineEdit::textChanged, this, &LiveDataDock::scrollToTopicTreeItem); + connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &LiveDataDock::scrollToSubsriptionTreeItem); + connect(ui.bLWT, &QPushButton::clicked, this, &LiveDataDock::showWillSettings); + + ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); + ui.bSubscribe->setToolTip(i18n("Subscribe selected topics")); + ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_ArrowLeft)); + ui.bUnsubscribe->setToolTip(i18n("Unsubscribe selected topics")); ui.bLWT->setToolTip(i18n("Manage MQTT connection's will settings")); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); - QString info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); + QString info = i18n("Enter the name of the topic to navigate to it."); + ui.lTopicSearch->setToolTip(info); + ui.leTopics->setToolTip(info); + ui.lSubscriptionSearch->setToolTip(info); + ui.leSubscriptions->setToolTip(info); + + info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); - ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); + ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); + + info = i18n("Set the Quality of Service (QoS) for the subscription to define the guarantee of the message delivery:" + "
    " + "
  • 0 - deliver at most once
  • " + "
  • 1 - deliver at least once
  • " + "
  • 2 - deliver exactly once
  • " + "
"); + ui.cbQoS->setToolTip(info); #endif } #ifdef HAVE_MQTT LiveDataDock::~LiveDataDock() { - for (auto & host : m_hosts) - delete host.client; - - delete m_subscriptionWidget; + delete m_searchTimer; + QMapIterator clients(m_clients); + while (clients.hasNext()) { + clients.next(); + delete clients.value(); + } } #else LiveDataDock::~LiveDataDock() = default; @@ -94,30 +120,25 @@ #ifdef HAVE_MQTT /*! - * \brief Sets the MQTTClient of this dock widget + * \brief Sets the MQTTClients of this dock widget * \param clients */ -void LiveDataDock::setMQTTClient(MQTTClient* const client) { - m_liveDataSource = nullptr; // prevent updates due to changes to input widgets - if (m_mqttClient == client) - return; - auto oldclient = m_mqttClient; - m_mqttClient = nullptr; // prevent updates due to changes to input widgets - - ui.leName->setText(client->name()); - const QPair id(client->clientHostName(), client->clientPort()); - ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(id.first).arg(id.second)); +void LiveDataDock::setMQTTClients(const QList& clients) { + m_liveDataSources.clear(); + m_mqttClients.clear(); + m_mqttClients = clients; + const MQTTClient* const fmc = clients.at(0); - ui.sbUpdateInterval->setValue(client->updateInterval()); - ui.cbUpdateType->setCurrentIndex(static_cast(client->updateType())); - ui.cbReadingType->setCurrentIndex(static_cast(client->readingType())); + ui.sbUpdateInterval->setValue(fmc->updateInterval()); + ui.cbUpdateType->setCurrentIndex(static_cast(fmc->updateType())); + ui.cbReadingType->setCurrentIndex(static_cast(fmc->readingType())); - if (client->updateType() == MQTTClient::NewData) { + if (fmc->updateType() == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } - if (client->isPaused()) { + if (fmc->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { @@ -125,166 +146,121 @@ ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } - ui.sbKeepNValues->setValue(client->keepNValues()); + ui.sbKeepNValues->setValue(fmc->keepNValues()); ui.sbKeepNValues->setEnabled(true); - if (client->readingType() == MQTTClient::TillEnd) { + if (fmc->readingType() == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else - ui.sbSampleSize->setValue(client->sampleSize()); + ui.sbSampleSize->setValue(fmc->sampleSize()); // disable "whole file" option const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); - QStandardItem* item = model->item(LiveDataSource::WholeFile); + QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); - if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) - ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); - - m_mqttClient = client; // updates may be applied from now on //show MQTT connected options - ui.lTopics->show(); - ui.swSubscriptions->setVisible(true); - m_subscriptionWidget->setVisible(true); - m_subscriptionWidget->makeVisible(true); + ui.lTopics->show(); + ui.gbManageSubscriptions->show(); + ui.bSubscribe->show(); + ui.bUnsubscribe->show(); + ui.twTopics->show(); + ui.leTopics->show(); + ui.lTopicSearch->show(); + ui.twSubscriptions->show(); + ui.cbQoS->show(); ui.lLWT->show(); ui.bLWT->show(); - m_previousHost = m_currentHost; //if there isn't a client with this hostname we instantiate a new one - auto it = m_hosts.find(id); - if (it == m_hosts.end()) { - m_currentHost = &m_hosts[id]; - m_currentHost->count = 1; - m_currentHost->client = new QMqttClient; - - connect(client, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); + if (m_clients[fmc->clientHostName()] == nullptr) { + m_clients[fmc->clientHostName()] = new QMqttClient(); - connect(m_currentHost->client, &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); - connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); + connect(fmc, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription); + connect(m_clients[fmc->clientHostName()], &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); + connect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); + m_clients[fmc->clientHostName()]->setHostname(fmc->clientHostName()); + m_clients[fmc->clientHostName()]->setPort(fmc->clientPort()); - m_currentHost->client->setHostname(id.first); - m_currentHost->client->setPort(id.second); - - if (client->MQTTUseAuthentication()) { - m_currentHost->client->setUsername(client->clientUserName()); - m_currentHost->client->setPassword(client->clientPassword()); + if (fmc->MQTTUseAuthentication()) { + m_clients[fmc->clientHostName()]->setUsername(fmc->clientUserName()); + m_clients[fmc->clientHostName()]->setPassword(fmc->clientPassword()); } - if (client->MQTTUseID()) - m_currentHost->client->setClientId(client->clientID()); + if (fmc->MQTTUseID()) { + m_clients[fmc->clientHostName()]->setClientId(fmc->clientID()); + } - m_currentHost->client->connectToHost(); - } else { - m_currentHost = &it.value(); - ++m_currentHost->count; + m_clients[fmc->clientHostName()]->connectToHost(); } - if (m_previousMQTTClient == nullptr) { - m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() {emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions());}); + if (m_previousMQTTClient == nullptr) { + connect(fmc, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); //Fill the subscription tree(useful if the MQTTClient was loaded) - QVector topics = client->topicNames(); + QVector topics = fmc->topicNames(); for (const auto& topic : topics) { addTopicToTree(topic); } - emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); + fillSubscriptions(); } //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots //and clear the tree widgets - else if (m_previousMQTTClient->clientHostName() != client->clientHostName()) { - disconnect(m_updateSubscriptionConn); - disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); - connect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); - - disconnect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); - - disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, m_previousMQTTClient, &MQTTClient::reparentTopic); - disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, m_previousMQTTClient, &MQTTClient::addBeforeRemoveSubscription); - disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, m_previousMQTTClient, &MQTTClient::removeMQTTSubscription); - disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, m_previousMQTTClient, &MQTTClient::addMQTTSubscription); + else if (m_previousMQTTClient->clientHostName() != fmc->clientHostName()) { + disconnect(m_previousMQTTClient, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); + disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); + connect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); - m_previousHost->topicList = m_subscriptionWidget->getTopicList(); - m_subscriptionWidget->setTopicList(m_currentHost->topicList); + disconnect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); - emit MQTTClearTopics(); + ui.twTopics->clear(); //repopulating the tree widget with the already known topics of the client - for (int i = 0; i < m_currentHost->addedTopics.size(); ++i) { - addTopicToTree(m_currentHost->addedTopics.at(i)); + for (int i = 0; i < m_addedTopics[fmc->clientHostName()].size(); ++i) { + addTopicToTree(m_addedTopics[fmc->clientHostName()].at(i)); } - //fill subscriptions tree widget - emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); + //fill subscriptions tree widget + ui.twSubscriptions->clear(); + fillSubscriptions(); - m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() {emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions());}); - connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); - - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription); - connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription); + connect(fmc, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); + connect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); } - if (client->willUpdateType() == MQTTClient::OnClick && client->MQTTWillUse()) + if (fmc->willUpdateType() == MQTTClient::WillUpdateType::OnClick && fmc->MQTTWillUse()) ui.bWillUpdateNow->show(); - m_previousMQTTClient = oldclient; + m_previousMQTTClient = fmc; } #endif /*! - * \brief Sets the live data source of this dock widget + * \brief Sets the live data sources of this dock widget * \param sources */ -void LiveDataDock::setLiveDataSource(LiveDataSource* const source) { +void LiveDataDock::setLiveDataSources(const QList& sources) { #ifdef HAVE_MQTT - m_mqttClient = nullptr; + m_mqttClients.clear(); #endif - if (m_liveDataSource == source) - return; - m_liveDataSource = nullptr; // prevent updates due to changes to input widgets - - ui.leName->setText(source->name()); - const LiveDataSource::SourceType sourceType = source->sourceType(); - const LiveDataSource::ReadingType readingType = source->readingType(); - const LiveDataSource::UpdateType updateType = source->updateType(); - const AbstractFileFilter::FileType fileType = source->fileType(); - ui.sbUpdateInterval->setValue(source->updateInterval()); + m_liveDataSources = sources; + const LiveDataSource* const fds = sources.at(0); + const LiveDataSource::SourceType sourceType = fds->sourceType(); + const LiveDataSource::ReadingType readingType = fds->readingType(); + const LiveDataSource::UpdateType updateType = fds->updateType(); + ui.sbUpdateInterval->setValue(fds->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(updateType)); ui.cbReadingType->setCurrentIndex(static_cast(readingType)); - switch (sourceType) { - case LiveDataSource::FileOrPipe: - ui.leSourceInfo->setText(source->fileName()); - break; - case LiveDataSource::NetworkTcpSocket: - case LiveDataSource::NetworkUdpSocket: - ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(source->host()).arg(source->port())); - break; - case LiveDataSource::LocalSocket: - ui.leSourceInfo->setText(source->localSocketName()); - break; - case LiveDataSource::SerialPort: - ui.leSourceInfo->setText(source->serialPortName()); - break; - case LiveDataSource::MQTT: - break; - } - if (updateType == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } - if (source->isPaused()) { + if (fds->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { @@ -292,86 +268,76 @@ ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } - ui.sbKeepNValues->setValue(source->keepNValues()); + ui.sbKeepNValues->setValue(fds->keepNValues()); // disable "whole file" when having no file (i.e. socket or port) auto* model = qobject_cast(ui.cbReadingType->model()); - QStandardItem* item = model->item(LiveDataSource::WholeFile); - if (sourceType == LiveDataSource::SourceType::FileOrPipe) { + QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); + if (sourceType == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - //for file types other than ASCII and binary we support re-reading the whole file only - //select "read whole file" and deactivate the combobox - if (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary) { - ui.cbReadingType->setCurrentIndex(LiveDataSource::WholeFile); - ui.cbReadingType->setEnabled(false); - } else - ui.cbReadingType->setEnabled(true); - } else { - if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) - ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); + else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); - } - if (((sourceType == LiveDataSource::FileOrPipe || sourceType == LiveDataSource::NetworkUdpSocket) && - (readingType == LiveDataSource::ContinuousFixed || readingType == LiveDataSource::FromEnd))) - ui.sbSampleSize->setValue(source->sampleSize()); - else { + if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort + || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); + } else { + ui.sbSampleSize->setValue(fds->sampleSize()); } // disable "on new data"-option if not available model = qobject_cast(ui.cbUpdateType->model()); - item = model->item(LiveDataSource::NewData); - if (sourceType == LiveDataSource::NetworkTcpSocket || sourceType == LiveDataSource::NetworkUdpSocket || - sourceType == LiveDataSource::SerialPort) + item = model->item(LiveDataSource::UpdateType::NewData); + if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::NetworkUdpSocket || + sourceType == LiveDataSource::SourceType::SerialPort) item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); else item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); -#ifdef HAVE_MQTT ui.lTopics->hide(); ui.bLWT->hide(); ui.lLWT->hide(); ui.bWillUpdateNow->hide(); - ui.swSubscriptions->setVisible(false); - m_subscriptionWidget->setVisible(false); - m_subscriptionWidget->makeVisible(false); -#endif - m_liveDataSource = source; // updates may be applied from now on + ui.bSubscribe->hide(); + ui.bUnsubscribe->hide(); + ui.twTopics->hide(); + ui.leTopics->hide(); + ui.lTopicSearch->hide(); + ui.twSubscriptions->hide(); + ui.gbManageSubscriptions->hide(); } /*! - * \brief Modifies the sample size of the live data source or MQTTClient object + * \brief Modifies the sample size of the live data sources or MQTTClient objects * \param sampleSize */ void LiveDataDock::sampleSizeChanged(int sampleSize) { - if (m_liveDataSource) - m_liveDataSource->setSampleSize(sampleSize); + if (!m_liveDataSources.isEmpty()) { + for (auto* source : m_liveDataSources) + source->setSampleSize(sampleSize); + } #ifdef HAVE_MQTT - else if (m_mqttClient) - m_mqttClient->setSampleSize(sampleSize); + else if (!m_mqttClients.isEmpty()) { + for (auto* client : m_mqttClients) + client->setSampleSize(sampleSize); + } #endif } /*! - * \brief Updates the live data source now + * \brief Updates the live data sources now */ void LiveDataDock::updateNow() { - if (m_liveDataSource) - m_liveDataSource->updateNow(); -#ifdef HAVE_MQTT - else if (m_mqttClient) - m_mqttClient->updateNow(); -#endif -} - -void LiveDataDock::nameChanged(const QString& name) { - if (m_liveDataSource) - m_liveDataSource->setName(name); + if (!m_liveDataSources.isEmpty()) { + for (auto* source : m_liveDataSources) + source->updateNow(); + } #ifdef HAVE_MQTT - else if (m_mqttClient) - m_mqttClient->setName(name); + else if (!m_mqttClients.isEmpty()) { + for (auto* client : m_mqttClients) + client->updateNow(); + } #endif } @@ -380,52 +346,55 @@ * \param idx */ void LiveDataDock::updateTypeChanged(int idx) { - if (m_liveDataSource) { + if (!m_liveDataSources.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); - const LiveDataSource::UpdateType updateType = static_cast(idx); + const auto type = static_cast(idx); - switch (updateType) { - case LiveDataSource::TimeInterval: { + switch (type) { + case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); - const LiveDataSource::SourceType s = m_liveDataSource->sourceType(); - const LiveDataSource::ReadingType r = m_liveDataSource->readingType(); - const bool showSampleSize = ((s == LiveDataSource::FileOrPipe || s == LiveDataSource::NetworkUdpSocket) && - (r == LiveDataSource::ContinuousFixed || r == LiveDataSource::FromEnd)); - ui.lSampleSize->setVisible(showSampleSize); - ui.sbSampleSize->setVisible(showSampleSize); - - m_liveDataSource->setUpdateType(updateType); - m_liveDataSource->setUpdateInterval(ui.sbUpdateInterval->value()); - m_liveDataSource->setFileWatched(false); + ui.lSampleSize->show(); + ui.sbSampleSize->show(); + + for (auto* source : m_liveDataSources) { + source->setUpdateType(type); + source->setUpdateInterval(ui.sbUpdateInterval->value()); + source->setFileWatched(false); + } break; - } - case LiveDataSource::NewData: + case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); - m_liveDataSource->setFileWatched(true); - m_liveDataSource->setUpdateType(updateType); + for (auto* source : m_liveDataSources) { + source->setFileWatched(true); + source->setUpdateType(type); + } } } #ifdef HAVE_MQTT - else if (m_mqttClient) { + else if (!m_mqttClients.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); const MQTTClient::UpdateType type = static_cast(idx); - if (type == MQTTClient::TimeInterval) { + if (type == MQTTClient::UpdateType::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); - m_mqttClient->setUpdateType(type); - m_mqttClient->setUpdateInterval(ui.sbUpdateInterval->value()); - } else if (type == MQTTClient::NewData) { + for (auto* client : m_mqttClients) { + client->setUpdateType(type); + client->setUpdateInterval(ui.sbUpdateInterval->value()); + } + } else if (type == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); - m_mqttClient->setUpdateType(type); + for (auto* client : m_mqttClients) { + client->setUpdateType(type); + } } } #endif @@ -436,14 +405,15 @@ * \param idx */ void LiveDataDock::readingTypeChanged(int idx) { - if (m_liveDataSource) { + if (!m_liveDataSources.isEmpty()) { const auto type = static_cast(idx); - const LiveDataSource::SourceType sourceType = m_liveDataSource->sourceType(); - const LiveDataSource::UpdateType updateType = m_liveDataSource->updateType(); + const LiveDataSource* const fds = m_liveDataSources.at(0); + const LiveDataSource::SourceType sourceType = fds->sourceType(); + const LiveDataSource::UpdateType updateType = fds->updateType(); - if (sourceType == LiveDataSource::NetworkTcpSocket || sourceType == LiveDataSource::LocalSocket || sourceType == LiveDataSource::SerialPort - || type == LiveDataSource::TillEnd || type == LiveDataSource::WholeFile - || updateType == LiveDataSource::NewData) { + if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort + || type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile + || updateType == LiveDataSource::UpdateType::NewData) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { @@ -451,13 +421,14 @@ ui.sbSampleSize->show(); } - m_liveDataSource->setReadingType(type); + for (auto* source : m_liveDataSources) + source->setReadingType(type); } #ifdef HAVE_MQTT - else if (m_mqttClient) { + else if (!m_mqttClients.isEmpty()) { MQTTClient::ReadingType type = static_cast(idx); - if (type == MQTTClient::TillEnd) { + if (type == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { @@ -465,34 +436,43 @@ ui.sbSampleSize->show(); } - m_mqttClient->setReadingType(type); + for (auto* client : m_mqttClients) + client->setReadingType(type); } #endif } /*! - * \brief Modifies the update interval of the live data source + * \brief Modifies the update interval of the live data sources * \param updateInterval */ void LiveDataDock::updateIntervalChanged(int updateInterval) { - if (m_liveDataSource) - m_liveDataSource->setUpdateInterval(updateInterval); + if (!m_liveDataSources.isEmpty()) { + for (auto* source : m_liveDataSources) + source->setUpdateInterval(updateInterval); + } #ifdef HAVE_MQTT - else if (m_mqttClient) - m_mqttClient->setUpdateInterval(updateInterval); + else if (!m_mqttClients.isEmpty()) { + for (auto* client : m_mqttClients) + client->setUpdateInterval(updateInterval); + } #endif } /*! - * \brief Modifies the number of samples to keep in each of the live data source + * \brief Modifies the number of samples to keep in each of the live data sources * \param keepNValues */ void LiveDataDock::keepNValuesChanged(const int keepNValues) { - if (m_liveDataSource) - m_liveDataSource->setKeepNValues(keepNValues); + if (!m_liveDataSources.isEmpty()) { + for (auto* source : m_liveDataSources) + source->setKeepNValues(keepNValues); + } #ifdef HAVE_MQTT - else if (m_mqttClient) - m_mqttClient->setKeepNValues(keepNValues); + else if (!m_mqttClients.isEmpty()) { + for (auto* client : m_mqttClients) + client->setKeepNValues(keepNValues); + } #endif } @@ -500,11 +480,15 @@ * \brief Pauses the reading of the live data source */ void LiveDataDock::pauseReading() { - if (m_liveDataSource) - m_liveDataSource->pauseReading(); + if (!m_liveDataSources.isEmpty()) { + for (auto* source: m_liveDataSources) + source->pauseReading(); + } #ifdef HAVE_MQTT - else if (m_mqttClient) - m_mqttClient->pauseReading(); + else if (!m_mqttClients.isEmpty()) { + for (auto* client : m_mqttClients) + client->pauseReading(); + } #endif } @@ -512,11 +496,15 @@ * \brief Continues the reading of the live data source */ void LiveDataDock::continueReading() { - if (m_liveDataSource) - m_liveDataSource->continueReading(); + if (!m_liveDataSources.isEmpty()) { + for (auto* source : m_liveDataSources) + source->continueReading(); + } #ifdef HAVE_MQTT - else if (m_mqttClient) - m_mqttClient->continueReading(); + else if (!m_mqttClients.isEmpty()) { + for (auto* client : m_mqttClients) + client->continueReading(); + } #endif } @@ -541,7 +529,7 @@ /*! *\brief called when use will message checkbox's state is changed in the will settings widget, - * Sets the mqttUseWill according to state for the m_mqttClient + * Sets the mqttUseWill according to state for every client in m_mqttClients * * \param state the state of the checbox */ @@ -549,123 +537,147 @@ qDebug()<<"Use will message: " << use; if (use) { - m_mqttClient->setMQTTWillUse(true); - if (m_mqttClient->willUpdateType() == MQTTClient::OnClick) + for (auto* source : m_mqttClients) + source->setMQTTWillUse(true); + + if (m_mqttClients.first()->willUpdateType() == MQTTClient::WillUpdateType::OnClick) ui.bWillUpdateNow->show(); } else { - m_mqttClient->setMQTTWillUse(false); + for (auto* source : m_mqttClients) + source->setMQTTWillUse(false); + ui.bWillUpdateNow->hide(); } } /*! *\brief called when will message's QoS is changed in the will settings widget - * sets the will QoS level for the m_mqttClient + * sets the will QoS level for every client in m_mqttClients * * \param QoS the QoS level of the will message */ void LiveDataDock::willQoSChanged(int QoS) { - m_mqttClient->setWillQoS(QoS); + for (auto* source : m_mqttClients) + source->setWillQoS(QoS); } /*! *\brief called when will message's retain flag is changed in the will settings widget - * sets the retain flag for the will message in in m_mqttClient + * sets the retain flag for the will message in every client in m_mqttClients * * \param state the state of the will retain chechbox */ void LiveDataDock::willRetainChanged(bool useWillRetainMessages) { if (useWillRetainMessages) { - m_mqttClient->setWillRetain(true); + for (auto* source : m_mqttClients) + source->setWillRetain(true); } else { - m_mqttClient->setWillRetain(false); + for (auto* source : m_mqttClients) + source->setWillRetain(false); } } /*! *\brief called when will topic combobox's current item is changed in the will settings widget - * sets the will topic for the m_mqttClient + * sets the will topic for every client in m_mqttClients * * \param topic the current text of cbWillTopic */ void LiveDataDock::willTopicChanged(const QString& topic) { - if (m_mqttClient->willTopic() != topic) - m_mqttClient->clearLastMessage(); + for (auto* source : m_mqttClients) { + if (source->willTopic() != topic) + source->clearLastMessage(); - m_mqttClient->setWillTopic(topic); + source->setWillTopic(topic); + } } /*! *\brief called when the selected will message type is changed in the will settings widget - * sets the will message type for the m_mqttClient + * sets the will message type for every client in m_mqttClients * * \param type the selected will message type */ void LiveDataDock::willMessageTypeChanged(MQTTClient::WillMessageType willMessageType) { - m_mqttClient->setWillMessageType(willMessageType); + for (auto* source : m_mqttClients) + source->setWillMessageType(willMessageType); } /*! *\brief called when the will own message is changed in the will settings widget - * sets the will own message for the m_mqttClient + * sets the will own message for every client in m_mqttClients * * \param message the will message given by the user */ void LiveDataDock::willOwnMessageChanged(const QString& message) { - m_mqttClient->setWillOwnMessage(message); + for (auto* source : m_mqttClients) + source->setWillOwnMessage(message); } /*! *\brief called when the selected update type for the will message is changed in the will settings widget - * sets the will update type for the m_mqttClient + * sets the will update type for every client in m_mqttClients * * \param type the selected will update type */ void LiveDataDock::willUpdateTypeChanged(int updateType) { - m_mqttClient->setWillUpdateType(static_cast(updateType)); + for (auto* source : m_mqttClients) + source->setWillUpdateType(static_cast(updateType)); - if (static_cast(updateType) == MQTTClient::TimePeriod) { + if (static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { ui.bWillUpdateNow->hide(); - m_mqttClient->startWillTimer(); - } else if (static_cast(updateType) == MQTTClient::OnClick) { + for (auto* source : m_mqttClients) + source->startWillTimer(); + } else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { ui.bWillUpdateNow->show(); //if update type is on click we stop the will timer - m_mqttClient->stopWillTimer(); + for (auto* source : m_mqttClients) + source->stopWillTimer(); } } /*! *\brief called when the will update now button is pressed - * updates the will message of m_mqttClient + * updates the will message of every client in m_mqttClients */ void LiveDataDock::willUpdateNow() { - m_mqttClient->updateWillMessage(); + for (auto* source : m_mqttClients) + source->updateWillMessage(); } /*! *\brief called when the update interval for will message is changed in the will settings widget - * sets the will update interval for the m_mqttClient, then starts the will timer for each one + * sets the will update interval for every client in m_mqttClients, then starts the will timer for each one * * \param interval the new will update interval */ void LiveDataDock::willUpdateIntervalChanged(int interval) { - m_mqttClient->setWillTimeInterval(interval); - m_mqttClient->startWillTimer(); + for (auto* source : m_mqttClients) { + source->setWillTimeInterval(interval); + source->startWillTimer(); + } } /*! *\brief called when the will statistics are changed in the will settings widget - * adds or removes the statistic represented by the index from m_mqttClient + * adds or removes the statistic represented by the index from every client in m_mqttClients */ void LiveDataDock::statisticsChanged(MQTTClient::WillStatisticsType willStatisticsType) { + const bool useStatistic = m_mqttClients.first()->willStatistics()[static_cast(willStatisticsType)]; + if (willStatisticsType >= 0) { //if it's not already added and it's checked we add it - if (!m_mqttClient->willStatistics()[static_cast(willStatisticsType)]) - m_mqttClient->addWillStatistics(willStatisticsType); - else //otherwise remove it - m_mqttClient->removeWillStatistics(willStatisticsType); + if (!useStatistic) + { + for (auto* source : m_mqttClients) + source->addWillStatistics(willStatisticsType); + } else { + //otherwise remove it + for (auto* source : m_mqttClients) + source->removeWillStatistics(willStatisticsType); + } } } @@ -674,7 +686,8 @@ * in order to later list every available topic */ void LiveDataDock::onMQTTConnect() { - if (!m_currentHost || !m_currentHost->client || !m_currentHost->client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) + auto* client = m_clients[m_mqttClients.first()->clientHostName()]; + if (!client || !client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); } @@ -684,12 +697,792 @@ */ void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) - if (!m_currentHost->addedTopics.contains(topic.name())) { - m_currentHost->addedTopics.push_back(topic.name()); + if (!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { + m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); addTopicToTree(topic.name()); } } +/*! + *\brief called when the subscribe button is pressed + * subscribes to the topic represented by the current item of twTopics in every client from m_mqttClients + */ +void LiveDataDock::addSubscription() { + QString name; + QTreeWidgetItem* item = ui.twTopics->currentItem(); + if (item != nullptr) { + QTreeWidgetItem* tempItem = item; + + //determine the topic name that the current item represents + name.prepend(item->text(0)); + if (item->childCount() != 0) + name.append("/#"); + while (tempItem->parent() != nullptr) { + tempItem = tempItem->parent(); + name.prepend(tempItem->text(0) + '/'); + } + + //check if the subscription already exists + const QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); + if (topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { + qDebug() << "LiveDataDock: start to add new subscription: " << name; + bool foundSuperior = false; + + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + //if the new subscriptions contains an already existing one, we remove the inferior one + if (checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + ui.twSubscriptions->topLevelItem(i)->takeChildren(); + ui.twSubscriptions->takeTopLevelItem(i); + --i; + continue; + } + + //if there is a subscription containing the new one we set foundSuperior true + if (checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + foundSuperior = true; + qDebug()<<"Can't add "<topLevelItem(i)->text(0); + break; + } + } + + //if there wasn't a superior subscription we can subscribe to the new topic + if (!foundSuperior) { + QStringList toplevelName; + toplevelName.push_back(name); + QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); + ui.twSubscriptions->addTopLevelItem(newTopLevelItem); + + if (name.endsWith('#')) { + //adding every topic that the subscription contains to twSubscriptions + addSubscriptionChildren(item, newTopLevelItem); + } + + //subscribe in every MQTTClient + for (auto* source : m_mqttClients) { + source->addMQTTSubscription(name, ui.cbQoS->currentIndex()); + } + + if (name.endsWith('#')) { + //if an already existing subscription contains a topic that the new subscription also contains + //we decompose the already existing subscription + //by unsubscribing from its topics, that are present in the new subscription as well + const QStringList& nameList = name.split('/', QString::SkipEmptyParts); + const QString root = nameList.first(); + QVector children; + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + children.clear(); + + //get the "leaf" children of the inspected subscription + findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); + for (int j = 0; j < children.size(); ++j) { + if (checkTopicContains(name, children[j]->text(0))) { + //if the new subscription contains a topic, we unsubscribe from it + QTreeWidgetItem* unsubscribeItem = children[j]; + while (unsubscribeItem->parent() != nullptr) { + for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { + + if (unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { + //add topic as subscription to every client + for (auto* source : m_mqttClients) { + source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); + } + //also add it to twSubscriptions + ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); + --i; + } else { + //before we remove the topic, we reparent it to the new subscription + //so no data is lost + for (auto* source : m_mqttClients) { + source->reparentTopic(unsubscribeItem->text(0), name); + } + } + } + unsubscribeItem = unsubscribeItem->parent(); + } + + qDebug()<<"Remove: "<text(0); + //Remove topic/subscription + for (auto* source : m_mqttClients) { + source->removeMQTTSubscription(unsubscribeItem->text(0)); + } + + ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); + } + } + } + } + } + + manageCommonLevelSubscriptions(); + updateSubscriptionCompleter(); + + if (!ui.bLWT->isEnabled()) + ui.bLWT->setEnabled(true); + } else { + QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); + } + } + else + QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); + } + else + QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); +} + +/*! + *\brief called when the unsubscribe button is pressed + * unsubscribes from the topic represented by the current item of twSubscription in every client from m_mqttClients + */ +void LiveDataDock::removeSubscription() { + QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); + + if (unsubscribeItem != nullptr) { + qDebug() << "LiveDataDock: unsubscribe from " << unsubscribeItem->text(0); + + //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) + //we can simply unsubscribe from it + if (unsubscribeItem->parent() == nullptr) { + for (auto* source : m_mqttClients) { + source->removeMQTTSubscription(unsubscribeItem->text(0)); + } + ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); + } + //otherwise we remove the selected item, but subscribe to every other topic, that was contained by + //the selected item's parent subscription(top level item of twSubscriptions) + else { + while (unsubscribeItem->parent() != nullptr) { + for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { + if (unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { + //add topic as subscription to every client + for (auto* source : m_mqttClients) { + source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); + } + ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); + --i; + } + } + unsubscribeItem = unsubscribeItem->parent(); + } + + //remove topic/subscription from every client + for (auto* source : m_mqttClients) { + source->removeMQTTSubscription(unsubscribeItem->text(0)); + } + ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); + + //check if any common topics were subscribed, if possible merge them + manageCommonLevelSubscriptions(); + } + + if (ui.twSubscriptions->topLevelItemCount() <= 0) + ui.bLWT->setEnabled(false); + + updateSubscriptionCompleter(); + } else + QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); +} + +/*! + *\brief called when a new topic is added to the tree(twTopics) + * appends the topic's root to the topicList if it isn't in the list already + * then sets the completer for leTopics + */ +void LiveDataDock::setTopicCompleter(const QString& topicName) { + if (!m_searching) { + const QStringList& list = topicName.split('/', QString::SkipEmptyParts); + QString topic; + if (!list.isEmpty()) { + topic = list.at(0); + } else + topic = topicName; + + if (!m_topicList[m_mqttClients.first()->clientHostName()].contains(topic)) { + m_topicList[m_mqttClients.first()->clientHostName()].append(topic); + m_topicCompleter = new QCompleter(m_topicList[m_mqttClients.first()->clientHostName()], this); + m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); + m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); + ui.leTopics->setCompleter(m_topicCompleter); + } + } +} + +/*! + *\brief Updates the completer for leSubscriptions + */ +void LiveDataDock::updateSubscriptionCompleter() { + QStringList subscriptionList; + const QVector& subscriptions = m_mqttClients.first()->MQTTSubscriptions(); + + if (!subscriptions.isEmpty()) { + for (const auto& subscription : subscriptions) + subscriptionList << subscription; + + m_subscriptionCompleter = new QCompleter(subscriptionList, this); + m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); + m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); + ui.leSubscriptions->setCompleter(m_subscriptionCompleter); + } else { + ui.leSubscriptions->setCompleter(nullptr); + } +} + +/*! + *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics + * enables updating the completer for le + */ +void LiveDataDock::topicTimeout() { + m_searching = false; + m_searchTimer->stop(); +} + +/*! + *\brief called when a new the host name of the MQTTClients from m _mqttClients changes + * or when the MQTTClients initialize their subscriptions + * Fills twSubscriptions with the subscriptions of the MQTTClient + */ +void LiveDataDock::fillSubscriptions() { + const MQTTClient* const fmc = m_mqttClients.at(0); + + ui.twSubscriptions->clear(); + + QVector subscriptions = fmc->MQTTSubscriptions(); + for (int i = 0; i < subscriptions.count(); ++i) { + QStringList name; + name.append(subscriptions[i]); + + bool found = false; + for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + if (ui.twSubscriptions->topLevelItem(j)->text(0) == subscriptions[i]) { + found = true; + break; + } + } + + if (!found) { + //Add the subscription to the tree widget + QTreeWidgetItem* newItem = new QTreeWidgetItem(name); + ui.twSubscriptions->addTopLevelItem(newItem); + name.clear(); + name = subscriptions[i].split('/', QString::SkipEmptyParts); + + //find the corresponding "root" item in twTopics + QTreeWidgetItem* topic = nullptr; + for (int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { + if (ui.twTopics->topLevelItem(j)->text(0) == name[0]) { + topic = ui.twTopics->topLevelItem(j); + break; + } + } + + //restore the children of the subscription + if (topic != nullptr && topic->childCount() > 0) + restoreSubscriptionChildren(topic, newItem, name, 1); + } + } + m_searching = false; +} + +/*! + *\brief Checks if a topic contains another one + * + * \param superior the name of a topic + * \param inferior the name of a topic + * \return true if superior is equal to or contains(if superior contains wildcards) inferior, + * false otherwise + */ +bool LiveDataDock::checkTopicContains(const QString& superior, const QString& inferior) { + if (superior == inferior) + return true; + else { + if (superior.contains('/')) { + QStringList superiorList = superior.split('/', QString::SkipEmptyParts); + QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); + + //a longer topic can't contain a shorter one + if (superiorList.size() > inferiorList.size()) + return false; + + bool ok = true; + for (int i = 0; i < superiorList.size(); ++i) { + if (superiorList.at(i) != inferiorList.at(i)) { + if ((superiorList.at(i) != "+") && + !(superiorList.at(i) == '#' && i == superiorList.size() - 1)) { + //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) + //then superior can't contain inferior + ok = false; + break; + } else if (i == superiorList.size() - 1 && (superiorList.at(i) == "+" && inferiorList.at(i) == '#') ) { + //if the two topics differ at the last level + //and the superior's current level is + while the inferior's is #(which can be only in the last position) + //then superior can't contain inferior + ok = false; + break; + } + } + } + + return ok; + } + return false; + } +} + +/*! + *\brief called when leTopics' text is changed + * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget + * + * \param rootName the current text of leTopics + */ +void LiveDataDock::scrollToTopicTreeItem(const QString& rootName) { + m_searching = true; + m_searchTimer->start(); + + int topItemIdx = -1; + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) + if (ui.twTopics->topLevelItem(i)->text(0) == rootName) { + topItemIdx = i; + break; + } + + if (topItemIdx >= 0) + ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); +} + +/*! + *\brief called when leSubscriptions' text is changed + * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget + * + * \param rootName the current text of leSubscriptions + */ +void LiveDataDock::scrollToSubsriptionTreeItem(const QString& rootName) { + int topItemIdx = -1; + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) + if (ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { + topItemIdx = i; + break; + } + + if (topItemIdx >= 0) + ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); +} + +/*! + *\brief Returns the "+" wildcard containing topic name, which includes the given topic names + * + * \param first the name of a topic + * \param second the name of a topic + * \return The name of the common topic, if it exists, otherwise "" + */ +QString LiveDataDock::checkCommonLevel(const QString& first, const QString& second) { + const QStringList firstList = first.split('/', QString::SkipEmptyParts); + const QStringList secondtList = second.split('/', QString::SkipEmptyParts); + QString commonTopic = ""; + + if (!firstList.isEmpty()) { + //the two topics have to be the same size and can't be identic + if (firstList.size() == secondtList.size() && (first != second)) { + + //the index where they differ + int differIndex = -1; + for (int i = 0; i < firstList.size(); ++i) { + if (firstList.at(i) != secondtList.at(i)) { + differIndex = i; + break; + } + } + + //they can differ at only one level + bool differ = false; + if (differIndex > 0 && differIndex < firstList.size() - 1) { + for (int j = differIndex + 1; j < firstList.size(); ++j) { + if (firstList.at(j) != secondtList.at(j)) { + differ = true; + break; + } + } + } + else + differ = true; + + if (!differ) + { + for (int i = 0; i < firstList.size(); ++i) { + if (i != differIndex) { + commonTopic.append(firstList.at(i)); + } else { + //we put "+" wildcard at the level where they differ + commonTopic.append('+'); + } + + if (i != firstList.size() - 1) + commonTopic.append('/'); + } + } + } + } + qDebug() << "Common topic for " << first << " and "<childCount() > 0) { + for (int i = 0; i < topic->childCount(); ++i) { + QTreeWidgetItem* temp = topic->child(i); + QString name; + //if it has children, then we add it as a # wildcrad containing topic + if (topic->child(i)->childCount() > 0) { + name.append(temp->text(0) + QLatin1String("/#")); + while (temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + '/'); + } + + } + //if not then we simply add the topic itself + else { + name.append(temp->text(0)); + while (temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + '/'); + } + } + + QStringList nameList { QStringList() << name}; + QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); + subscription->addChild(childItem); + //we use the function recursively on the given item + addSubscriptionChildren(topic->child(i), childItem); + } + } +} + +/*! + *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards + * + * \param topic pointer to a top level item in twTopics which represents the root of the subscription topic + * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored + * \param list QStringList containing the levels of the subscription topic + * \param level the level's number which is being investigated + */ +void LiveDataDock::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { + if (level < list.size()) { + if ((level < list.size() - 1) && (list[level] != "+") && (list[level] != '#')) { + for (int i = 0; i < topic->childCount(); ++i) { + //if the current level isn't + or # wildcard we recursively continue with the next level + if (topic->child(i)->text(0) == list[level]) { + restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); + break; + } + } + } else if (list[level] == "+") { + for (int i = 0; i < topic->childCount(); ++i) { + //determine the name of the topic, contained by the subscription + QString name; + name.append(topic->child(i)->text(0)); + for (int j = level + 1; j < list.size(); ++j) { + name.append('/' + list[j]); + } + QTreeWidgetItem* temp = topic->child(i); + while (temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + '/'); + } + + //Add the topic as child of the subscription + QStringList nameList; + nameList.append(name); + QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); + subscription->addChild(newItem); + //Continue adding children recursively to the new item + restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); + } + } else if (list[level] == '#') { + //add the children of the # wildcard containing subscription + addSubscriptionChildren(topic, subscription); + } + } +} + +/*! + *\brief Returns the index of level where the two topic names differ, if there is a common topic for them + * + * \param first the name of a topic + * \param second the name of a topic + * \return The index of the unequal level, if there is a common topic, otherwise -1 + */ +int LiveDataDock::commonLevelIndex(const QString& first, const QString& second) { + QStringList firstList = first.split('/', QString::SkipEmptyParts); + QStringList secondtList = second.split('/', QString::SkipEmptyParts); + QString commonTopic = ""; + int differIndex = -1; + + if (!firstList.isEmpty()) { + //the two topics have to be the same size and can't be identic + if (firstList.size() == secondtList.size() && (first != second)) { + + //the index where they differ + for (int i = 0; i < firstList.size(); ++i) { + if (firstList.at(i) != secondtList.at(i)) { + differIndex = i; + break; + } + } + + //they can differ at only one level + bool differ = false; + if (differIndex > 0) { + for (int j = differIndex + 1; j < firstList.size(); ++j) { + if (firstList.at(j) != secondtList.at(j)) { + differ = true; + break; + } + } + } + else + differ = true; + + if (!differ) + { + for (int i = 0; i < firstList.size(); ++i) { + if (i != differIndex) + commonTopic.append(firstList.at(i)); + else + commonTopic.append('+'); + + if (i != firstList.size() - 1) + commonTopic.append('/'); + } + } + } + } + + //if there is a common topic we return the differIndex + if (!commonTopic.isEmpty()) + return differIndex; + else + return -1; +} + +/*! + *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) + * + * \param children vector of TreeWidgetItem pointers + * \param root pointer to a TreeWidgetItem of twSubscriptions + */ +void LiveDataDock::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { + if (root->childCount() == 0) { + children.push_back(root); + } else { + for (int i = 0; i < root->childCount(); ++i) { + findSubscriptionLeafChildren(children, root->child(i)); + } + } +} + +/*! + *\brief Returns the amount of topics that the "+" wildcard will replace in the level position + * + * \param levelIdx the level currently being investigated + * \param level the level where the new + wildcard will be placed + * \param commonList the topic name split into levels + * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level + * represented by levelIdx + * \return returns the childCount, or -1 if some topics already represented by + wildcard have different + * amount of children + */ +int LiveDataDock::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { + //we recursively check the number of children, until we get to level-1 + if (levelIdx < level - 1) { + if (commonList[levelIdx] != "+") { + for (int j = 0; j < currentItem->childCount(); ++j) { + if (currentItem->child(j)->text(0) == commonList[levelIdx]) { + //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively + return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); + } + } + } else { + int childCount = -1; + bool ok = true; + + //otherwise we check if every + wildcard represented topic has the same number of children, recursively + for (int j = 0; j < currentItem->childCount(); ++j) { + int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); + + if ((j > 0) && (temp != childCount)) { + ok = false; + break; + } + childCount = temp; + } + + //if yes we return this number, otherwise -1 + if (ok) + return childCount; + else + return -1; + } + } else if (levelIdx == level - 1) { + if (commonList[levelIdx] != "+") { + for (int j = 0; j < currentItem->childCount(); ++j) { + if (currentItem->child(j)->text(0) == commonList[levelIdx]) { + //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item + return currentItem->child(j)->childCount(); + } + } + } else { + int childCount = -1; + bool ok = true; + + //otherwise we check if every + wildcard represented topic has the same number of children + for (int j = 0; j < currentItem->childCount(); ++j) { + if ((j > 0) && (currentItem->child(j)->childCount() != childCount)) { + ok = false; + break; + } + childCount = currentItem->child(j)->childCount(); + } + + //if yes we return this number, otherwise -1 + if (ok) + return childCount; + else + return -1; + } + } else if (level == 1 && levelIdx == 1) + return currentItem->childCount(); + + return -1; +} + +/*! + *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. + * We do this until there are no topics to merge + */ +void LiveDataDock::manageCommonLevelSubscriptions() { + bool foundEqual = false; + do { + foundEqual = false; + QMap> equalTopicsMap; + QVector equalTopics; + + //compare the subscriptions present in the TreeWidget + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { + for (int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); + + //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) + if (!commonTopic.isEmpty()) { + if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { + equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); + } + + if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { + equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); + } + } + } + } + + if (!equalTopicsMap.isEmpty()) { + qDebug()<<"Manage equal topics"; + + QVector commonTopics; + QMapIterator> topics(equalTopicsMap); + + //check for every map entry, if the found topics can be merged or not + while (topics.hasNext()) { + topics.next(); + + int level = commonLevelIndex(topics.value().last(), topics.value().first()); + QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); + QTreeWidgetItem* currentItem = nullptr; + + //search the corresponding item to the common topics first level(root) + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { + currentItem = ui.twTopics->topLevelItem(i); + break; + } + } + + if (!currentItem) + break; + + //calculate the number of topics the new + wildcard could replace + int childCount = checkCommonChildCount(1, level, commonList, currentItem); + if (childCount > 0) { + //if the number of topics found and the calculated number of topics is equal, the topics can be merged + if (topics.value().size() == childCount) { + qDebug() << "Found common topic to manage: " << topics.key(); + foundEqual = true; + commonTopics.push_back(topics.key()); + } + } + } + + if (foundEqual) { + //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new "+" wildcard + int highestLevel = INT_MAX; + int topicIdx = -1; + for (int i = 0; i < commonTopics.size(); ++i) { + int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); + if (level < highestLevel) { + topicIdx = i; + highestLevel = level; + } + } + qDebug() << "Start to manage: " << commonTopics[topicIdx]; + equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); + + //Add the common topic ("merging") + QString commonTopic; + commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); + QStringList nameList; + nameList.append(commonTopic); + QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); + ui.twSubscriptions->addTopLevelItem(newTopic); + + //remove the "merged" topics + for (int i = 0; i < equalTopics.size(); ++i) { + for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + if (ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { + newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); + break; + } + } + } + + //remove any subscription that the new subscription contains + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && + commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { + ui.twSubscriptions->topLevelItem(i)->takeChildren(); + ui.twSubscriptions->takeTopLevelItem(i); + i--; + } + } + + //make the subscription on commonTopic in every MQTTClient from m_mqttClients + for (auto* source : m_mqttClients) { + source->addMQTTSubscription(commonTopic, ui.cbQoS->currentIndex()); + } + } + } + } while (foundEqual); +} + /*! *\brief Adds topicName to twTopics * @@ -708,8 +1501,8 @@ QTreeWidgetItem* currentItem; //check whether the first level of the topic can be found in twTopics int topItemIdx = -1; - for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { - if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { topItemIdx = i; break; } @@ -717,7 +1510,7 @@ //if not we simply add every level of the topic to the tree if ( topItemIdx < 0) { currentItem = new QTreeWidgetItem(name); - m_subscriptionWidget->addTopic(currentItem); + ui.twTopics->addTopLevelItem(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); @@ -728,7 +1521,7 @@ //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { - currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); + currentItem = ui.twTopics->topLevelItem(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; @@ -760,14 +1553,14 @@ else { rootName = topicName; name.append(topicName); - m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); + ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions - for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { - QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); if (rootName == subscriptionName[0]) { - emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); + fillSubscriptions(); break; } } @@ -778,48 +1571,40 @@ } /*! - *\brief called when a client receives a message, if the clients hostname isn't identic with the host name of MQTTClient - * if the message arrived from a new topic, the topic is added to the host data + *\brief called when a client receives a message, if the clients hostname isn't identic with the host name of MQTTClients + * if the message arrived from a new topic, the topic is put in m_addedTopics */ void LiveDataDock::mqttMessageReceivedInBackground(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) - if (!m_currentHost->addedTopics.contains(topic.name())) - m_currentHost->addedTopics.push_back(topic.name()); + if (!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) + m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); } /*! *\brief called when an MQTTClient is about to be deleted - * removes every data connected to the MQTTClient, and disconnects the corresponding client from the host + * removes every data connected to the MQTTClient, and disconnects the corresponding client from m_clients * - * \param hostname the host name of the MQTTClient that will be deleted * \param name the host name of the MQTTClient that will be deleted */ -void LiveDataDock::removeClient(const QString& hostname, quint16 port) { - auto it = m_hosts.find(qMakePair(hostname, port)); - if (it == m_hosts.end()) - return; +void LiveDataDock::removeClient(const QString& name) { + m_clients[name]->disconnectFromHost(); - MQTTHost & host = it.value(); + m_addedTopics.remove(name); + m_topicList.remove(name); - if (host.count > 1) { - --host.count; - return; - } - - host.client->disconnectFromHost(); - - if (m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == hostname) { - disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); + if (m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == name) { + disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); m_previousMQTTClient = nullptr; } - if (m_mqttClient->clientHostName() == hostname) { - emit MQTTClearTopics(); - m_mqttClient = nullptr; + if (m_mqttClients.first()->clientHostName() == name) { + ui.twSubscriptions->clear(); + ui.twTopics->clear(); + m_mqttClients.clear(); } - delete host.client; - m_hosts.erase(it); + delete m_clients[name]; + m_clients.remove(name); } /*! @@ -829,9 +1614,9 @@ bool LiveDataDock::testSubscribe(const QString& topic) { QStringList topicList = topic.split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; - for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { - if (m_subscriptionWidget->topLevelTopic(i)->text(0) == topicList[0]) { - currentItem = m_subscriptionWidget->topLevelTopic(i); + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == topicList[0]) { + currentItem = ui.twTopics->topLevelItem(i); break; } } @@ -853,7 +1638,8 @@ } else return false; - m_subscriptionWidget->testSubscribe(currentItem); + ui.twTopics->setCurrentItem(currentItem); + addSubscription(); return true; } @@ -863,9 +1649,9 @@ */ bool LiveDataDock::testUnsubscribe(const QString& topic) { QTreeWidgetItem* currentItem = nullptr; - for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { - if (MQTTSubscriptionWidget::checkTopicContains(m_subscriptionWidget->topLevelSubscription(i)->text(0), topic)) { - currentItem = m_subscriptionWidget->topLevelSubscription(i); + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), topic)) { + currentItem = ui.twSubscriptions->topLevelItem(i); break; } } @@ -873,12 +1659,13 @@ if (currentItem) { do { if (topic == currentItem->text(0)) { - m_subscriptionWidget->testUnsubscribe(currentItem); + ui.twSubscriptions->setCurrentItem(currentItem); + removeSubscription(); return true; } else { for (int i = 0; i < currentItem->childCount(); ++i) { qDebug()<child(i)->text(0)<<" "<child(i)->text(0), topic)) { + if (checkTopicContains(currentItem->child(i)->text(0), topic)) { currentItem = currentItem->child(i); break; } else if (i == currentItem->childCount() - 1) @@ -894,8 +1681,9 @@ void LiveDataDock::showWillSettings() { QMenu menu; - const QVector& topics = m_mqttClient->topicNames(); - MQTTWillSettingsWidget willSettingsWidget(&menu, m_mqttClient->willSettings(), topics); + const MQTTClient* const fmc = m_mqttClients.at(0); + const QVector& topics = fmc->topicNames(); + MQTTWillSettingsWidget willSettingsWidget(&menu, fmc->willSettings(), topics); connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() { this->useWillMessage(willSettingsWidget.will().enabled); @@ -917,13 +1705,4 @@ QPoint pos(ui.bLWT->sizeHint().width(), ui.bLWT->sizeHint().height()); menu.exec(ui.bLWT->mapToGlobal(pos)); } - -void LiveDataDock::enableWill(bool enable) { - if(enable) { - if(!ui.bLWT->isEnabled()) - ui.bLWT->setEnabled(enable); - } else { - ui.bLWT->setEnabled(enable); - } -} #endif diff --git a/src/kdefrontend/dockwidgets/MatrixDock.cpp b/src/kdefrontend/dockwidgets/MatrixDock.cpp --- a/src/kdefrontend/dockwidgets/MatrixDock.cpp +++ b/src/kdefrontend/dockwidgets/MatrixDock.cpp @@ -95,8 +95,8 @@ ui.leName->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } //show the properties of the first Matrix in the list, if there are >1 matrixs diff --git a/src/kdefrontend/dockwidgets/ProjectDock.cpp b/src/kdefrontend/dockwidgets/ProjectDock.cpp --- a/src/kdefrontend/dockwidgets/ProjectDock.cpp +++ b/src/kdefrontend/dockwidgets/ProjectDock.cpp @@ -66,7 +66,7 @@ ui.lModified->setText(project->modificationTime().toString()); //show default properties of the project - KConfig config(QString(), KConfig::SimpleConfig); + KConfig config("", KConfig::SimpleConfig); loadConfig(config); connect(m_project, &Project::aspectDescriptionChanged, this, &ProjectDock::projectDescriptionChanged); diff --git a/src/kdefrontend/dockwidgets/SpreadsheetDock.cpp b/src/kdefrontend/dockwidgets/SpreadsheetDock.cpp --- a/src/kdefrontend/dockwidgets/SpreadsheetDock.cpp +++ b/src/kdefrontend/dockwidgets/SpreadsheetDock.cpp @@ -79,8 +79,8 @@ ui.leName->setEnabled(false); ui.teComment->setEnabled(false); - ui.leName->setText(QString()); - ui.teComment->setText(QString()); + ui.leName->setText(""); + ui.teComment->setText(""); } //show the properties of the first Spreadsheet in the list diff --git a/src/kdefrontend/dockwidgets/WorksheetDock.cpp b/src/kdefrontend/dockwidgets/WorksheetDock.cpp --- a/src/kdefrontend/dockwidgets/WorksheetDock.cpp +++ b/src/kdefrontend/dockwidgets/WorksheetDock.cpp @@ -57,7 +57,9 @@ ui.cbBackgroundColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); - ui.leBackgroundFileName->setCompleter(new QCompleter(new QDirModel, this)); + auto* completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui.leBackgroundFileName->setCompleter(completer); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { @@ -160,8 +162,8 @@ ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } //show the properties of the first worksheet @@ -653,7 +655,7 @@ if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else - ui.leBackgroundFileName->setStyleSheet(QString()); + ui.leBackgroundFileName->setStyleSheet(""); for (auto* worksheet : m_worksheetList) worksheet->setBackgroundFileName(fileName); @@ -835,7 +837,7 @@ if (!m_worksheet->backgroundFileName().isEmpty() && !QFile::exists(m_worksheet->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else - ui.leBackgroundFileName->setStyleSheet(QString()); + ui.leBackgroundFileName->setStyleSheet(""); // Layout ui.sbLayoutTopMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutTopMargin(), Worksheet::Centimeter) ); diff --git a/src/kdefrontend/dockwidgets/XYConvolutionCurveDock.cpp b/src/kdefrontend/dockwidgets/XYConvolutionCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYConvolutionCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYConvolutionCurveDock.cpp @@ -153,8 +153,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -216,7 +216,7 @@ list.clear(); list << "Folder" << "Workbook" << "Datapicker" << "DatapickerCurve" << "Spreadsheet" - << "LiveDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYConvolutionCurve"; + << "FileDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYConvolutionCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbY2DataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/dockwidgets/XYCorrelationCurveDock.cpp b/src/kdefrontend/dockwidgets/XYCorrelationCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYCorrelationCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYCorrelationCurveDock.cpp @@ -142,8 +142,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -199,7 +199,7 @@ list.clear(); list << "Folder" << "Workbook" << "Datapicker" << "DatapickerCurve" << "Spreadsheet" - << "LiveDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYCorrelationCurve"; + << "FileDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYCorrelationCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbY2DataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/dockwidgets/XYCurveDock.cpp b/src/kdefrontend/dockwidgets/XYCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYCurveDock.cpp @@ -73,7 +73,9 @@ ui.cbFillingColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bFillingOpen->setIcon( QIcon::fromTheme("document-open") ); - ui.leFillingFileName->setCompleter(new QCompleter(new QDirModel, this)); + auto* completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui.leFillingFileName->setCompleter(completer); //Tab "Error bars" gridLayout = qobject_cast(ui.tabErrorBars->layout()); @@ -471,7 +473,7 @@ QList list; list << "Folder" << "Workbook" << "Datapicker" << "DatapickerCurve" << "Spreadsheet" - << "LiveDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYFitCurve" << "CantorWorksheet"; + << "FileDataSource" << "Column" << "Worksheet" << "CartesianPlot" << "XYFitCurve" << "CantorWorksheet"; if (cbXColumn) { cbXColumn->setTopLevelClasses(list); @@ -547,8 +549,8 @@ cbXColumn->setCurrentModelIndex(QModelIndex()); cbYColumn->setCurrentModelIndex(QModelIndex()); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve diff --git a/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp b/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp @@ -136,8 +136,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -195,7 +195,7 @@ list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" - <<"LiveDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; + <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp b/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp @@ -130,8 +130,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -180,7 +180,7 @@ list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" - <<"LiveDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; + <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/dockwidgets/XYEquationCurveDock.cpp b/src/kdefrontend/dockwidgets/XYEquationCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYEquationCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYEquationCurveDock.cpp @@ -148,8 +148,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve diff --git a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp @@ -1,4 +1,4 @@ -/*************************************************************************** +/*************************************************************************** File : XYFitCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- @@ -231,8 +231,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } uiGeneralTab.cbDataSourceType->setCurrentIndex(m_fitCurve->dataSourceType()); @@ -293,7 +293,7 @@ cbDataSourceCurve->setHiddenAspects(hiddenAspects); list.clear(); - list << "Folder" << "Workbook" << "Spreadsheet" << "LiveDataSource" << "Column" << "CantorWorksheet" << "Datapicker"; + list << "Folder" << "Workbook" << "Spreadsheet" << "FileDataSource" << "Column" << "CantorWorksheet" << "Datapicker"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbXErrorColumn->setTopLevelClasses(list); @@ -1156,9 +1156,9 @@ //clear the previous result uiGeneralTab.twParameters->setRowCount(0); for (int row = 0; row < uiGeneralTab.twGoodness->rowCount(); ++row) - uiGeneralTab.twGoodness->item(row, 2)->setText(QString()); + uiGeneralTab.twGoodness->item(row, 2)->setText(""); for (int row = 0; row < uiGeneralTab.twLog->rowCount(); ++row) - uiGeneralTab.twLog->item(row, 1)->setText(QString()); + uiGeneralTab.twLog->item(row, 1)->setText(""); const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); diff --git a/src/kdefrontend/dockwidgets/XYFourierFilterCurveDock.cpp b/src/kdefrontend/dockwidgets/XYFourierFilterCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYFourierFilterCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYFourierFilterCurveDock.cpp @@ -138,8 +138,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -194,7 +194,7 @@ list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" - <<"LiveDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; + <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); @@ -461,7 +461,7 @@ uiGeneralTab.sbCutoff->setDecimals(6); uiGeneralTab.sbCutoff->setMaximum(1.0); uiGeneralTab.sbCutoff->setSingleStep(0.01); - uiGeneralTab.sbCutoff->setSuffix(QString()); + uiGeneralTab.sbCutoff->setSuffix(""); switch (oldUnit) { case nsl_filter_cutoff_unit_frequency: uiGeneralTab.sbCutoff->setValue(oldValue/f); @@ -477,7 +477,7 @@ uiGeneralTab.sbCutoff->setDecimals(0); uiGeneralTab.sbCutoff->setSingleStep(1); uiGeneralTab.sbCutoff->setMaximum(n); - uiGeneralTab.sbCutoff->setSuffix(QString()); + uiGeneralTab.sbCutoff->setSuffix(""); switch (oldUnit) { case nsl_filter_cutoff_unit_frequency: uiGeneralTab.sbCutoff->setValue(oldValue*n/f); @@ -538,7 +538,7 @@ uiGeneralTab.sbCutoff2->setDecimals(6); uiGeneralTab.sbCutoff2->setMaximum(1.0); uiGeneralTab.sbCutoff2->setSingleStep(0.01); - uiGeneralTab.sbCutoff2->setSuffix(QString()); + uiGeneralTab.sbCutoff2->setSuffix(""); switch (oldUnit) { case nsl_filter_cutoff_unit_frequency: uiGeneralTab.sbCutoff2->setValue(oldValue/f); @@ -554,7 +554,7 @@ uiGeneralTab.sbCutoff2->setDecimals(0); uiGeneralTab.sbCutoff2->setSingleStep(1); uiGeneralTab.sbCutoff2->setMaximum(n); - uiGeneralTab.sbCutoff2->setSuffix(QString()); + uiGeneralTab.sbCutoff2->setSuffix(""); switch (oldUnit) { case nsl_filter_cutoff_unit_frequency: uiGeneralTab.sbCutoff2->setValue(oldValue*n/f); diff --git a/src/kdefrontend/dockwidgets/XYFourierTransformCurveDock.cpp b/src/kdefrontend/dockwidgets/XYFourierTransformCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYFourierTransformCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYFourierTransformCurveDock.cpp @@ -122,8 +122,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -164,7 +164,7 @@ void XYFourierTransformCurveDock::setModel() { QList list; list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" - <<"LiveDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; + <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp b/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp @@ -131,8 +131,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -181,7 +181,7 @@ list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" - <<"LiveDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; + <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/dockwidgets/XYInterpolationCurveDock.cpp b/src/kdefrontend/dockwidgets/XYInterpolationCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYInterpolationCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYInterpolationCurveDock.cpp @@ -153,8 +153,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -214,7 +214,7 @@ list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" - <<"LiveDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; + <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp b/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp @@ -144,8 +144,8 @@ uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); - uiGeneralTab.leName->setText(QString()); - uiGeneralTab.leComment->setText(QString()); + uiGeneralTab.leName->setText(""); + uiGeneralTab.leComment->setText(""); } //show the properties of the first curve @@ -200,7 +200,7 @@ list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" - <<"LiveDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; + <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); diff --git a/src/kdefrontend/matrix/MatrixFunctionDialog.h b/src/kdefrontend/matrix/MatrixFunctionDialog.h --- a/src/kdefrontend/matrix/MatrixFunctionDialog.h +++ b/src/kdefrontend/matrix/MatrixFunctionDialog.h @@ -3,7 +3,7 @@ Project : LabPlot Description : Dialog for generating matrix values from a mathematical function -------------------------------------------------------------------- - Copyright : (C) 2015-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -39,13 +39,11 @@ public: explicit MatrixFunctionDialog(Matrix*, QWidget* parent = nullptr); - ~MatrixFunctionDialog(); private: Ui::MatrixFunctionWidget ui; Matrix* m_matrix; QPushButton* m_okButton; - private slots: void generate(); void checkValues(); diff --git a/src/kdefrontend/matrix/MatrixFunctionDialog.cpp b/src/kdefrontend/matrix/MatrixFunctionDialog.cpp --- a/src/kdefrontend/matrix/MatrixFunctionDialog.cpp +++ b/src/kdefrontend/matrix/MatrixFunctionDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : Dialog for generating matrix values from a mathematical function ------------------------------------------------------------------------ - Copyright : (C) 2015-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -32,31 +32,28 @@ #include "kdefrontend/widgets/ConstantsWidget.h" #include "kdefrontend/widgets/FunctionsWidget.h" +extern "C" { +#include "backend/gsl/parser.h" +} +#include + #include #include #include #include #include #include -#include #ifndef NDEBUG #include #endif -#include -#include - -extern "C" { -#include "backend/gsl/parser.h" -} -#include - /*! \class MatrixFunctionDialog \brief Dialog for generating matrix values from a mathematical function. \ingroup kdefrontend */ + MatrixFunctionDialog::MatrixFunctionDialog(Matrix* m, QWidget* parent) : QDialog(parent), m_matrix(m) { Q_ASSERT(m_matrix); setWindowTitle(i18nc("@title:window", "Function Values")); @@ -80,7 +77,7 @@ ui.teEquation->setPlainText(m_matrix->formula()); QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - ui.gridLayout_2->addWidget(btnBox, 3, 0, 1, 3); + ui.gridLayout_2->addWidget(btnBox); m_okButton = btnBox->button(QDialogButtonBox::Ok); connect(btnBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &MatrixFunctionDialog::close); @@ -95,19 +92,7 @@ connect(ui.tbFunctions, &QToolButton::clicked, this, &MatrixFunctionDialog::showFunctions); connect(m_okButton, &QPushButton::clicked, this, &MatrixFunctionDialog::generate); - //restore saved settings if available - create(); // ensure there's a window created - KConfigGroup conf(KSharedConfig::openConfig(), "MatrixFunctionDialog"); - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(300, 0).expandedTo(minimumSize())); -} - -MatrixFunctionDialog::~MatrixFunctionDialog() { - KConfigGroup conf(KSharedConfig::openConfig(), "MatrixFunctionDialog"); - KWindowConfig::saveWindowSize(windowHandle(), conf); + resize(QSize(300,0).expandedTo(minimumSize())); } void MatrixFunctionDialog::checkValues() { diff --git a/src/kdefrontend/spreadsheet/AddSubtractValueDialog.h b/src/kdefrontend/spreadsheet/AddSubtractValueDialog.h --- a/src/kdefrontend/spreadsheet/AddSubtractValueDialog.h +++ b/src/kdefrontend/spreadsheet/AddSubtractValueDialog.h @@ -33,7 +33,6 @@ class Column; class Spreadsheet; -class Matrix; class QPushButton; class AddSubtractValueDialog : public QDialog { @@ -43,26 +42,19 @@ enum Operation {Add, Subtract, Multiply, Divide}; explicit AddSubtractValueDialog(Spreadsheet*, Operation, QWidget* parent = nullptr); - explicit AddSubtractValueDialog(Matrix*, Operation, QWidget* parent = nullptr); ~AddSubtractValueDialog() override; void setColumns(QVector); - void setMatrices(); private: - void init(); - void generateForColumns(); - void generateForMatrices(); - QString getMessage(QString); - Ui::AddSubtractValueWidget ui; QVector m_columns; - Spreadsheet* m_spreadsheet = nullptr; - Matrix* m_matrix = nullptr; + Spreadsheet* m_spreadsheet; QPushButton* m_okButton; Operation m_operation; private slots: void generate(); + void valueChanged(); }; #endif diff --git a/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp b/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp --- a/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp +++ b/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp @@ -30,11 +30,9 @@ #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" -#include "backend/matrix/Matrix.h" #include #include -#include #include #include @@ -51,46 +49,6 @@ m_spreadsheet(s), m_operation(op) { Q_ASSERT(s != nullptr); - init(); - - switch (m_operation) { - case Add: - m_okButton->setToolTip(i18n("Add the specified value to column values")); - break; - case Subtract: - m_okButton->setToolTip(i18n("Subtract the specified value from column values")); - break; - case Multiply: - m_okButton->setToolTip(i18n("Multiply column values by the specified value")); - break; - case Divide: - m_okButton->setToolTip(i18n("Divide column values by the specified value")); - break; - } -} - -AddSubtractValueDialog::AddSubtractValueDialog(Matrix* m, Operation op, QWidget* parent) : QDialog(parent), - m_matrix(m), m_operation(op) { - Q_ASSERT(m != nullptr); - - init(); - - switch (m_operation) { - case Add: - m_okButton->setToolTip(i18n("Add the specified value to matrix values")); - break; - case Subtract: - m_okButton->setToolTip(i18n("Subtract the specified value from matrix values")); - break; - case Multiply: - m_okButton->setToolTip(i18n("Multiply matrix values by the specified value")); - break; - case Divide: - m_okButton->setToolTip(i18n("Divide matrix values by the specified value")); - } -} - -void AddSubtractValueDialog::init() { switch (m_operation) { case Add: setWindowTitle(i18nc("@title:window", "Add Value")); @@ -116,15 +74,19 @@ switch (m_operation) { case Add: m_okButton->setText(i18n("&Add")); + m_okButton->setToolTip(i18n("Add the specified value to column values")); break; case Subtract: m_okButton->setText(i18n("&Subtract")); + m_okButton->setToolTip(i18n("Subtract the specified value from column values")); break; case Multiply: m_okButton->setText(i18n("&Multiply")); + m_okButton->setToolTip(i18n("Multiply column values by the specified value")); break; case Divide: m_okButton->setText(i18n("&Divide")); + m_okButton->setToolTip(i18n("Divide column values by the specified value")); break; } @@ -132,15 +94,11 @@ connect(btnBox, &QDialogButtonBox::accepted, this, &AddSubtractValueDialog::accept); connect(btnBox, &QDialogButtonBox::rejected, this, &AddSubtractValueDialog::reject); - connect(ui.leValue, &QLineEdit::textChanged, this, [=]() {m_okButton->setEnabled(!ui.leValue->text().isEmpty());}); - //restore saved settings if available - create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "AddSubtractValueDialog"); - if (conf.exists()) { + if (conf.exists()) KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else + else resize(QSize(300, 0).expandedTo(minimumSize())); } @@ -187,63 +145,38 @@ } } } + + valueChanged(); } -void AddSubtractValueDialog::setMatrices() { - if (m_matrix->mode() == AbstractColumn::Integer) { - ui.lTimeValue->setVisible(false); - ui.dateTimeEdit->setVisible(false); - ui.leValue->setValidator(new QIntValidator(ui.leValue)); - ui.leValue->setText(QString::number(m_matrix->cell(0,0))); - } else if (m_matrix->mode() == AbstractColumn::Numeric) { - ui.lTimeValue->setVisible(false); - ui.dateTimeEdit->setVisible(false); - ui.leValue->setValidator(new QDoubleValidator(ui.leValue)); - ui.leValue->setText(QString::number(m_matrix->cell(0,0))); - } else { //datetime - ui.lValue->setVisible(false); - ui.leValue->setVisible(false); - } +void AddSubtractValueDialog::valueChanged() { + } /*! * generates new values in the selected columns by adding/subtracting the value provided in this dialog * from every column element. */ +void AddSubtractValueDialog::generate() { + Q_ASSERT(m_spreadsheet); -QString AddSubtractValueDialog::getMessage(QString name) -{ - QString msg = ""; + WAIT_CURSOR; + QString msg; QString value = ui.leValue->text(); switch (m_operation) { case Add: - msg = i18n("%1: add %2 to column values", name, value); + msg = i18n("%1: add %2 to column values", m_spreadsheet->name(), value); break; case Subtract: - msg = i18n("%1: subtract %2 from column values", name, value); + msg = i18n("%1: subtract %2 from column values", m_spreadsheet->name(), value); break; case Multiply: - msg = i18n("%1: multiply column values by %2", name, value); + msg = i18n("%1: multiply column values by %2", m_spreadsheet->name(), value); break; case Divide: - msg = i18n("%1: divide column values by %2", name, value); + msg = i18n("%1: divide column values by %2", m_spreadsheet->name(), value); break; } - return msg; -} - -void AddSubtractValueDialog::generate() { - if(m_spreadsheet != nullptr) - generateForColumns(); - else if(m_matrix != nullptr) - generateForMatrices(); -} - -void AddSubtractValueDialog::generateForColumns() { - Q_ASSERT(m_spreadsheet); - - WAIT_CURSOR; - QString msg = getMessage(m_spreadsheet->name()); m_spreadsheet->beginMacro(msg); AbstractColumn::ColumnMode mode = m_columns.first()->columnMode(); @@ -345,113 +278,3 @@ RESET_CURSOR; } - -void AddSubtractValueDialog::generateForMatrices() { - Q_ASSERT(m_matrix); - - WAIT_CURSOR; - QString msg = getMessage(m_matrix->name()); - m_matrix->beginMacro(msg); - - AbstractColumn::ColumnMode mode = m_matrix->mode(); - - const int rows = m_matrix->rowCount(); - const int cols = m_matrix->columnCount(); - if (mode == AbstractColumn::Integer) { - int new_data; - int value = ui.leValue->text().toInt(); - - switch (m_operation) { - case Subtract: - value *= -1; - //fall through - case Add: - for (int i = 0; icell(i,j); - new_data += value; - m_matrix->setCell(i,j,new_data); - } - break; - case Multiply: - for (int i = 0; icell(i,j); - new_data *= value; - m_matrix->setCell(i,j,new_data); - } - break; - case Divide: - for (int i = 0; icell(i,j); - new_data /= value; - m_matrix->setCell(i,j,new_data); - } - break; - } - } else if (mode == AbstractColumn::Numeric) { - double new_data; - double value = ui.leValue->text().toDouble(); - switch (m_operation) { - case Subtract: - value *= -1.; - //fall through - case Add: - for (int i = 0; icell(i,j); - new_data += value; - m_matrix->setCell(i,j,new_data); - } - break; - case Multiply: - for (int i = 0; icell(i,j); - new_data *= value; - m_matrix->setCell(i,j,new_data); - } - break; - case Divide: - for (int i = 0; icell(i,j); - new_data /= value; - m_matrix->setCell(i,j,new_data); - } - break; - } - } else { //datetime - QDateTime new_data; - quint64 value = ui.dateTimeEdit->dateTime().toMSecsSinceEpoch(); - switch (m_operation) { - case Subtract: - value *= -1.; - //fall through - case Add: - for (int i = 0; icell(i,j)).toMSecsSinceEpoch(); - new_data = QDateTime::fromMSecsSinceEpoch(data + value); - m_matrix->setCell(i,j,new_data); - } - break; - case Multiply: - case Divide: - break; - } - } - - m_matrix->endMacro(); - - RESET_CURSOR; - -} diff --git a/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.h b/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.h --- a/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.h +++ b/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.h @@ -3,7 +3,7 @@ Project : LabPlot Description : export spreadsheet dialog -------------------------------------------------------------------- - Copyright : (C) 2014-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2014-2018 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -94,6 +94,7 @@ void formatChanged(int); void fileNameChanged(const QString&); void fitsExportToChanged(int); + void loadSettings(); }; #endif diff --git a/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.cpp b/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.cpp --- a/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.cpp +++ b/src/kdefrontend/spreadsheet/ExportSpreadsheetDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : export spreadsheet dialog -------------------------------------------------------------------- - Copyright : (C) 2014-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2014-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include #include @@ -67,7 +67,9 @@ m_okButton = btnBox->button(QDialogButtonBox::Ok); m_cancelButton = btnBox->button(QDialogButtonBox::Cancel); - ui->leFileName->setCompleter(new QCompleter(new QDirModel, this)); + auto* completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui->leFileName->setCompleter(completer); ui->cbFormat->addItem("ASCII", ASCII); ui->cbFormat->addItem("Binary", Binary); @@ -106,7 +108,11 @@ setWindowTitle(i18nc("@title:window", "Export Spreadsheet")); setWindowIcon(QIcon::fromTheme("document-export-database")); - //restore saved settings if available + QTimer::singleShot(0, this, &ExportSpreadsheetDialog::loadSettings); +} + +void ExportSpreadsheetDialog::loadSettings() { + //restore saved settings KConfigGroup conf(KSharedConfig::openConfig(), "ExportSpreadsheetDialog"); KWindowConfig::restoreWindowSize(windowHandle(), conf); ui->cbFormat->setCurrentIndex(conf.readEntry("Format", 0)); @@ -126,13 +132,6 @@ ui->gbOptions->setVisible(m_showOptions); m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : m_showOptionsButton->setText(i18n("Show Options")); - - create(); // ensure there's a window created - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); } ExportSpreadsheetDialog::~ExportSpreadsheetDialog() { diff --git a/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp --- a/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include @@ -54,6 +53,7 @@ \ingroup kdefrontend */ + FunctionValuesDialog::FunctionValuesDialog(Spreadsheet* s, QWidget* parent) : QDialog(parent), m_spreadsheet(s) { Q_ASSERT(s != nullptr); setWindowTitle(i18nc("@title:window", "Function Values")); @@ -101,13 +101,11 @@ connect(m_okButton, &QPushButton::clicked, this, &FunctionValuesDialog::generate); //restore saved settings if available - create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "FunctionValuesDialog"); - if (conf.exists()) { + if (conf.exists()) KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); + else + resize(QSize(300, 0).expandedTo(minimumSize())); } FunctionValuesDialog::~FunctionValuesDialog() { diff --git a/src/kdefrontend/spreadsheet/PlotDataDialog.h b/src/kdefrontend/spreadsheet/PlotDataDialog.h --- a/src/kdefrontend/spreadsheet/PlotDataDialog.h +++ b/src/kdefrontend/spreadsheet/PlotDataDialog.h @@ -87,6 +87,7 @@ void plot(); void curvePlacementChanged(); void plotPlacementChanged(); + void loadSettings(); }; #endif diff --git a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp --- a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp +++ b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : Dialog for generating plots for the spreadsheet data -------------------------------------------------------------------- - Copyright : (C) 2017-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -54,7 +54,7 @@ #include #include -#include +#include #include #include @@ -148,21 +148,24 @@ connect(cbExistingPlots, &TreeViewComboBox::currentModelIndexChanged, this, &PlotDataDialog::checkOkButton); connect(cbExistingWorksheets, &TreeViewComboBox::currentModelIndexChanged, this, &PlotDataDialog::checkOkButton); + QTimer::singleShot(0, this, &PlotDataDialog::loadSettings); +} + +void PlotDataDialog::loadSettings() { //restore saved settings if available - create(); // ensure there's a window created - KConfigGroup conf(KSharedConfig::openConfig(), "PlotDataDialog"); + QApplication::processEvents(QEventLoop::AllEvents, 0); + const KConfigGroup conf(KSharedConfig::openConfig(), "PlotDataDialog"); if (conf.exists()) { + KWindowConfig::restoreWindowSize(windowHandle(), conf); + int index = conf.readEntry("CurvePlacement", 0); if (index == 2) ui->rbCurvePlacement2->setChecked(true); index = conf.readEntry("PlotPlacement", 0); if (index == 2) ui->rbPlotPlacement2->setChecked(true); if (index == 3) ui->rbPlotPlacement3->setChecked(true); - - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 } else - resize(QSize(0, 0).expandedTo(minimumSize())); + resize( QSize(0,0).expandedTo(minimumSize()) ); processColumns(); plotPlacementChanged(); diff --git a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp --- a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : Dialog for generating non-uniformly distributed random numbers -------------------------------------------------------------------- - Copyright : (C) 2014-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2014 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ @@ -32,10 +32,9 @@ #include "backend/spreadsheet/Spreadsheet.h" #include -#include #include #include -#include +#include #include #include @@ -101,7 +100,6 @@ connect(buttonBox, &QDialogButtonBox::accepted, this, &RandomValuesDialog::generate); //restore saved settings if available - create(); // ensure there's a window created const KConfigGroup conf(KSharedConfig::openConfig(), "RandomValuesDialog"); if (conf.exists()) { ui.cbDistribution->setCurrentIndex(conf.readEntry("Distribution", 0)); @@ -111,7 +109,6 @@ ui.leParameter3->setText(conf.readEntry("Parameter3")); KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 } else { //Gaussian distribution as default this->distributionChanged(0); @@ -323,7 +320,7 @@ ui.lParameter2->hide(); ui.leParameter2->hide(); if (dist == nsl_sf_stats_bernoulli) - ui.lFunc->setText(QString()); + ui.lFunc->setText(""); else ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("p ="); diff --git a/src/kdefrontend/spreadsheet/StatisticsDialog.h b/src/kdefrontend/spreadsheet/StatisticsDialog.h --- a/src/kdefrontend/spreadsheet/StatisticsDialog.h +++ b/src/kdefrontend/spreadsheet/StatisticsDialog.h @@ -4,7 +4,7 @@ Description : Dialog showing statistics for column values -------------------------------------------------------------------- Copyright : (C) 2016-2017 by Fabian Kristof (fkristofszabolcs@gmail.com) - Copyright : (C) 2016-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -51,6 +51,7 @@ private slots: void currentTabChanged(int); + void loadSettings(); }; #endif diff --git a/src/kdefrontend/spreadsheet/StatisticsDialog.cpp b/src/kdefrontend/spreadsheet/StatisticsDialog.cpp --- a/src/kdefrontend/spreadsheet/StatisticsDialog.cpp +++ b/src/kdefrontend/spreadsheet/StatisticsDialog.cpp @@ -4,7 +4,7 @@ Description : Dialog showing statistics for column values -------------------------------------------------------------------- Copyright : (C) 2016-2017 by Fabian Kristof (fkristofszabolcs@gmail.com)) - Copyright : (C) 2016-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2016-2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -30,18 +30,16 @@ #include "StatisticsDialog.h" #include "backend/core/column/Column.h" -#include -#include -#include #include -#include -#include - +#include +#include #include -#include +#include +#include #include - +#include #include +#include StatisticsDialog::StatisticsDialog(const QString& title, QWidget* parent) : QDialog(parent), m_twStatistics(new QTabWidget) { @@ -61,7 +59,6 @@ setLayout(layout); setWindowTitle(title); - setWindowIcon(QIcon::fromTheme("view-statistics")); setAttribute(Qt::WA_DeleteOnClose); const QString htmlColor = (palette().color(QPalette::Base).lightness() < 128) ? QLatin1String("#5f5f5f") : QLatin1String("#D1D1D1"); @@ -180,14 +177,16 @@ ""); connect(m_twStatistics, &QTabWidget::currentChanged, this, &StatisticsDialog::currentTabChanged); + QTimer::singleShot(0, this, &StatisticsDialog::loadSettings); +} +void StatisticsDialog::loadSettings() { //restore saved settings if available - create(); // ensure there's a window created + QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "StatisticsDialog"); - if (conf.exists()) { + if (conf.exists()) KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else + else resize(QSize(490, 520)); } diff --git a/src/kdefrontend/ui/datasources/asciioptionswidget.ui b/src/kdefrontend/ui/datasources/asciioptionswidget.ui --- a/src/kdefrontend/ui/datasources/asciioptionswidget.ui +++ b/src/kdefrontend/ui/datasources/asciioptionswidget.ui @@ -6,8 +6,8 @@ 0 0 - 671 - 403 + 636 + 399 @@ -24,7 +24,7 @@ - + @@ -37,7 +37,7 @@ - + Qt::Horizontal @@ -57,7 +57,7 @@ - + true @@ -71,7 +71,7 @@ - + @@ -81,7 +81,7 @@ - + @@ -127,14 +127,14 @@ - - + + - Create timestamp column + Skip empty parts - + Removes the whitespaces from the start and the end, and replaces each sequence of internal whitespaces with a single space. @@ -147,7 +147,7 @@ - + Remove quotes @@ -190,7 +190,7 @@ - + true @@ -203,13 +203,6 @@ - - - - Skip empty parts - - - diff --git a/src/kdefrontend/ui/datasources/databasemanagerwidget.ui b/src/kdefrontend/ui/datasources/databasemanagerwidget.ui --- a/src/kdefrontend/ui/datasources/databasemanagerwidget.ui +++ b/src/kdefrontend/ui/datasources/databasemanagerwidget.ui @@ -212,24 +212,12 @@ - QFrame::NoFrame + QFrame::StyledPanel QFrame::Raised - - 0 - - - 0 - - - 0 - - - 0 - diff --git a/src/kdefrontend/ui/datasources/importfilewidget.ui b/src/kdefrontend/ui/datasources/importfilewidget.ui --- a/src/kdefrontend/ui/datasources/importfilewidget.ui +++ b/src/kdefrontend/ui/datasources/importfilewidget.ui @@ -6,8 +6,8 @@ 0 0 - 633 - 1296 + 674 + 1593 @@ -29,88 +29,227 @@ Data Source - - - - - - - Connection: - - - - - - - - - - Field: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - + + - Port: + - - + + Qt::Horizontal - - QSizePolicy::Fixed - - 10 - 23 + 40 + 0 - - - - QAbstractItemView::NoEditTriggers + + + + + 0 + 0 + - - false + + - - - 16 - 16 - + + true + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + Available + + + + + + + + + 0 + 0 + + + + + + + + + + + 12 + + + 6 + + + 6 + + + 6 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QoS 0 + + + + + QoS 1 + + + + + QoS 2 + + + + + + + + + + true + + + + + + + + 0 + 0 + + + + + Subscribed + + + + + + + + + 0 + 0 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + + - - + + + + + + + + - LWT: + Filter: - - + + - - - - - 0 - 0 - - + + - + Connection: @@ -121,43 +260,48 @@ - - - - false - - - - 0 - 0 - - - + + - - - - false - + + 0 0 - - Show file info - - - - - false + + + + Port: + + + + + + + Host: + + + + + + Topics: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 @@ -165,29 +309,35 @@ - Manage filters + Select the file to import - - - - - + + - Source: + Baud rate: - - - - Port: + + + + Qt::Horizontal - + + QSizePolicy::Fixed + + + + 10 + 23 + + + @@ -218,31 +368,22 @@ - - + + - Host: + Source: - - - - - 0 - 0 - - - - Select the file to import - + + - + Type: - - + + @@ -254,17 +395,22 @@ - - - - Baud rate: + + + + false + + + + 0 + 0 + + + + Show file info - - - - - Type: + @@ -275,60 +421,85 @@ - - - - false - - - Save the current filter settings - - - - - + + - Filter: + Port: - - - - Qt::Horizontal + + + + QAbstractItemView::NoEditTriggers - + + false + + - 40 - 0 + 16 + 16 - + - - + + + + false + + + + 0 + 0 + + + + Manage filters + - Topics: + + + + + + + + Field: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - QFrame::NoFrame + + + + false - - QFrame::Plain + + Save the current filter settings - - + + + + false + + + + 0 + 0 + + + + + + - + LWT: diff --git a/src/kdefrontend/ui/datasources/mqttsubscriptionwidget.ui b/src/kdefrontend/ui/datasources/mqttsubscriptionwidget.ui deleted file mode 100644 --- a/src/kdefrontend/ui/datasources/mqttsubscriptionwidget.ui +++ /dev/null @@ -1,214 +0,0 @@ - - - MQTTSubscriptionWidget - - - - 0 - 0 - 762 - 362 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - - - - - - 0 - 0 - - - - - Subscribed - - - - - - - - 12 - - - 6 - - - 6 - - - 6 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - QoS 0 - - - - - QoS 1 - - - - - QoS 2 - - - - - - - - - - true - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - - Available - - - - - - - - - - - - diff --git a/src/kdefrontend/ui/dockwidgets/axisdock.ui b/src/kdefrontend/ui/dockwidgets/axisdock.ui --- a/src/kdefrontend/ui/dockwidgets/axisdock.ui +++ b/src/kdefrontend/ui/dockwidgets/axisdock.ui @@ -29,7 +29,7 @@ - 3 + 0 @@ -517,255 +517,185 @@ Ticks - - - - - 75 - true - + + + + Qt::Horizontal - - Minor ticks + + QSizePolicy::Fixed - - - - - - Increment: + + + 17 + 24 + - + - - + + + + + - Direction: + Type: - - + + - - + + - Increment: + Number: - - + + - Column: - - - - - - - - 0 - 0 - - - - The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - - % - - - 0 - - - 100 - - - 10 - - - 100 + Increment: - - - 0.000000000000000 - - - 999999999999999.000000000000000 - - + - - - - pt - - - 0.500000000000000 - - - - - + + - Length: + Column: - - + + Qt::Vertical + + QSizePolicy::Fixed + - 48 - 301 + 20 + 18 - - + + - Width: + Style: - - - - Column: + + + + + 0 + 0 + - - + + - Number: + Color: - - - - - - - Type: + + + + + 0 + 0 + - - + + - Opacity: + Width: - - - - Increment: + + + + pt + + + 0.500000000000000 - + - Color: + Length: - - - - Style: + + + + pt - - - - - - - - - - 0 - 0 - + + 0.500000000000000 - - + + Opacity: - - - - Number: - - - - - + + 0 0 - - - - - - - 75 - true - + + The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - Major ticks + + % - - - - - - Direction: + + - - - - - - pt + + 0 + + + 100 - 0.500000000000000 + 10 - - - - - - Color: + + 100 - + Qt::Vertical @@ -781,31 +711,54 @@ - - + + + + Direction: + + + + + + + + Type: - - + + + + + - Style: + Number: - - - - pt + + + + + + + Increment: - - 0.500000000000000 + + + + + + + + + Column: - + Qt::Vertical @@ -821,42 +774,31 @@ - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 17 - 24 - + + + + Style: - - - - + - - - - Qt::Vertical - - - QSizePolicy::Fixed + + + + + 0 + 0 + - - - 20 - 18 - + + + + + + Color: - + - + @@ -866,8 +808,15 @@ - - + + + + Width: + + + + + pt @@ -876,8 +825,32 @@ - - + + + + Length: + + + + + + + pt + + + 0.500000000000000 + + + + + + + Opacity: + + + + + 0 @@ -890,9 +863,6 @@ % - - - 0 @@ -907,44 +877,49 @@ - - - - - 0 - 0 - + + + + Qt::Vertical - - - - - - - - - Length: + + + 48 + 301 + - + - - + + + + + 75 + true + + - Width: + Major ticks - - + + + + + 75 + true + + - Increment: + Minor ticks - - - - 999999999999999.000000000000000 + + + + Direction: @@ -1163,10 +1138,10 @@ ° - -180 + -360 - 180 + 360 10 @@ -1498,16 +1473,16 @@ - - KFontRequester - QWidget -
kfontrequester.h
-
KComboBox QComboBox
kcombobox.h
+ + KFontRequester + QWidget +
kfontrequester.h
+
KColorButton QPushButton diff --git a/src/kdefrontend/ui/dockwidgets/boxplotdock.ui b/src/kdefrontend/ui/dockwidgets/boxplotdock.ui deleted file mode 100644 --- a/src/kdefrontend/ui/dockwidgets/boxplotdock.ui +++ /dev/null @@ -1,977 +0,0 @@ - - - BoxPlotDock - - - - 0 - 0 - 421 - 834 - - - - - 0 - - - - - 0 - - - - General - - - - - - Name: - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 13 - 23 - - - - - - - - - - - Comment: - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 72 - 18 - - - - - - - - - 75 - true - - - - Data - - - - - - - Column: - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 72 - 18 - - - - - - - - Whiskers: - - - - - - - - - - Direction: - - - - - - - - - - Qt::Vertical - - - - 20 - 420 - - - - - - - - Visible - - - - - - - - Box - - - - - - - 75 - true - - - - Filling - - - - - - - Type: - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 13 - 23 - - - - - - - - - 0 - 0 - - - - - - - - Style: - - - - - - - - - - Style: - - - - - - - - - - First color: - - - - - - - - - - Second color: - - - - - - - - - - Opacity: - - - - - - - - 0 - 0 - - - - The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - - % - - - 0 - - - 100 - - - 10 - - - 100 - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 72 - 18 - - - - - - - - - 75 - true - - - - Border - - - - - - - Style: - - - - - - - - - - Color: - - - - - - - - 0 - 0 - - - - - - - - Width: - - - - - - - pt - - - 0.500000000000000 - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 72 - 18 - - - - - - - - Position: - - - - - - - - - - Style: - - - - - - - Color: - - - - - - - Width: - - - - - - - Opacity: - - - - - - - Qt::Vertical - - - - 24 - 136 - - - - - - - - - - - - 0 - 0 - - - - - - - - pt - - - 0.500000000000000 - - - - - - - - 75 - true - - - - Reference - - - - - - - - 0 - 0 - - - - The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - - % - - - 0 - - - 100 - - - 10 - - - 100 - - - - - - - - Markers - - - - - - - 75 - true - - - - Symbol - - - - - - - Outliers: - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 23 - - - - - - - - - 0 - 0 - - - - - - - - Mean: - - - - - - - - 0 - 0 - - - - - - - - Size: - - - - - - - - - - pt - - - 0.500000000000000 - - - - - - - Rotation: - - - - - - - - 0 - 0 - - - - - - - ° - - - -360 - - - 360 - - - 10 - - - 0 - - - - - - - Opacity: - - - - - - - - 0 - 0 - - - - The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - - % - - - 0 - - - 100 - - - 10 - - - 100 - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 72 - 18 - - - - - - - - - 75 - true - - - - Filling - - - - - - - Style: - - - - - - - - - - Color: - - - - - - - - 0 - 0 - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 72 - 18 - - - - - - - - - 75 - true - - - - Border - - - - - - - Style: - - - - - - - - - - Color: - - - - - - - - 0 - 0 - - - - - - - - Width: - - - - - - - pt - - - 0.500000000000000 - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 72 - 0 - - - - - - - - - Whiskers - - - - - - pt - - - 0.500000000000000 - - - - - - - Type: - - - - - - - pt - - - 0.500000000000000 - - - - - - - Style: - - - - - - - Color: - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - - - - - Opacity: - - - - - - - - - - Width: - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 104 - - - - - - - - - 75 - true - - - - Format - - - - - - - Cap size: - - - - - - - - 0 - 0 - - - - The opacity ranges from 0 to 100, where 0 is fully transparent and 100 is fully opaque. - - - % - - - 0 - - - 100 - - - 10 - - - 100 - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 13 - 18 - - - - - - - - - - - - - KComboBox - QComboBox -
kcombobox.h
-
- - KColorButton - QPushButton -
kcolorbutton.h
-
-
- - -
diff --git a/src/kdefrontend/ui/dockwidgets/livedatadock.ui b/src/kdefrontend/ui/dockwidgets/livedatadock.ui --- a/src/kdefrontend/ui/dockwidgets/livedatadock.ui +++ b/src/kdefrontend/ui/dockwidgets/livedatadock.ui @@ -11,13 +11,6 @@ - - - - true - - - @@ -31,67 +24,125 @@ - + Read: - - + + Qt::Horizontal + + QSizePolicy::Fixed + - 318 + 10 20 - - + + - + 0 0 + + + Continuously Fixed + + + + + From End + + + + + Till the End + + + + + Read Whole File + + + + + + - + Sample size: - + Keep last values: - - - - - 0 - 0 - + + + + Update: + + + + + + + Periodically + + + + + On New Data + + + + + + - Update Will Message + Update interval: - - + + + + ms + + + 20 + + + 3600000 + + + 1000 + + + + + - Update: + Update Now - + Qt::Vertical @@ -107,34 +158,204 @@ - - + + + + + 75 + true + + - LWT: + Topics + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - + + + + + 0 + 0 + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Available + + + + + + + + true + + + + + + + + Subscribed + + + + + + + + true + + + + + + + + + + + + + + 12 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QoS 0 + + + + + QoS 1 + + + + + QoS 2 + + + + + + + + + + + - Source: + LWT: - - + + - + 0 0 - Pause Reading + - + + + + Qt::Horizontal + + + + 318 + 20 + + + + + Qt::Vertical @@ -147,14 +368,7 @@ - - - - Update interval: - - - - + @@ -173,23 +387,7 @@ - - - - - 75 - true - - - - Topics - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - + false @@ -208,116 +406,30 @@ - - - - - Periodically - - - - - On New Data - - - - - - + + 0 0 - - - Continuously Fixed - - - - - From End - - - - - Till the End - - - - - Read Whole File - - - - - - - - Sample size: - - - - - - - Update Now - - - - - - - ms - - - 20 - - - 3600000 - - - 1000 - - - - - - Name: + Pause Reading - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - + - + 0 0 - - + + Update Will Message + diff --git a/src/kdefrontend/widgets/ConstantsWidget.cpp b/src/kdefrontend/widgets/ConstantsWidget.cpp --- a/src/kdefrontend/widgets/ConstantsWidget.cpp +++ b/src/kdefrontend/widgets/ConstantsWidget.cpp @@ -87,8 +87,8 @@ ui.lwConstants->setCurrentRow(0); ui.bInsert->setEnabled(true); } else { - ui.leValue->setText(QString()); - ui.lUnit->setText(QString()); + ui.leValue->setText(""); + ui.lUnit->setText(""); ui.bInsert->setEnabled(false); } } else { diff --git a/src/kdefrontend/widgets/DatapickerCurveWidget.cpp b/src/kdefrontend/widgets/DatapickerCurveWidget.cpp --- a/src/kdefrontend/widgets/DatapickerCurveWidget.cpp +++ b/src/kdefrontend/widgets/DatapickerCurveWidget.cpp @@ -128,8 +128,8 @@ ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } load(); diff --git a/src/kdefrontend/widgets/DatapickerImageWidget.cpp b/src/kdefrontend/widgets/DatapickerImageWidget.cpp --- a/src/kdefrontend/widgets/DatapickerImageWidget.cpp +++ b/src/kdefrontend/widgets/DatapickerImageWidget.cpp @@ -128,7 +128,10 @@ ui.leFileName->setClearButtonEnabled(true); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); - ui.leFileName->setCompleter(new QCompleter(new QDirModel, this)); + + auto* completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui.leFileName->setCompleter(completer); auto* editTabLayout = static_cast(ui.tEdit->layout()); editTabLayout->setContentsMargins(2,2,2,2); @@ -310,8 +313,8 @@ ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); - ui.leName->setText(QString()); - ui.leComment->setText(QString()); + ui.leName->setText(""); + ui.leComment->setText(""); } this->load(); diff --git a/src/kdefrontend/widgets/ExpressionTextEdit.cpp b/src/kdefrontend/widgets/ExpressionTextEdit.cpp --- a/src/kdefrontend/widgets/ExpressionTextEdit.cpp +++ b/src/kdefrontend/widgets/ExpressionTextEdit.cpp @@ -155,7 +155,7 @@ if (!m_isValid) setStyleSheet("QTextEdit{background: red;}"); else - setStyleSheet(QString()); + setStyleSheet(""); m_currentExpression = text; } @@ -223,7 +223,7 @@ const QString& token = tc.selectedText(); if (token.isEmpty()) { - setToolTip(QString()); + setToolTip(""); return; } @@ -244,7 +244,7 @@ static const QStringList& names = ExpressionParser::getInstance()->functionsNames(); setToolTip(functions.at(index) + " - " + names.at(index)); } else - setToolTip(QString()); + setToolTip(""); } KTextEdit::mouseMoveEvent(e); diff --git a/src/kdefrontend/widgets/FitParametersWidget.cpp b/src/kdefrontend/widgets/FitParametersWidget.cpp --- a/src/kdefrontend/widgets/FitParametersWidget.cpp +++ b/src/kdefrontend/widgets/FitParametersWidget.cpp @@ -410,7 +410,7 @@ if (invalid) le->setStyleSheet("QLineEdit{background: red;}"); else - le->setStyleSheet(QString()); + le->setStyleSheet(""); if (m_invalidRanges) emit parametersValid(false); diff --git a/src/kdefrontend/widgets/LabelWidget.cpp b/src/kdefrontend/widgets/LabelWidget.cpp --- a/src/kdefrontend/widgets/LabelWidget.cpp +++ b/src/kdefrontend/widgets/LabelWidget.cpp @@ -297,8 +297,8 @@ } else { //save an empty string instead of a html-string with empty body, if no text available in QTextEdit QString text; - if (ui.teLabel->toPlainText().isEmpty()) - text.clear(); + if (ui.teLabel->toPlainText() == "") + text = ""; else text = ui.teLabel->toHtml(); @@ -393,7 +393,7 @@ //when switching to the text mode, set the background color to white just for the case the latex code provided by the user //in the TeX-mode is not valid and the background was set to red (s.a. LabelWidget::labelTeXImageUpdated()) - ui.teLabel->setStyleSheet(QString()); + ui.teLabel->setStyleSheet(QLatin1String("")); } //no latex is available and the user switched to the text mode, @@ -403,7 +403,7 @@ ui.tbTexUsed->setToolTip(i18n("LaTeX typesetting not possible. Please check the settings.")); } else { ui.tbTexUsed->setEnabled(true); - ui.tbTexUsed->setToolTip(QString()); + ui.tbTexUsed->setToolTip(QLatin1String("")); } if (m_initializing) @@ -779,7 +779,7 @@ if (!valid) ui.teLabel->setStyleSheet(QLatin1String("QTextEdit{background: red;}")); else - ui.teLabel->setStyleSheet(QString()); + ui.teLabel->setStyleSheet(QLatin1String("")); } void LabelWidget::labelTeXFontChanged(const QFont& font) { diff --git a/src/kdefrontend/widgets/ThemesComboBox.cpp b/src/kdefrontend/widgets/ThemesComboBox.cpp --- a/src/kdefrontend/widgets/ThemesComboBox.cpp +++ b/src/kdefrontend/widgets/ThemesComboBox.cpp @@ -53,7 +53,7 @@ m_groupBox->hide(); m_groupBox->installEventFilter(this); - addItem(QString()); + addItem(""); setCurrentIndex(0); connect(m_view, &ThemesWidget::themeSelected, this, &ThemesComboBox::handleThemeChanged); diff --git a/src/kdefrontend/widgets/ThemesWidget.cpp b/src/kdefrontend/widgets/ThemesWidget.cpp --- a/src/kdefrontend/widgets/ThemesWidget.cpp +++ b/src/kdefrontend/widgets/ThemesWidget.cpp @@ -123,7 +123,7 @@ // else if (index.row() == model()->rowCount()-1) - emit themeSelected(QString()); //item with the string "None" was selected -> no theme + emit themeSelected(""); //item with the string "None" was selected -> no theme else emit themeSelected(themeName); } diff --git a/src/kdefrontend/worksheet/ExportWorksheetDialog.h b/src/kdefrontend/worksheet/ExportWorksheetDialog.h --- a/src/kdefrontend/worksheet/ExportWorksheetDialog.h +++ b/src/kdefrontend/worksheet/ExportWorksheetDialog.h @@ -3,7 +3,7 @@ Project : LabPlot Description : export worksheet dialog -------------------------------------------------------------------- - Copyright : (C) 2011-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -60,6 +60,7 @@ QPushButton* m_okButton; QPushButton* m_cancelButton; + private slots: void slotButtonClicked(QAbstractButton *); void okClicked(); @@ -67,6 +68,7 @@ void selectFile(); void formatChanged(int); void fileNameChanged(const QString&); + void loadSettings(); }; #endif diff --git a/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp b/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp --- a/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp +++ b/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp @@ -3,7 +3,7 @@ Project : LabPlot Description : export worksheet dialog -------------------------------------------------------------------- - Copyright : (C) 2011-2019 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include #include @@ -61,7 +61,9 @@ m_okButton = btnBox->button(QDialogButtonBox::Ok); m_cancelButton = btnBox->button(QDialogButtonBox::Cancel); - ui->leFileName->setCompleter(new QCompleter(new QDirModel, this)); + auto* completer = new QCompleter(this); + completer->setModel(new QDirModel); + ui->leFileName->setCompleter(completer); ui->bOpen->setIcon(QIcon::fromTheme(QLatin1String("document-open"))); @@ -91,8 +93,15 @@ setWindowTitle(i18nc("@title:window", "Export Worksheet")); setWindowIcon(QIcon::fromTheme(QLatin1String("document-export-database"))); - //restore saved settings if available + QTimer::singleShot(0, this, &ExportWorksheetDialog::loadSettings); +} + +void ExportWorksheetDialog::loadSettings() { + //restore saved setting KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); + + KWindowConfig::restoreWindowSize(windowHandle(), conf); + ui->cbFormat->setCurrentIndex(conf.readEntry("Format", 0)); ui->cbExportArea->setCurrentIndex(conf.readEntry("Area", 0)); ui->chkExportBackground->setChecked(conf.readEntry("Background", true)); @@ -101,13 +110,6 @@ m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : m_showOptionsButton->setText(i18n("Show Options")); ui->gbOptions->setVisible(m_showOptions); - - create(); // ensure there's a window created - if (conf.exists()) { - KWindowConfig::restoreWindowSize(windowHandle(), conf); - resize(windowHandle()->size()); // workaround for QTBUG-40584 - } else - resize(QSize(0, 0).expandedTo(minimumSize())); } ExportWorksheetDialog::~ExportWorksheetDialog() { diff --git a/src/labplot2.xml b/src/labplot2.xml --- a/src/labplot2.xml +++ b/src/labplot2.xml @@ -5,22 +5,16 @@ LabPlot2 Project File Fitxer de projecte LabPlot2 Fitxer de projecte LabPlot2 - Soubor projektu LabPlot2 - LabPlot2-Projektdatei - LabPlot2 Project File Archivo de proyecto de LabPlot2 LabPlot2-projektitiedosto - Fichier projet LabPlot2 Ficheiro de proxecto de LabPlot2 File di progetto LabPlot2 Projectbestand LabPlot - Plik projektu LabPlot2 Ficheiro de Projecto do LabPlot2 Arquivo de projeto do LabPlot2 Súbor projektu LabPlot2 Labplot2 projektfil файл проекту LabPlot2 - LabPlot2 專案檔 diff --git a/src/tools/TeXRenderer.cpp b/src/tools/TeXRenderer.cpp --- a/src/tools/TeXRenderer.cpp +++ b/src/tools/TeXRenderer.cpp @@ -135,25 +135,59 @@ // TEX -> PDF -> PNG QImage TeXRenderer::imageFromPDF(const QTemporaryFile& file, const int dpi, const QString& engine, bool* success) { QFileInfo fi(file.fileName()); - const QString& baseName = fi.completeBaseName(); - - // pdflatex: produce the PDF file QProcess latexProcess; #if defined(HAVE_WINDOWS) latexProcess.setNativeArguments("-interaction=batchmode " + file.fileName()); - latexProcess.start(engine, QStringList() << QString()); + latexProcess.start(engine, QStringList() << ""); #else latexProcess.start(engine, QStringList() << "-interaction=batchmode" << file.fileName()); #endif - - if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) { - WARN("pdflatex process failed, exit code = " << latexProcess.exitCode()); + if (!latexProcess.waitForFinished()) { + WARN("latex process failed."); *success = false; - QFile::remove(baseName + ".aux"); - QFile::remove(baseName + ".log"); + QFile::remove(fi.completeBaseName() + ".aux"); + QFile::remove(fi.completeBaseName() + ".log"); return QImage(); } +/// HEAD + //TODO: pdflatex doesn't come back with EX_OK +// if (latexProcess.exitCode() != 0) // skip if pdflatex failed +// return QImage(); + + // convert: PDF -> PNG +/// convertProcess.start("convert", QStringList() << "-density"<< QString::number(dpi) + 'x' + QString::number(dpi) +/// << fi.completeBaseName() + ".pdf" +/// << fi.completeBaseName() + ".png"); + + // clean up and read png file +/// if (convertProcess.waitForFinished()) { +/// QFile::remove(fi.completeBaseName()+".pdf"); + +/// QImage image; +/// image.load(fi.completeBaseName()+".png"); +/// QFile::remove(fi.completeBaseName()+".png"); + +/// return image; +/// } else { +/// QFile::remove(fi.completeBaseName()+".pdf"); +/// return QImage(); +/// } +/// } else { +/// qWarning()<<"pdflatex failed."< PNG QProcess convertProcess; #if defined(HAVE_WINDOWS) @@ -162,57 +196,49 @@ env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2"))); convertProcess.setProcessEnvironment(env); #endif - - const QStringList params{"-density", QString::number(dpi), baseName + ".pdf", baseName + ".png"}; - convertProcess.start("convert", params); - - if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) { - WARN("convert process failed, exit code = " << convertProcess.exitCode()); + convertProcess.start("convert", QStringList() << "-density" << QString::number(dpi) + 'x' + QString::number(dpi) + << fi.completeBaseName() + ".pdf" << fi.completeBaseName() + ".png"); + if (!convertProcess.waitForFinished()) { + WARN("convert process failed."); *success = false; - QFile::remove(baseName + ".aux"); - QFile::remove(baseName + ".log"); - QFile::remove(baseName + ".pdf"); + QFile::remove(fi.completeBaseName() + ".pdf"); return QImage(); } - // read png file - QImage image(baseName + ".png", "png"); + // read png file and clean up + QImage image; + image.load(fi.completeBaseName() + ".png"); // final clean up - QFile::remove(baseName + ".aux"); - QFile::remove(baseName + ".log"); - QFile::remove(baseName + ".pdf"); - QFile::remove(baseName + ".png"); + QFile::remove(fi.completeBaseName() + ".png"); + QFile::remove(fi.completeBaseName() + ".pdf"); - *success = true; return image; } // TEX -> DVI -> PS -> PNG QImage TeXRenderer::imageFromDVI(const QTemporaryFile& file, const int dpi, bool* success) { QFileInfo fi(file.fileName()); - const QString& baseName = fi.completeBaseName(); - - //latex: produce the DVI file QProcess latexProcess; latexProcess.start("latex", QStringList() << "-interaction=batchmode" << file.fileName()); - if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) { - WARN("latex process failed, exit code = " << latexProcess.exitCode()); - *success = false; - QFile::remove(baseName + ".aux"); - QFile::remove(baseName + ".log"); + if (!latexProcess.waitForFinished()) { + WARN("latex process failed."); + QFile::remove(fi.completeBaseName() + ".aux"); + QFile::remove(fi.completeBaseName() + ".log"); return QImage(); } + *success = (latexProcess.exitCode() == 0); + + QFile::remove(fi.completeBaseName() + ".aux"); + QFile::remove(fi.completeBaseName() + ".log"); + // dvips: DVI -> PS QProcess dvipsProcess; - dvipsProcess.start("dvips", QStringList() << "-E" << baseName); - if (!dvipsProcess.waitForFinished() || dvipsProcess.exitCode() != 0) { - WARN("dvips process failed, exit code = " << dvipsProcess.exitCode()); - *success = false; - QFile::remove(baseName + ".aux"); - QFile::remove(baseName + ".log"); - QFile::remove(baseName + ".dvi"); + dvipsProcess.start("dvips", QStringList() << "-E" << fi.completeBaseName()); + if (!dvipsProcess.waitForFinished()) { + WARN("dvips process failed."); + QFile::remove(fi.completeBaseName() + ".dvi"); return QImage(); } @@ -224,31 +250,24 @@ env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2"))); convertProcess.setProcessEnvironment(env); #endif - - const QStringList params{"-density", QString::number(dpi), baseName + ".ps", baseName + ".png"}; - convertProcess.start("convert", params); - - if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) { - WARN("convert process failed, exit code = " << convertProcess.exitCode()); - *success = false; - QFile::remove(baseName + ".aux"); - QFile::remove(baseName + ".log"); - QFile::remove(baseName + ".dvi"); - QFile::remove(baseName + ".ps"); + convertProcess.start("convert", QStringList() << "-density" << QString::number(dpi) + 'x' + QString::number(dpi) + << fi.completeBaseName() + ".ps" << fi.completeBaseName() + ".png"); + if (!convertProcess.waitForFinished()) { + WARN("convert process failed."); + QFile::remove(fi.completeBaseName() + ".dvi"); + QFile::remove(fi.completeBaseName() + ".ps"); return QImage(); } // read png file - QImage image(baseName + ".png", "png"); + QImage image; + image.load(fi.completeBaseName() + ".png", "png"); // final clean up - QFile::remove(baseName + ".aux"); - QFile::remove(baseName + ".log"); - QFile::remove(baseName + ".dvi"); - QFile::remove(baseName + ".ps"); - QFile::remove(baseName + ".png"); + QFile::remove(fi.completeBaseName() + ".png"); + QFile::remove(fi.completeBaseName() + ".dvi"); + QFile::remove(fi.completeBaseName() + ".ps"); - *success = true; return image; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,4 +3,3 @@ add_subdirectory(analysis) add_subdirectory(import_export) -add_subdirectory(nsl) diff --git a/tests/analysis/AnalysisTest.h b/tests/analysis/AnalysisTest.h --- a/tests/analysis/AnalysisTest.h +++ b/tests/analysis/AnalysisTest.h @@ -27,9 +27,28 @@ #ifndef ANALYSISTEST_H #define ANALYSISTEST_H -#include "../CommonTest.h" +#include +#include // DEBUG() -class AnalysisTest : public CommonTest { +extern "C" { +#include +} + +class AnalysisTest : public QObject { Q_OBJECT + +private slots: + void initTestCase(); +protected: + // compare floats with given delta (could be useful for other tests too) + // delta - relative error + static inline void FuzzyCompare(double actual, double expected, double delta = 1.e-12) { + if (fabs(expected) < delta) + QVERIFY(fabs(actual) < delta); + else { + DEBUG(std::setprecision(15) << actual - fabs(actual)*delta << " <= " << expected << " <= " << actual + fabs(actual)*delta); + QVERIFY(!gsl_fcmp(actual, expected, delta)); + } + } }; #endif diff --git a/tests/analysis/AnalysisTest.cpp b/tests/analysis/AnalysisTest.cpp --- a/tests/analysis/AnalysisTest.cpp +++ b/tests/analysis/AnalysisTest.cpp @@ -28,3 +28,10 @@ #include "AnalysisTest.h" #include "backend/core/column/Column.h" +void AnalysisTest::initTestCase() { + // needed in order to have the signals triggered by SignallingUndoCommand, see LabPlot.cpp + //TODO: redesign/remove this + qRegisterMetaType("const AbstractAspect*"); + qRegisterMetaType("const AbstractColumn*"); +} + diff --git a/tests/analysis/convolution/CMakeLists.txt b/tests/analysis/convolution/CMakeLists.txt --- a/tests/analysis/convolution/CMakeLists.txt +++ b/tests/analysis/convolution/CMakeLists.txt @@ -1,5 +1,5 @@ INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (convolutiontest ConvolutionTest.cpp ../AnalysisTest.cpp ../../CommonTest.cpp) +add_executable (convolutiontest ConvolutionTest.cpp ../AnalysisTest.cpp) target_link_libraries(convolutiontest Qt5::Test) target_link_libraries(convolutiontest KF5::Archive KF5::XmlGui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) diff --git a/tests/analysis/correlation/CMakeLists.txt b/tests/analysis/correlation/CMakeLists.txt --- a/tests/analysis/correlation/CMakeLists.txt +++ b/tests/analysis/correlation/CMakeLists.txt @@ -1,5 +1,5 @@ INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (correlationtest CorrelationTest.cpp ../AnalysisTest.cpp ../../CommonTest.cpp) +add_executable (correlationtest CorrelationTest.cpp ../AnalysisTest.cpp) target_link_libraries(correlationtest Qt5::Test) target_link_libraries(correlationtest KF5::Archive KF5::XmlGui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) diff --git a/tests/analysis/differentiation/CMakeLists.txt b/tests/analysis/differentiation/CMakeLists.txt --- a/tests/analysis/differentiation/CMakeLists.txt +++ b/tests/analysis/differentiation/CMakeLists.txt @@ -1,5 +1,5 @@ INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (differentiationtest DifferentiationTest.cpp ../AnalysisTest.cpp ../../CommonTest.cpp) +add_executable (differentiationtest DifferentiationTest.cpp ../AnalysisTest.cpp) target_link_libraries(differentiationtest Qt5::Test) target_link_libraries(differentiationtest KF5::Archive KF5::XmlGui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) diff --git a/tests/analysis/fit/CMakeLists.txt b/tests/analysis/fit/CMakeLists.txt --- a/tests/analysis/fit/CMakeLists.txt +++ b/tests/analysis/fit/CMakeLists.txt @@ -1,5 +1,5 @@ INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (fittest FitTest.cpp ../AnalysisTest.cpp ../../CommonTest.cpp) +add_executable (fittest FitTest.cpp ../AnalysisTest.cpp) target_link_libraries(fittest Qt5::Test) target_link_libraries(fittest KF5::Archive KF5::XmlGui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) diff --git a/tests/analysis/integration/CMakeLists.txt b/tests/analysis/integration/CMakeLists.txt --- a/tests/analysis/integration/CMakeLists.txt +++ b/tests/analysis/integration/CMakeLists.txt @@ -1,5 +1,5 @@ INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (integrationtest IntegrationTest.cpp ../AnalysisTest.cpp ../../CommonTest.cpp) +add_executable (integrationtest IntegrationTest.cpp ../AnalysisTest.cpp) target_link_libraries(integrationtest Qt5::Test) target_link_libraries(integrationtest KF5::Archive KF5::XmlGui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) diff --git a/tests/import_export/ASCII/AsciiFilterTest.h b/tests/import_export/ASCII/AsciiFilterTest.h --- a/tests/import_export/ASCII/AsciiFilterTest.h +++ b/tests/import_export/ASCII/AsciiFilterTest.h @@ -54,29 +54,8 @@ void testHeader05(); void testHeader06(); - //read ranges - void testColumnRange00(); - void testColumnRange01(); - void testColumnRange02(); - void testColumnRange03(); - void testColumnRange04(); - void testColumnRange05(); - void testColumnRange06(); - - void testRowRange00(); - void testRowRange01(); - void testRowRange02(); - - void testRowColumnRange00(); - //different separators - //qouted strings - void testQuotedStrings00(); - void testQuotedStrings01(); - void testQuotedStrings02(); - void testQuotedStrings03(); - //different locales //handling of NANs diff --git a/tests/import_export/ASCII/AsciiFilterTest.cpp b/tests/import_export/ASCII/AsciiFilterTest.cpp --- a/tests/import_export/ASCII/AsciiFilterTest.cpp +++ b/tests/import_export/ASCII/AsciiFilterTest.cpp @@ -241,7 +241,7 @@ AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(false); - filter.setVectorNames(QString()); + filter.setVectorNames(""); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 3); @@ -256,7 +256,7 @@ AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(true); - filter.setVectorNames(QString()); + filter.setVectorNames(""); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 2);//out of 3 rows one row is used for the column names (header) @@ -329,436 +329,14 @@ filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 3); - QCOMPARE(spreadsheet.columnCount(), 2); //three names were specified, but there're only two columns in the file -> we import only two columns + QCOMPARE(spreadsheet.columnCount(), 2); //thee names were specified, but there're only two columns in the file -> we import only two columns QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("x")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("y")); } -//############################################################################## -//##################### handling of different read ranges ##################### -//############################################################################## -void AsciiFilterTest::testColumnRange00() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //no ranges specified, all rows and columns have to be read - QCOMPARE(spreadsheet.rowCount(), 5); - QCOMPARE(spreadsheet.columnCount(), 3); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); - - //check the values for the first line - QCOMPARE(spreadsheet.column(0)->valueAt(0), 1.716299); - QCOMPARE(spreadsheet.column(1)->valueAt(0), -0.485527); - QCOMPARE(spreadsheet.column(2)->valueAt(0), -0.288690); -} - -void AsciiFilterTest::testColumnRange01() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setCreateIndexEnabled(true); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //no ranges specified, all rows and columns have to be read plus the additional column for the index - QCOMPARE(spreadsheet.rowCount(), 5); - QCOMPARE(spreadsheet.columnCount(), 4); - - 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); - - //check the values for the first line - QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); - QCOMPARE(spreadsheet.column(1)->valueAt(0), 1.716299); - QCOMPARE(spreadsheet.column(2)->valueAt(0), -0.485527); - QCOMPARE(spreadsheet.column(3)->valueAt(0), -0.288690); -} - -void AsciiFilterTest::testColumnRange02() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setStartColumn(2); - filter.setEndColumn(3); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //read all rows and the last two columns only - QCOMPARE(spreadsheet.rowCount(), 5); - QCOMPARE(spreadsheet.columnCount(), 2); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); - - //check the values for the first line - QCOMPARE(spreadsheet.column(0)->valueAt(0), -0.485527); - QCOMPARE(spreadsheet.column(1)->valueAt(0), -0.288690); -} - -void AsciiFilterTest::testColumnRange03() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setCreateIndexEnabled(true); - filter.setStartColumn(2); - filter.setEndColumn(3); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //read all rows and the last two columns only plus the additional column for the index - QCOMPARE(spreadsheet.rowCount(), 5); - QCOMPARE(spreadsheet.columnCount(), 3); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); - - //check the values for the first line - QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); - QCOMPARE(spreadsheet.column(1)->valueAt(0), -0.485527); - QCOMPARE(spreadsheet.column(2)->valueAt(0), -0.288690); -} - -void AsciiFilterTest::testColumnRange04() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setStartColumn(3); - filter.setEndColumn(3); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //read all rows and the last column only - QCOMPARE(spreadsheet.rowCount(), 5); - QCOMPARE(spreadsheet.columnCount(), 1); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Numeric); - - //check the values for the first line - QCOMPARE(spreadsheet.column(0)->valueAt(0), -0.288690); -} - -void AsciiFilterTest::testColumnRange05() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setStartColumn(3); - filter.setEndColumn(2); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //wrong column range specified (start>end), nothing to read, - //empty spreadsheet because of the replace mode - QCOMPARE(spreadsheet.rowCount(), 0); - QCOMPARE(spreadsheet.columnCount(), 0); -} - -void AsciiFilterTest::testColumnRange06() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setCreateIndexEnabled(true); - filter.setStartColumn(3); - filter.setEndColumn(2); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //wrong column range specified (start>end), only the index column is created - QCOMPARE(spreadsheet.rowCount(), 5); - QCOMPARE(spreadsheet.columnCount(), 1); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); -} - -void AsciiFilterTest::testRowRange00() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setStartRow(3); - filter.setEndRow(5); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //three rows to read - QCOMPARE(spreadsheet.rowCount(), 3); - QCOMPARE(spreadsheet.columnCount(), 3); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); - - //check the values for the first and for the last lines - QCOMPARE(spreadsheet.column(0)->valueAt(0), 1.711721); - QCOMPARE(spreadsheet.column(1)->valueAt(0), -0.485527); - QCOMPARE(spreadsheet.column(2)->valueAt(0), -0.293267); - - QCOMPARE(spreadsheet.column(0)->valueAt(2), 1.716299); - QCOMPARE(spreadsheet.column(1)->valueAt(2), -0.494682); - QCOMPARE(spreadsheet.column(2)->valueAt(2), -0.284112); -} - -void AsciiFilterTest::testRowRange01() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setStartRow(3); - filter.setEndRow(10); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //end row larger than the the number of available rows, three rows to read - QCOMPARE(spreadsheet.rowCount(), 3); - QCOMPARE(spreadsheet.columnCount(), 3); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); - - //check the values for the first and for the last lines - QCOMPARE(spreadsheet.column(0)->valueAt(0), 1.711721); - QCOMPARE(spreadsheet.column(1)->valueAt(0), -0.485527); - QCOMPARE(spreadsheet.column(2)->valueAt(0), -0.293267); - - QCOMPARE(spreadsheet.column(0)->valueAt(2), 1.716299); - QCOMPARE(spreadsheet.column(1)->valueAt(2), -0.494682); - QCOMPARE(spreadsheet.column(2)->valueAt(2), -0.284112); -} - -void AsciiFilterTest::testRowRange02() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setStartRow(3); - filter.setEndRow(1); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //start bigger than end, no rows to read - //wrong row range specified (start>end), nothing to read, - //spreadsheet is not touched, default number of rows and columns - //TODO: this is inconsistent with the handling for columns, see testColumnRange05() - QCOMPARE(spreadsheet.rowCount(), 100); - QCOMPARE(spreadsheet.columnCount(), 2); -} - -void AsciiFilterTest::testRowColumnRange00() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "numeric_data.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter("auto"); - filter.setHeaderEnabled(false); - filter.setStartRow(3); - filter.setEndRow(5); - filter.setStartColumn(2); - filter.setEndColumn(3); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //three rows and two columns to read - QCOMPARE(spreadsheet.rowCount(), 3); - QCOMPARE(spreadsheet.columnCount(), 2); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); - - //check the values for the first and for the last lines - QCOMPARE(spreadsheet.column(0)->valueAt(0), -0.485527); - QCOMPARE(spreadsheet.column(1)->valueAt(0), -0.293267); - - QCOMPARE(spreadsheet.column(0)->valueAt(2), -0.494682); - QCOMPARE(spreadsheet.column(1)->valueAt(2), -0.284112); -} - //############################################################################## //##################### handling of different separators ###################### //############################################################################## -//############################################################################## -//##################################### quoted strings ######################## -//############################################################################## -void AsciiFilterTest::testQuotedStrings00() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "quoted_strings.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter(","); - filter.setHeaderEnabled(false); - filter.setRemoveQuotesEnabled(true); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //three rows and two columns to read - QCOMPARE(spreadsheet.rowCount(), 3); - QCOMPARE(spreadsheet.columnCount(), 4); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Text); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(3)->columnMode(), AbstractColumn::Numeric); - - QCOMPARE(spreadsheet.column(0)->textAt(0), QLatin1String("a")); - QCOMPARE(spreadsheet.column(1)->integerAt(0), 1000); - QCOMPARE(spreadsheet.column(2)->integerAt(0), 201811); - QCOMPARE(spreadsheet.column(3)->valueAt(0), 1.1); - - QCOMPARE(spreadsheet.column(0)->textAt(1), QLatin1String("ab")); - QCOMPARE(spreadsheet.column(1)->integerAt(1), 2000); - QCOMPARE(spreadsheet.column(2)->integerAt(1), 201812); - QCOMPARE(spreadsheet.column(3)->valueAt(1), 1.2); - - QCOMPARE(spreadsheet.column(0)->textAt(2), QLatin1String("abc")); - QCOMPARE(spreadsheet.column(1)->integerAt(2), 3000); - QCOMPARE(spreadsheet.column(2)->integerAt(2), 201901); - QCOMPARE(spreadsheet.column(3)->valueAt(2), 1.3); -} - -void AsciiFilterTest::testQuotedStrings01() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "quoted_strings_with_header.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter(","); - filter.setHeaderEnabled(true); - filter.setSimplifyWhitespacesEnabled(true); - filter.setRemoveQuotesEnabled(true); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //three rows and two columns to read - QCOMPARE(spreadsheet.rowCount(), 3); - QCOMPARE(spreadsheet.columnCount(), 4); - - //column names - QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("col1")); - QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("col2")); - QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("col3")); - QCOMPARE(spreadsheet.column(3)->name(), QLatin1String("col4")); - - //data types - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Text); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(3)->columnMode(), AbstractColumn::Numeric); - - //values - QCOMPARE(spreadsheet.column(0)->textAt(0), QLatin1String("a")); - QCOMPARE(spreadsheet.column(1)->integerAt(0), 1000); - QCOMPARE(spreadsheet.column(2)->integerAt(0), 201811); - QCOMPARE(spreadsheet.column(3)->valueAt(0), 1.1); - - QCOMPARE(spreadsheet.column(0)->textAt(1), QLatin1String("ab")); - QCOMPARE(spreadsheet.column(1)->integerAt(1), 2000); - QCOMPARE(spreadsheet.column(2)->integerAt(1), 201812); - QCOMPARE(spreadsheet.column(3)->valueAt(1), 1.2); - - QCOMPARE(spreadsheet.column(0)->textAt(2), QLatin1String("abc")); - QCOMPARE(spreadsheet.column(1)->integerAt(2), 3000); - QCOMPARE(spreadsheet.column(2)->integerAt(2), 201901); - QCOMPARE(spreadsheet.column(3)->valueAt(2), 1.3); -} - -void AsciiFilterTest::testQuotedStrings02() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "quoted_strings_one_line.txt"; - - QCOMPARE(QFile::exists(fileName), true); - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter(","); - filter.setHeaderEnabled(false); - filter.setRemoveQuotesEnabled(true); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //three rows and two columns to read -// QCOMPARE(spreadsheet.rowCount(), 1); -// QCOMPARE(spreadsheet.columnCount(), 4); - - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Text); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(3)->columnMode(), AbstractColumn::Numeric); - - QCOMPARE(spreadsheet.column(0)->textAt(0), QLatin1String("a")); - QCOMPARE(spreadsheet.column(1)->integerAt(0), 1000); - QCOMPARE(spreadsheet.column(2)->integerAt(0), 201811); - QCOMPARE(spreadsheet.column(3)->valueAt(0), 1.1); -} - -void AsciiFilterTest::testQuotedStrings03() { - Spreadsheet spreadsheet("test", false); - AsciiFilter filter; - const QString fileName = m_dataDir + "quoted_strings_one_line_with_header.txt"; - - AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; - filter.setSeparatingCharacter(","); - filter.setHeaderEnabled(true); - filter.setSimplifyWhitespacesEnabled(true); - filter.setRemoveQuotesEnabled(true); - filter.readDataFromFile(fileName, &spreadsheet, mode); - - //three rows and two columns to read -// QCOMPARE(spreadsheet.rowCount(), 1); -// QCOMPARE(spreadsheet.columnCount(), 4); - - //column names - QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("col1")); - QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("col2")); - QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("col3")); - QCOMPARE(spreadsheet.column(3)->name(), QLatin1String("col4")); - - //data types - QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Text); - QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(3)->columnMode(), AbstractColumn::Numeric); - - //values - QCOMPARE(spreadsheet.column(0)->textAt(0), QLatin1String("a")); - QCOMPARE(spreadsheet.column(1)->integerAt(0), 1000); - QCOMPARE(spreadsheet.column(2)->integerAt(0), 201811); - QCOMPARE(spreadsheet.column(3)->valueAt(0), 1.1); -} - QTEST_MAIN(AsciiFilterTest) diff --git a/tests/import_export/ASCII/MqttTest/MQTT_test.cpp b/tests/import_export/ASCII/MqttTest/MQTT_test.cpp --- a/tests/import_export/ASCII/MqttTest/MQTT_test.cpp +++ b/tests/import_export/ASCII/MqttTest/MQTT_test.cpp @@ -152,9 +152,9 @@ void MainWindow::onTimeout() { if(m_client->state() == QMqttClient::ClientState::Connected && !m_brownianTopics.isEmpty()) { - QString s; + QString s = ""; QVector brownianY; - brownianY.fill(QString(), m_pathes); + brownianY.fill("", m_pathes); if(m_iterCount < m_itersTotal - m_iters - 1) for (int i = 0; i < m_iters; i++) { diff --git a/tests/import_export/ASCII/data/numeric_data.txt b/tests/import_export/ASCII/data/numeric_data.txt deleted file mode 100644 --- a/tests/import_export/ASCII/data/numeric_data.txt +++ /dev/null @@ -1,5 +0,0 @@ -1.716299 -0.485527 -0.288690 -1.716299 -0.476371 -0.274957 -1.711721 -0.485527 -0.293267 -1.711721 -0.480949 -0.293267 -1.716299 -0.494682 -0.284112 diff --git a/tests/import_export/ASCII/data/quoted_strings.txt b/tests/import_export/ASCII/data/quoted_strings.txt deleted file mode 100644 --- a/tests/import_export/ASCII/data/quoted_strings.txt +++ /dev/null @@ -1,3 +0,0 @@ -"a", "1000", "201811", "1.1" -"ab", "2000", "201812", "1.2" -"abc", "3000", "201901", "1.3" diff --git a/tests/import_export/ASCII/data/quoted_strings_one_line.txt b/tests/import_export/ASCII/data/quoted_strings_one_line.txt deleted file mode 100644 --- a/tests/import_export/ASCII/data/quoted_strings_one_line.txt +++ /dev/null @@ -1 +0,0 @@ -"a", "1000", "201811", "1.1" diff --git a/tests/import_export/ASCII/data/quoted_strings_one_line_with_header.txt b/tests/import_export/ASCII/data/quoted_strings_one_line_with_header.txt deleted file mode 100644 --- a/tests/import_export/ASCII/data/quoted_strings_one_line_with_header.txt +++ /dev/null @@ -1,2 +0,0 @@ -"col1", "col2", "col3", "col4" -"a", "1000", "201811", "1.1" diff --git a/tests/import_export/ASCII/data/quoted_strings_with_header.txt b/tests/import_export/ASCII/data/quoted_strings_with_header.txt deleted file mode 100644 --- a/tests/import_export/ASCII/data/quoted_strings_with_header.txt +++ /dev/null @@ -1,4 +0,0 @@ -"col1", "col2", "col3", "col4" -"a", "1000", "201811", "1.1" -"ab", "2000", "201812", "1.2" -"abc", "3000", "201901", "1.3" diff --git a/tests/import_export/ASCII/scripts/brownian_motion.py b/tests/import_export/ASCII/scripts/brownian_motion.py --- a/tests/import_export/ASCII/scripts/brownian_motion.py +++ b/tests/import_export/ASCII/scripts/brownian_motion.py @@ -42,7 +42,7 @@ datafile.write(out) datafile.close() if LIVE_DATA_DEBUG: - print(out) + print out out = "" i = 0 time.sleep(sleepInterval) @@ -52,7 +52,7 @@ datafile.write(out) datafile.close() if LIVE_DATA_DEBUG: - print(out) + print out if not LIVE_DATA: datafile = open(fileName, "a") diff --git a/tests/import_export/JSON/JsonFilterTest.cpp b/tests/import_export/JSON/JsonFilterTest.cpp --- a/tests/import_export/JSON/JsonFilterTest.cpp +++ b/tests/import_export/JSON/JsonFilterTest.cpp @@ -59,10 +59,6 @@ QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Text); QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(0)->plotDesignation(), Column::X); - QCOMPARE(spreadsheet.column(1)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(2)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(0)->name(), i18n("index")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("Column 1")); //TODO is translatable in JsonFilter QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("Column 2")); @@ -101,12 +97,6 @@ QCOMPARE(spreadsheet.column(3)->columnMode(), AbstractColumn::Numeric); QCOMPARE(spreadsheet.column(4)->columnMode(), AbstractColumn::Text); - QCOMPARE(spreadsheet.column(0)->plotDesignation(), Column::X); - QCOMPARE(spreadsheet.column(1)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(2)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(3)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(4)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(0)->name(), i18n("index")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("1")); QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("2")); @@ -155,12 +145,6 @@ QCOMPARE(spreadsheet.column(3)->columnMode(), AbstractColumn::Numeric); QCOMPARE(spreadsheet.column(4)->columnMode(), AbstractColumn::Text); - QCOMPARE(spreadsheet.column(0)->plotDesignation(), Column::X); - QCOMPARE(spreadsheet.column(1)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(2)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(3)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(4)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(0)->name(), i18n("name")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("1")); QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("2")); @@ -210,10 +194,6 @@ QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); - QCOMPARE(spreadsheet.column(0)->plotDesignation(), Column::X); - QCOMPARE(spreadsheet.column(1)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(2)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(0)->name(), i18n("name")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("2")); QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("3")); @@ -260,13 +240,6 @@ QCOMPARE(spreadsheet.column(4)->columnMode(), AbstractColumn::Numeric); QCOMPARE(spreadsheet.column(5)->columnMode(), AbstractColumn::Integer); - QCOMPARE(spreadsheet.column(0)->plotDesignation(), Column::X); - QCOMPARE(spreadsheet.column(1)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(2)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(3)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(4)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(5)->plotDesignation(), Column::Y); - QCOMPARE(spreadsheet.column(0)->name(), i18n("timestamp")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("1. open")); QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("2. high")); diff --git a/tests/import_export/MQTT/MQTTUnitTest.cpp b/tests/import_export/MQTT/MQTTUnitTest.cpp --- a/tests/import_export/MQTT/MQTTUnitTest.cpp +++ b/tests/import_export/MQTT/MQTTUnitTest.cpp @@ -417,7 +417,9 @@ mqttClient->addInitialMQTTSubscriptions(topicFilter, 0); LiveDataDock* liveDock = new LiveDataDock(); - liveDock->setMQTTClient(mqttClient); + QList list; + list.push_back(mqttClient); + liveDock->setMQTTClients(list); mqttClient->read(); mqttClient->ready(); diff --git a/tests/nsl/CMakeLists.txt b/tests/nsl/CMakeLists.txt deleted file mode 100644 --- a/tests/nsl/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -add_subdirectory(dft) -add_subdirectory(diff) -add_subdirectory(int) -add_subdirectory(sf) -add_subdirectory(smooth) diff --git a/tests/nsl/NSLTest.h b/tests/nsl/NSLTest.h deleted file mode 100644 --- a/tests/nsl/NSLTest.h +++ /dev/null @@ -1,35 +0,0 @@ -/*************************************************************************** - File : NSLTest.h - Project : LabPlot - Description : NSL tests - -------------------------------------------------------------------- - Copyright : (C) 2019 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 NSLTEST_H -#define NSLTEST_H - -#include "../CommonTest.h" - -class NSLTest : public CommonTest { - Q_OBJECT -}; -#endif diff --git a/tests/nsl/NSLTest.cpp b/tests/nsl/NSLTest.cpp deleted file mode 100644 --- a/tests/nsl/NSLTest.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/*************************************************************************** - File : NSLTest.cpp - Project : LabPlot - Description : NSL Tests - -------------------------------------------------------------------- - Copyright : (C) 2019 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 "NSLTest.h" -#include "backend/core/column/Column.h" - diff --git a/tests/nsl/dft/CMakeLists.txt b/tests/nsl/dft/CMakeLists.txt deleted file mode 100644 --- a/tests/nsl/dft/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (nsldfttest NSLDFTTest.cpp ../NSLTest.cpp ../../CommonTest.cpp) - -target_link_libraries(nsldfttest Qt5::Test) -target_link_libraries(nsldfttest ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) - -IF (FFTW_FOUND) - target_link_libraries(nsldfttest ${FFTW_LIBRARIES}) -ENDIF () - -target_link_libraries(nsldfttest labplot2lib) - -add_test(NAME nsldfttest COMMAND nsldfttest) diff --git a/tests/nsl/dft/NSLDFTTest.h b/tests/nsl/dft/NSLDFTTest.h deleted file mode 100644 --- a/tests/nsl/dft/NSLDFTTest.h +++ /dev/null @@ -1,64 +0,0 @@ -/*************************************************************************** - File : NSLDFTTest.h - Project : LabPlot - Description : NSL Tests for DFT - -------------------------------------------------------------------- - Copyright : (C) 2019 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 NSLDFTTEST_H -#define NSLDFTTEST_H - -#include "../NSLTest.h" - -class NSLDFTTest : public NSLTest { - Q_OBJECT - -private slots: - // one sided - void testOnesided_real(); - void testOnesided_imag(); - void testOnesided_magnitude(); - void testOnesided_amplitude(); - void testOnesided_power(); - void testOnesided_phase(); - void testOnesided_dB(); - void testOnesided_squaremagnitude(); - void testOnesided_squareamplitude(); - void testOnesided_normdB(); - // two sided - void testTwosided_real(); - void testTwosided_imag(); - void testTwosided_magnitude(); - void testTwosided_amplitude(); - void testTwosided_power(); - void testTwosided_phase(); - void testTwosided_dB(); - void testTwosided_squaremagnitude(); - void testTwosided_squareamplitude(); - void testTwosided_normdB(); - // performance - void testPerformance_onesided(); - void testPerformance_twosided(); -private: - QString m_dataDir; -}; -#endif diff --git a/tests/nsl/dft/NSLDFTTest.cpp b/tests/nsl/dft/NSLDFTTest.cpp deleted file mode 100644 --- a/tests/nsl/dft/NSLDFTTest.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/*************************************************************************** - File : NSLDFTTest.cpp - Project : LabPlot - Description : NSL Tests for DFT - -------------------------------------------------------------------- - Copyright : (C) 2019 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 "NSLDFTTest.h" -#include "backend/lib/macros.h" - -extern "C" { -#include "backend/nsl/nsl_dft.h" -} - -#define ONESIDED 0 -#define TWOSIDED 1 -const int N = 10; - -//############################################################################## -//################# one sided tests -//############################################################################## - -void NSLDFTTest::testOnesided_real() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {10, 2, -5.85410196624968, 2, 0.854101966249685}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_real); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testOnesided_imag() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {0, -4.97979656976556, 0, 0.449027976579585, 0}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_imag); - for (unsigned int i = 0; i < N/2; i++) { - DEBUG(std::setprecision(15) << data[i]); - if (i == 2) - QCOMPARE(data[i] + 1., 1.); // -1.11022302462516e-16 (Win) - else - QCOMPARE(data[i], result[i]); - } -} - -void NSLDFTTest::testOnesided_magnitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {10, 5.36641163872553, 5.85410196624968, 2.04978684837013, 0.854101966249685}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_magnitude); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testOnesided_amplitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {1, 1.07328232774511, 1.17082039324994, 0.409957369674026, 0.170820393249937}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_amplitude); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testOnesided_power() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {10, 5.75967477524977, 6.85410196624968, 0.840325224750231, 0.145898033750316}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_power); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testOnesided_phase() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {0, 1.18889174012102, -3.14159265358979, -0.220851801285041, 0}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_phase); - for (unsigned int i = 0; i < N/2; i++) { - DEBUG(std::setprecision(15) << data[i]); - if (i == 2) // sign can be + or - - QCOMPARE(fabs(data[i]), fabs(result[i])); - else - QCOMPARE(data[i], result[i]); - } -} - -void NSLDFTTest::testOnesided_dB() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {0, 0.614279569861449, 1.36980556663896, -7.7452260401377, -15.3492056533593}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_dB); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testOnesided_squaremagnitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {100, 28.7983738762488, 34.2705098312484, 4.20162612375116, 0.729490168751578}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_squaremagnitude); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testOnesided_squareamplitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {1, 1.15193495504995, 1.37082039324994, 0.168065044950046, 0.0291796067500631}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_squareamplitude); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testOnesided_normdB() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {-1.36980556663896, -0.755525996777514, 0, -9.11503160677666, -16.7190112199983}; - - nsl_dft_transform(data, 1, N, ONESIDED, nsl_dft_result_normdB); - for (unsigned int i = 0; i < N/2; i++) - QCOMPARE(data[i], result[i]); -} - -//############################################################################## -//################# two sided tests -//############################################################################## - -void NSLDFTTest::testTwosided_real() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {10, 2, -5.85410196624968, 2, 0.854101966249685, 2, 0.854101966249685, 2, -5.85410196624968, 2}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_real); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testTwosided_imag() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {0, -4.97979656976556, 0, 0.449027976579585, 0, 0, 0, -0.449027976579585, 0, 4.97979656976556}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_imag); - for (unsigned int i = 0; i < N; i++) { - DEBUG(std::setprecision(15) << data[i]); - if (i == 2 || i == 8) - QCOMPARE(data[i] + 1., 1.); // -1.11022302462516e-16 (Win) - else - QCOMPARE(data[i], result[i]); - } -} - -void NSLDFTTest::testTwosided_magnitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {10, 5.36641163872553, 5.85410196624968, 2.04978684837013, 0.854101966249685, 2, 0.854101966249685, 2.04978684837013, 5.85410196624968, 5.36641163872553}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_magnitude); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testTwosided_amplitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {1, 1.07328232774511, 1.17082039324994, 0.409957369674026, 0.170820393249937, 0.4, 0.170820393249937, 0.409957369674026, 1.17082039324994, 1.07328232774511}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_amplitude); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testTwosided_power() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {10, 5.75967477524977, 6.85410196624968, 0.840325224750231, 0.145898033750316, 0.8, 0.145898033750316, 0.840325224750231, 6.85410196624968, 5.75967477524977}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_power); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testTwosided_phase() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {0, 1.18889174012102, -3.14159265358979, -0.220851801285041, 0, 0, 0, 0.220851801285041, 3.14159265358979, -1.18889174012102}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_phase); - for (unsigned int i = 0; i < N; i++) { - DEBUG(std::setprecision(15) << data[i]); - if (i == 2 || i == 8) // sign can be + or - - QCOMPARE(fabs(data[i]), fabs(result[i])); - else - QCOMPARE(data[i], result[i]); - } -} - -void NSLDFTTest::testTwosided_dB() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {0, 0.614279569861449, 1.36980556663896, -7.7452260401377, -15.3492056533593, -7.95880017344075, -15.3492056533593, -7.7452260401377, 1.36980556663896, 0.614279569861449}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_dB); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testTwosided_squaremagnitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {100, 28.7983738762488, 34.2705098312484, 4.20162612375116, 0.729490168751578, 4, 0.729490168751578, 4.20162612375116, 34.2705098312484, 28.7983738762488}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_squaremagnitude); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testTwosided_squareamplitude() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {1, 1.15193495504995, 1.37082039324994, 0.168065044950046, 0.0291796067500631, 0.16, 0.0291796067500631, 0.168065044950046, 1.37082039324994, 1.15193495504995}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_squareamplitude); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLDFTTest::testTwosided_normdB() { - double data[] = {1, 1, 3, 3, 1, -1, 0, 1, 1, 0}; - double result[] = {-1.36980556663896, -0.755525996777514, 0, -9.11503160677666, -16.7190112199983, -9.32860574007971, -16.7190112199983, -9.11503160677666, 0, -0.755525996777514}; - - nsl_dft_transform(data, 1, N, TWOSIDED, nsl_dft_result_normdB); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -//############################################################################## -//################# performance -//############################################################################## - -#ifdef HAVE_FFTW3 -const int NN = 1e6; -#else // GSL is much slower -const int NN = 1e6; -#endif - -void NSLDFTTest::testPerformance_onesided() { - double* data = new double[NN]; - - for (int i = 0; i < NN; i++) - data[i] = 1.; - - QBENCHMARK { - nsl_dft_transform(data, 1, NN, ONESIDED, nsl_dft_result_real); - } - - delete[] data; -} - -void NSLDFTTest::testPerformance_twosided() { - double* data = new double[NN]; - - for (int i = 0; i < NN; i++) - data[i] = 1.; - - QBENCHMARK { - nsl_dft_transform(data, 1, NN, TWOSIDED, nsl_dft_result_real); - } - - delete[] data; -} - -QTEST_MAIN(NSLDFTTest) diff --git a/tests/nsl/diff/CMakeLists.txt b/tests/nsl/diff/CMakeLists.txt deleted file mode 100644 --- a/tests/nsl/diff/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (nsldifftest NSLDiffTest.cpp ../NSLTest.cpp ../../CommonTest.cpp) - -target_link_libraries(nsldifftest Qt5::Test) -target_link_libraries(nsldifftest ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) - -target_link_libraries(nsldifftest labplot2lib) - -add_test(NAME nsldifftest COMMAND nsldifftest) diff --git a/tests/nsl/diff/NSLDiffTest.h b/tests/nsl/diff/NSLDiffTest.h deleted file mode 100644 --- a/tests/nsl/diff/NSLDiffTest.h +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************** - File : NSLDiffTest.h - Project : LabPlot - Description : NSL Tests for numerical differentiation - -------------------------------------------------------------------- - Copyright : (C) 2019 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 NSLDIFFTEST_H -#define NSLDIFFTEST_H - -#include "../NSLTest.h" - -class NSLDiffTest : public NSLTest { - Q_OBJECT - -private slots: - // first derivative - void testFirst_order2(); - void testFirst_order4(); - void testFirst_avg(); - // second derivative - void testSecond_order1(); - void testSecond_order2(); - void testSecond_order3(); - // higher derivative - void testThird_order2(); - void testFourth_order1(); - void testFourth_order3(); - void testFifth_order2(); - void testSixth_order1(); - // performance - void testPerformance_first(); - void testPerformance_second(); - void testPerformance_third(); -private: - QString m_dataDir; -}; -#endif diff --git a/tests/nsl/diff/NSLDiffTest.cpp b/tests/nsl/diff/NSLDiffTest.cpp deleted file mode 100644 --- a/tests/nsl/diff/NSLDiffTest.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/*************************************************************************** - File : NSLDiffTest.cpp - Project : LabPlot - Description : NSL Tests for numerical differentiation - -------------------------------------------------------------------- - Copyright : (C) 2019 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 "NSLDiffTest.h" - -extern "C" { -#include "backend/nsl/nsl_diff.h" -} - -//############################################################################## -//################# first derivative tests -//############################################################################## - -const int N = 7; -double xdata[] = {1, 2, 4, 8, 16, 32, 64}; - -void NSLDiffTest::testFirst_order2() { - double ydata[] = {1, 4, 16, 64, 256, 1024, 4096}; - - int status = nsl_diff_first_deriv(xdata, ydata, N, 2); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], 2 * xdata[i]); -} - -void NSLDiffTest::testFirst_order4() { - double ydata[] = {1, 8, 64, 512, 4096, 32768, 262144}; - - int status = nsl_diff_first_deriv(xdata, ydata, N, 4); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], 3 * xdata[i]*xdata[i]); -} - -void NSLDiffTest::testFirst_avg() { - double ydata[] = {1, 4, 16, 64, 256, 1024, 4096}; - double result[] = {3, 4.5, 9, 18, 36, 72, 96}; - - int status = nsl_diff_first_deriv_avg(xdata, ydata, N); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], result[i]); -} - -//############################################################################## -//################# second derivative tests -//############################################################################## - -void NSLDiffTest::testSecond_order1() { - double ydata[] = {1, 4, 16, 64, 256, 1024, 4096}; - - int status = nsl_diff_second_deriv(xdata, ydata, N, 1); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], 2.); -} - -void NSLDiffTest::testSecond_order2() { - double ydata[] = {1, 4, 16, 64, 256, 1024, 4096}; - - int status = nsl_diff_second_deriv(xdata, ydata, N, 2); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], 2.); -} - -void NSLDiffTest::testSecond_order3() { - double ydata[] = {1, 8, 64, 512, 4096, 32768, 262144}; - - int status = nsl_diff_second_deriv(xdata, ydata, N, 3); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], 6. * xdata[i]); -} - -//############################################################################## -//################# higher derivative tests -//############################################################################## - -void NSLDiffTest::testThird_order2() { - double ydata[] = {1, 8, 64, 512, 4096, 32768, 262144}; - - int status = nsl_diff_third_deriv(xdata, ydata, N, 2); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], 6.); -} - -void NSLDiffTest::testFourth_order1() { - double ydata[] = {1, 8, 64, 512, 4096, 32768, 262144}; - - int status = nsl_diff_fourth_deriv(xdata, ydata, N, 1); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i], 0.); -} - -void NSLDiffTest::testFourth_order3() { - double ydata[] = {1, 8, 64, 512, 4096, 32768, 262144}; - - int status = nsl_diff_fourth_deriv(xdata, ydata, N, 3); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i] + 1., 1.); -} - -void NSLDiffTest::testFifth_order2() { - double ydata[] = {1, 8, 64, 512, 4096, 32768, 262144}; - - int status = nsl_diff_fifth_deriv(xdata, ydata, N, 2); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i] + 1., 1.); -} - -void NSLDiffTest::testSixth_order1() { - double ydata[] = {1, 8, 64, 512, 4096, 32768, 262144}; - - int status = nsl_diff_sixth_deriv(xdata, ydata, N, 1); - QCOMPARE(status, 0); - for (unsigned int i = 0; i < N; i++) - QCOMPARE(ydata[i] + 1., 1.); -} - -//############################################################################## -//################# performance -//############################################################################## - -void NSLDiffTest::testPerformance_first() { - const int NN = 1e6; - double* xdata = new double[NN]; - double* ydata = new double[NN]; - - for (int i = 0; i < NN; i++) - xdata[i] = ydata[i] = (double)i; - - QBENCHMARK { - int status = nsl_diff_first_deriv(xdata, ydata, NN, 2); - QCOMPARE(status, 0); - } - - delete[] xdata; - delete[] ydata; -} - -void NSLDiffTest::testPerformance_second() { - const int NN = 1e6; - double* xdata = new double[NN]; - double* ydata = new double[NN]; - - for (int i = 0; i < NN; i++) - xdata[i] = ydata[i] = (double)i; - - QBENCHMARK { - int status = nsl_diff_second_deriv(xdata, ydata, NN, 2); - QCOMPARE(status, 0); - } - - delete[] xdata; - delete[] ydata; -} - -void NSLDiffTest::testPerformance_third() { - const int NN = 1e6; - double* xdata = new double[NN]; - double* ydata = new double[NN]; - - for (int i = 0; i < NN; i++) - xdata[i] = ydata[i] = (double)i; - - QBENCHMARK { - int status = nsl_diff_third_deriv(xdata, ydata, NN, 2); - QCOMPARE(status, 0); - } - - delete[] xdata; - delete[] ydata; -} - -QTEST_MAIN(NSLDiffTest) diff --git a/tests/nsl/int/CMakeLists.txt b/tests/nsl/int/CMakeLists.txt deleted file mode 100644 --- a/tests/nsl/int/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (nslinttest NSLIntTest.cpp ../NSLTest.cpp ../../CommonTest.cpp) - -target_link_libraries(nslinttest Qt5::Test) -target_link_libraries(nslinttest ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) - -target_link_libraries(nslinttest labplot2lib) - -add_test(NAME nslinttest COMMAND nslinttest) diff --git a/tests/nsl/int/NSLIntTest.h b/tests/nsl/int/NSLIntTest.h deleted file mode 100644 --- a/tests/nsl/int/NSLIntTest.h +++ /dev/null @@ -1,51 +0,0 @@ -/*************************************************************************** - File : NSLIntTest.h - Project : LabPlot - Description : NSL Tests for numerical integration - -------------------------------------------------------------------- - Copyright : (C) 2019 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 NSLINTTEST_H -#define NSLINTTEST_H - -#include "../NSLTest.h" - -class NSLIntTest : public NSLTest { - Q_OBJECT - -private slots: - // rules integral/area - void testRectangle_integral(); - void testRectangle_area(); - void testTrapezoid_integral(); - void testTrapezoid_area(); - void test3Point_integral(); - void test4Point_integral(); - // performance - void testPerformanceRectangle(); - void testPerformanceTrapezoid(); - void testPerformance3Point(); - void testPerformance4Point(); -private: - QString m_dataDir; -}; -#endif diff --git a/tests/nsl/int/NSLIntTest.cpp b/tests/nsl/int/NSLIntTest.cpp deleted file mode 100644 --- a/tests/nsl/int/NSLIntTest.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/*************************************************************************** - File : NSLIntTest.cpp - Project : LabPlot - Description : NSL Tests for numerical integration - -------------------------------------------------------------------- - Copyright : (C) 2019 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 "NSLIntTest.h" - -extern "C" { -#include "backend/nsl/nsl_int.h" -} - -//############################################################################## -//################# rule integral/area tests -//############################################################################## - -const int N = 5; -double xdata[] = {1, 2, 3, 5, 7}; - -void NSLIntTest::testRectangle_integral() { - double ydata[] = {2, 2, 2, -2, -2}; - - int status = nsl_int_rectangle(xdata, ydata, N, 0); - QCOMPARE(status, 0); - QCOMPARE(ydata[N - 1], 4.); -} - -void NSLIntTest::testRectangle_area() { - double ydata[] = {2, 2, 2, -2, -2}; - - int status = nsl_int_rectangle(xdata, ydata, N, 1); - QCOMPARE(status, 0); - QCOMPARE(ydata[N - 1], 12.); -} - -void NSLIntTest::testTrapezoid_integral() { - double ydata[] = {1, 2, 3, -1, -3}; - - int status = nsl_int_trapezoid(xdata, ydata, N, 0); - QCOMPARE(status, 0); - QCOMPARE(ydata[N - 1], 2.); -} - -void NSLIntTest::testTrapezoid_area() { - double ydata[] = {1, 2, 3, -1, -3}; - - int status = nsl_int_trapezoid(xdata, ydata, N, 1); - QCOMPARE(status, 0); - QCOMPARE(ydata[N - 1], 10.5); -} - -void NSLIntTest::test3Point_integral() { - double ydata[] = {1, 2, 3, -1, -3}; - - int np = (int)nsl_int_simpson(xdata, ydata, N, 0); - QCOMPARE(np, 3); - QCOMPARE(ydata[np - 1], 4/3.); -} - -void NSLIntTest::test4Point_integral() { - double xdata2[]={1, 2, 3, 5, 7, 8, 9}; - double ydata[] = {2, 2, 2, 2, 2, 2, 2, 2}; - const int n = 7; - - int np = (int)nsl_int_simpson_3_8(xdata2, ydata, n, 0); - QCOMPARE(np, 3); - QCOMPARE(ydata[np - 1], 16.); -} - -//############################################################################## -//################# performance -//############################################################################## - -void NSLIntTest::testPerformanceRectangle() { - const size_t n = 1e6; - double* xdata = new double[n]; - double* ydata = new double[n]; - - for (size_t i = 0; i < n; i++) - xdata[i] = (double)i; - - QBENCHMARK { - for (size_t i = 0; i < n; i++) - ydata[i] = 1.; - int status = nsl_int_rectangle(xdata, ydata, n, 0); - QCOMPARE(status, 0); - } - QCOMPARE(ydata[n - 1], (double)(n - 1)); - - delete[] xdata; - delete[] ydata; -} - -void NSLIntTest::testPerformanceTrapezoid() { - const int n = 1e6; - double* xdata = new double[n]; - double* ydata = new double[n]; - - for (int i = 0; i < n; i++) - xdata[i] = (double)i; - - QBENCHMARK { - for (int i = 0; i < n; i++) - ydata[i] = 1.; - int status = nsl_int_trapezoid(xdata, ydata, n, 0); - QCOMPARE(status, 0); - } - QCOMPARE(ydata[n - 1], (double)(n - 1)); - - delete[] xdata; - delete[] ydata; -} - -void NSLIntTest::testPerformance3Point() { - const int n = 1e6; - double* xdata = new double[n]; - double* ydata = new double[n]; - - for (int i = 0; i < n; i++) - xdata[i] = (double)i; - - int np; - QBENCHMARK { - for (int i = 0; i < n; i++) - ydata[i] = 1.; - np = (int)nsl_int_simpson(xdata, ydata, n, 0); - QCOMPARE(np, n/2 + 1); - } - QCOMPARE(ydata[np - 1], (double)(n - 1)); - - delete[] xdata; - delete[] ydata; -} - -void NSLIntTest::testPerformance4Point() { - const int n = 1e6; - double* xdata = new double[n]; - double* ydata = new double[n]; - - for (int i = 0; i < n; i++) - xdata[i] = (double)i; - - int np; - QBENCHMARK { - for (int i = 0; i < n; i++) - ydata[i] = 1.; - np = (int)nsl_int_simpson_3_8(xdata, ydata, n, 0); - QCOMPARE(np, n/3 + 1); - } - - //TODO: - //QCOMPARE(ydata[np - 1], (double)(n - 1)); - printf("%.15g %.15g\n", ydata[np - 1], (double)(n-1)); - - delete[] xdata; - delete[] ydata; -} - -QTEST_MAIN(NSLIntTest) diff --git a/tests/nsl/sf/CMakeLists.txt b/tests/nsl/sf/CMakeLists.txt deleted file mode 100644 --- a/tests/nsl/sf/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) - -# basic functions -add_executable (nslsfbasictest NSLSFBasicTest.cpp ../NSLTest.cpp ../../CommonTest.cpp) - -target_link_libraries(nslsfbasictest Qt5::Test) -target_link_libraries(nslsfbasictest ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) - -target_link_libraries(nslsfbasictest labplot2lib) - -add_test(NAME nslsfbasictest COMMAND nslsfbasictest) - -# window functions -add_executable (nslsfwindowtest NSLSFWindowTest.cpp ../NSLTest.cpp ../../CommonTest.cpp) - -target_link_libraries(nslsfwindowtest Qt5::Test) -target_link_libraries(nslsfwindowtest ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) - -target_link_libraries(nslsfwindowtest labplot2lib) - -add_test(NAME nslsfwindowtest COMMAND nslsfwindowtest) diff --git a/tests/nsl/sf/NSLSFBasicTest.h b/tests/nsl/sf/NSLSFBasicTest.h deleted file mode 100644 --- a/tests/nsl/sf/NSLSFBasicTest.h +++ /dev/null @@ -1,46 +0,0 @@ -/*************************************************************************** - File : NSLSFBasicTest.h - Project : LabPlot - Description : NSL Tests for the basic special functions - -------------------------------------------------------------------- - Copyright : (C) 2019 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 NSLSFBASICTEST_H -#define NSLSFBASICTEST_H - -#include "../NSLTest.h" - -class NSLSFBasicTest : public NSLTest { - Q_OBJECT - -private slots: - // log2 - void testlog2_int_C99(); - void testlog2_int(); - void testlog2_longlong(); - void testlog2_int2(); - void testlog2_int3(); - void testlog2p1_int(); -private: - QString m_dataDir; -}; -#endif diff --git a/tests/nsl/sf/NSLSFBasicTest.cpp b/tests/nsl/sf/NSLSFBasicTest.cpp deleted file mode 100644 --- a/tests/nsl/sf/NSLSFBasicTest.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/*************************************************************************** - File : NSLSFBasicTest.cpp - Project : LabPlot - Description : NSL Tests for basic special functions - -------------------------------------------------------------------- - Copyright : (C) 2019 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 "NSLSFBasicTest.h" - -extern "C" { -#include "backend/nsl/nsl_sf_basic.h" -} - -//############################################################################## -//################# log2() tests -//############################################################################## -void NSLSFBasicTest::testlog2_int_C99() { - QBENCHMARK { - for (unsigned int i = 1; i < 1e7; i++) - Q_UNUSED((int)log2(i)); - } -} - -void NSLSFBasicTest::testlog2_int() { - for (unsigned int i = 1; i < 1e5; i++) { - int result = nsl_sf_log2_int(i); - QCOMPARE(result, (int)log2(i)); - } - - QBENCHMARK { - for (unsigned int i = 1; i < 1e7; i++) - nsl_sf_log2_int(i); - } -} -void NSLSFBasicTest::testlog2_longlong() { -#ifndef _MSC_VER /* not implemented yet */ - for (unsigned long long i = 1; i < 1e5; i++) { - int result = nsl_sf_log2_longlong(i); - QCOMPARE(result, (int)log2(i)); - } - - QBENCHMARK { - for (unsigned long long i = 1; i < 1e7; i++) - nsl_sf_log2_longlong(i); - } -#endif -} - - -void NSLSFBasicTest::testlog2_int2() { - for (int i = 1; i < 1e5; i++) { - int result = nsl_sf_log2_int2(i); - QCOMPARE(result, (int)log2(i)); - } - - QBENCHMARK { - for (int i = 1; i < 1e7; i++) - nsl_sf_log2_int2(i); - } -} - -void NSLSFBasicTest::testlog2_int3() { - for (unsigned int i = 1; i < 1e5; i++) { - int result = nsl_sf_log2_int3(i); - QCOMPARE(result, (int)log2(i)); - } - - QBENCHMARK { - for (unsigned int i = 1; i < 1e7; i++) - nsl_sf_log2_int3(i); - } -} - -void NSLSFBasicTest::testlog2p1_int() { - for (int i = 1; i < 1e5; i++) { - int result = nsl_sf_log2p1_int(i); - QCOMPARE(result, (int)log2(i) + 1); - } - - QBENCHMARK { - for (int i = 1; i < 1e7; i++) - nsl_sf_log2p1_int(i); - } - -} - -QTEST_MAIN(NSLSFBasicTest) diff --git a/tests/nsl/sf/NSLSFWindowTest.cpp b/tests/nsl/sf/NSLSFWindowTest.cpp deleted file mode 100644 --- a/tests/nsl/sf/NSLSFWindowTest.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/*************************************************************************** - File : NSLSFWindowTest.cpp - Project : LabPlot - Description : NSL Tests for special window functions - -------------------------------------------------------------------- - Copyright : (C) 2019 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 "NSLSFWindowTest.h" - -extern "C" { -#include "backend/nsl/nsl_sf_window.h" -} - -//############################################################################## -//################# window types -//############################################################################## - -void NSLSFWindowTest::testWindowTypes() { - const int N = 10; - double data[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - double result[][N] = {{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - {0.1, 0.3, 0.5, 0.7, 0.9, 0.9, 0.7, 0.5, 0.3, 0.1}, - {0, 2/9., 4/9., 6/9., 8/9., 8/9., 6/9., 4/9., 2/9., 0}, - {2/11., 4/11., 6/11., 8/11., 10/11., 10/11., 8/11., 6/11., 4/11., 2/11.}, - {0.330578512396694, 0.59504132231405, 0.793388429752066, 0.925619834710744, 0.991735537190083, 0.991735537190083, 0.925619834710744, 0.793388429752066, 0.59504132231405, 0.330578512396694}, - {0, 0.116977778440511, 0.413175911166535, 0.75, 0.969846310392954, 0.969846310392954, 0.75, 0.413175911166535, 0.116977778440511, 0}, - {0.08, 0.18761955616527, 0.460121838273212, 0.77, 0.972258605561518, 0.972258605561518, 0.77, 0.460121838273212, 0.18761955616527, 0.08}, - {0, 0.0508696326538654, 0.258000501503662, 0.63, 0.951129865842472, 0.951129865842472, 0.63, 0.258000501503662, 0.0508696326538655, 0}, - {0, 0.0137486265628393, 0.141900826716656, 0.514746, 0.930560546720505, 0.930560546720505, 0.514746, 0.141900826716656, 0.0137486265628393, 0}, - {0.0003628, 0.01789099867138, 0.15559612641629, 0.5292298, 0.933220224912329, 0.93322022491233, 0.5292298, 0.155596126416291, 0.0178909986713801, 0.0003628}, - {6.e-05, 0.0150711734102182, 0.147039557862381, 0.520575, 0.9316592687274, 0.931659268727401, 0.520575, 0.147039557862382, 0.0150711734102182, 6.e-05}, - {0, -0.0867710194112928, -0.331895219303666, 0.918, 4.00066623871496, 4.00066623871496, 0.918, -0.331895219303665, -0.0867710194112926, 0}, - {0, 0.342020143325669, 0.642787609686539, 0.866025403784439, 0.984807753012208, 0.984807753012208, 0.866025403784439, 0.642787609686539, 0.342020143325669, 0}, - {0, 0.142236444948122, 0.420680359153233, 0.73, 0.950416529231978, 0.950416529231979, 0.73, 0.420680359153233, 0.142236444948122, 0}, - {0, 0.263064408273866, 0.564253278793615, 0.826993343132688, 0.979815536051017, 0.979815536051017, 0.826993343132688, 0.564253278793615, 0.263064408273866, 0}}; - - for (int t = (int)nsl_sf_window_uniform; t <= (int)nsl_sf_window_lanczos; t++) { - nsl_sf_apply_window(data, N, (nsl_sf_window_type)t); - for (int i = 0; i < N; i++) - QCOMPARE(data[i] + 1., result[t][i] + 1.); - } -} - -//############################################################################## -//################# performance -//############################################################################## - -void NSLSFWindowTest::testPerformance_triangle() { - const int N = 1e6; - double* data = new double[N]; - - QBENCHMARK { - nsl_sf_apply_window(data, N, nsl_sf_window_triangle); - } -} - -void NSLSFWindowTest::testPerformance_welch() { - const int N = 1e6; - double* data = new double[N]; - - QBENCHMARK { - nsl_sf_apply_window(data, N, nsl_sf_window_welch); - } -} - -void NSLSFWindowTest::testPerformance_flat_top() { - const int N = 1e6; - double* data = new double[N]; - - QBENCHMARK { - nsl_sf_apply_window(data, N, nsl_sf_window_flat_top); - } -} - -QTEST_MAIN(NSLSFWindowTest) diff --git a/tests/nsl/smooth/CMakeLists.txt b/tests/nsl/smooth/CMakeLists.txt deleted file mode 100644 --- a/tests/nsl/smooth/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -INCLUDE_DIRECTORIES(${GSL_INCLUDE_DIR}) -add_executable (nslsmoothtest NSLSmoothTest.cpp ../NSLTest.cpp ../../CommonTest.cpp) - -target_link_libraries(nslsmoothtest Qt5::Test) -target_link_libraries(nslsmoothtest ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) - -target_link_libraries(nslsmoothtest labplot2lib) - -add_test(NAME nslsmoothtest COMMAND nslsmoothtest) diff --git a/tests/nsl/smooth/NSLSmoothTest.h b/tests/nsl/smooth/NSLSmoothTest.h deleted file mode 100644 --- a/tests/nsl/smooth/NSLSmoothTest.h +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************** - File : NSLDiffTest.h - Project : LabPlot - Description : NSL Tests for smoothing - -------------------------------------------------------------------- - Copyright : (C) 2019 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 NSLSMOOTHTEST_H -#define NSLSMOOTHTEST_H - -#include "../NSLTest.h" - -class NSLSmoothTest : public NSLTest { - Q_OBJECT - -private slots: - // moving average tests - void testMA_padnone(); - void testMA_padmirror(); - void testMA_padnearest(); - void testMA_padconstant(); - void testMA_padperiodic(); - // lagged moving average tests - void testMAL_padnone(); - void testMAL_padmirror(); - void testMAL_padnearest(); - void testMAL_padconstant(); - void testMAL_padperiodic(); - // percentile tests - void testPercentile_padnone(); - void testPercentile_padmirror(); - void testPercentile_padnearest(); - void testPercentile_padconstant(); - void testPercentile_padperiodic(); - // Savivitzky-Golay coeff tests - void testSG_coeff31(); - void testSG_coeff51(); - void testSG_coeff53(); - void testSG_coeff73(); - void testSG_coeff74(); - void testSG_coeff92(); - void testSG_coeff94(); - - // Savivitzky-Golay modes - void testSG_mode_interp(); - void testSG_mode_mirror(); - void testSG_mode_nearest(); - void testSG_mode_constant(); - void testSG_mode_periodic(); - - // performance - void testPerformance_interp(); - void testPerformance_mirror(); - void testPerformance_nearest(); - void testPerformance_constant(); - void testPerformance_periodic(); -private: - QString m_dataDir; -}; -#endif diff --git a/tests/nsl/smooth/NSLSmoothTest.cpp b/tests/nsl/smooth/NSLSmoothTest.cpp deleted file mode 100644 --- a/tests/nsl/smooth/NSLSmoothTest.cpp +++ /dev/null @@ -1,438 +0,0 @@ -/*************************************************************************** - File : NSLSmoothTest.cpp - Project : LabPlot - Description : NSL Tests for smoothing - -------------------------------------------------------------------- - Copyright : (C) 2019 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 "NSLSmoothTest.h" - -extern "C" { -#include "backend/nsl/nsl_smooth.h" -} - -//############################################################################## -//################# moving average tests -//############################################################################## - -const int N = 9; -const int points = 5; -const nsl_smooth_weight_type weight = nsl_smooth_weight_uniform; - -void NSLSmoothTest::testMA_padnone() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2, 3, 2.4, 2, 1.8, 1.6, 3, 14/3., 9}; - - int status = nsl_smooth_moving_average(data, N, points, weight, nsl_smooth_pad_none); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMA_padmirror() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {3.2, 2.6, 2.4, 2, 1.8, 1.6, 3, 3.6, 3.8}; - - int status = nsl_smooth_moving_average(data, N, points, weight, nsl_smooth_pad_mirror); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMA_padnearest() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2.6, 2.6, 2.4, 2, 1.8, 1.6, 3, 4.6, 6.4}; - - int status = nsl_smooth_moving_average(data, N, points, weight, nsl_smooth_pad_nearest); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMA_padconstant() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {1.8, 2.2, 2.4, 2, 1.8, 1.6, 3, 2.8, 2.8}; - - int status = nsl_smooth_moving_average(data, N, points, weight, nsl_smooth_pad_constant); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMA_padperiodic() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {4.4, 4, 2.4, 2, 1.8, 1.6, 3, 3.2, 3.6}; - - int status = nsl_smooth_moving_average(data, N, points, weight, nsl_smooth_pad_periodic); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -//############################################################################## -//################# lagged moving average tests -//############################################################################## - -void NSLSmoothTest::testMAL_padnone() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2, 2, 3, 2.75, 2.4, 2, 1.8, 1.6, 3}; - - int status = nsl_smooth_moving_average_lagged(data, N, points, weight, nsl_smooth_pad_none); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMAL_padmirror() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2.4, 2.6, 3.2, 2.6, 2.4, 2, 1.8, 1.6, 3}; - - int status = nsl_smooth_moving_average_lagged(data, N, points, weight, nsl_smooth_pad_mirror); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMAL_padnearest() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2., 2., 2.6, 2.6, 2.4, 2, 1.8, 1.6, 3}; - - int status = nsl_smooth_moving_average_lagged(data, N, points, weight, nsl_smooth_pad_nearest); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMAL_padconstant() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {0.4, 0.8, 1.8, 2.2, 2.4, 2, 1.8, 1.6, 3}; - - int status = nsl_smooth_moving_average_lagged(data, N, points, weight, nsl_smooth_pad_constant); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testMAL_padperiodic() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {3.2, 3.6, 4.4, 4, 2.4, 2, 1.8, 1.6, 3}; - - int status = nsl_smooth_moving_average_lagged(data, N, points, weight, nsl_smooth_pad_periodic); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -//############################################################################## -//################# percentile tests -//############################################################################## - -const double percentile = 0.5; - -void NSLSmoothTest::testPercentile_padnone() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2, 2, 2, 1.5, 1, 1, 1, 2.5, 9}; - - int status = nsl_smooth_percentile(data, N, points, percentile, nsl_smooth_pad_none); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testPercentile_padmirror() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2, 2, 2, 1.5, 1, 1, 1, 2.5, 2.5}; - - int status = nsl_smooth_percentile(data, N, points, percentile, nsl_smooth_pad_mirror); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testPercentile_padnearest() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {2, 2, 2, 1.5, 1, 1, 1, 2.5, 6.5}; - - int status = nsl_smooth_percentile(data, N, points, percentile, nsl_smooth_pad_nearest); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testPercentile_padconstant() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {1, 2, 2, 1.5, 1, 1, 1, 0.5, 0.5}; - - int status = nsl_smooth_percentile(data, N, points, percentile, nsl_smooth_pad_constant); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testPercentile_padperiodic() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {3, 2, 2, 1.5, 1, 1, 1, 1.5, 2}; - - int status = nsl_smooth_percentile(data, N, points, percentile, nsl_smooth_pad_periodic); - QCOMPARE(status, 0); - for(int i = 0; i < N; i++) - QCOMPARE(data[i], result[i]); -} - -//############################################################################## -//################# Savitzky-Golay coeff tests -//############################################################################## - -void NSLSmoothTest::testSG_coeff31() { - int points = 3, order = 1; - gsl_matrix *h = gsl_matrix_alloc(points, points); - double result[] = {1, 1, 1}; - - int status = nsl_smooth_savgol_coeff(points, order, h); - QCOMPARE(status, 0); - for(int i = 0; i < points; i++) - QCOMPARE(points * gsl_matrix_get(h,(points-1)/2, i), result[i]); - - gsl_matrix_free(h); -} - -void NSLSmoothTest::testSG_coeff51() { - int points = 5, order = 1; - gsl_matrix *h = gsl_matrix_alloc(points, points); - double result[] = {1, 1, 1, 1, 1}; - - int status = nsl_smooth_savgol_coeff(points, order, h); - QCOMPARE(status, 0); - for(int i = 0; i < points; i++) - QCOMPARE(points * gsl_matrix_get(h,(points-1)/2, i), result[i]); - - gsl_matrix_free(h); -} - -void NSLSmoothTest::testSG_coeff53() { - int points = 5, order = 3; - gsl_matrix *h = gsl_matrix_alloc(points, points); - double result[] = {-3, 12, 17, 12, -3}; - - int status = nsl_smooth_savgol_coeff(points, order, h); - QCOMPARE(status, 0); - for(int i = 0; i < points; i++) - QCOMPARE(35 * gsl_matrix_get(h,(points-1)/2, i), result[i]); - - gsl_matrix_free(h); -} - -void NSLSmoothTest::testSG_coeff73() { - int points = 7, order = 3; - gsl_matrix *h = gsl_matrix_alloc(points, points); - double result[] = {-2, 3, 6, 7, 6, 3, -2}; - - int status = nsl_smooth_savgol_coeff(points, order, h); - QCOMPARE(status, 0); - for(int i = 0; i < points; i++) - QCOMPARE(21 * gsl_matrix_get(h,(points-1)/2, i), result[i]); - - gsl_matrix_free(h); -} - -void NSLSmoothTest::testSG_coeff74() { - int points = 7, order = 4; - gsl_matrix *h = gsl_matrix_alloc(points, points); - double result[] = {5, -30, 75, 131, 75, -30, 5}; - - int status = nsl_smooth_savgol_coeff(points, order, h); - QCOMPARE(status, 0); - - for(int i = 0; i < points; i++) { - FuzzyCompare(result[i], 231 * gsl_matrix_get(h,(points-1)/2, i), 1.e-10); - //QCOMPARE(231 * gsl_matrix_get(h,(points-1)/2, i), result[i]); - } - - gsl_matrix_free(h); -} - -void NSLSmoothTest::testSG_coeff92() { - int points = 9, order = 2; - gsl_matrix *h = gsl_matrix_alloc(points, points); - double result[] = {-21, 14, 39, 54, 59, 54, 39, 14, -21}; - - int status = nsl_smooth_savgol_coeff(points, order, h); - QCOMPARE(status, 0); - - for(int i = 0; i < points; i++) { - //FuzzyCompare(result[i], 231 * gsl_matrix_get(h,(points-1)/2, i), 1.e-10); - QCOMPARE(231 * gsl_matrix_get(h,(points-1)/2, i), result[i]); - } - - gsl_matrix_free(h); -} - -void NSLSmoothTest::testSG_coeff94() { - int points = 9, order = 4; - gsl_matrix *h = gsl_matrix_alloc(points, points); - double result[] = {15, -55, 30, 135, 179, 135, 30, -55, 15}; - - int status = nsl_smooth_savgol_coeff(points, order, h); - QCOMPARE(status, 0); - - for(int i = 0; i < points; i++) { - FuzzyCompare(result[i], 429 * gsl_matrix_get(h,(points-1)/2, i), 1.e-10); - //QCOMPARE(429 * gsl_matrix_get(h,(points-1)/2, i), result[i]); - } - - gsl_matrix_free(h); -} - -//############################################################################## -//################# Savitzky-Golay modes -//############################################################################## - -const int n = 9, m = 5, order = 2; - -void NSLSmoothTest::testSG_mode_interp() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {1.65714285714286, 3.17142857142857, 3.54285714285714, 2.85714285714285, 0.65714285714287, 0.17142857142858, 1., 4., 9.}; - - int status = nsl_smooth_savgol(data, n, m, order, nsl_smooth_pad_interp); - QCOMPARE(status, 0); - - for(int i = 0; i < n; i++) - QCOMPARE(data[i], result[i]); - //FuzzyCompare(data[i], result[i], 1.e-11); -} - -void NSLSmoothTest::testSG_mode_mirror() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {1.48571428571430, 3.02857142857143, 3.542857142857, 2.857142857143, 0.657142857143, 0.171428571429, 1., 5.02857142857142, 6.94285714285713}; - - int status = nsl_smooth_savgol(data, n, m, order, nsl_smooth_pad_mirror); - QCOMPARE(status, 0); - - for(int i = 0; i < n; i++) - FuzzyCompare(data[i], result[i], 1.e-11); - //QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testSG_mode_nearest() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {1.74285714285715, 3.02857142857143, 3.542857142857, 2.857142857143, 0.657142857143, 0.171428571429, 1., 4.6, 7.97142857142856}; - - int status = nsl_smooth_savgol(data, n, m, order, nsl_smooth_pad_nearest); - QCOMPARE(status, 0); - - for(int i = 0; i < n; i++) - FuzzyCompare(data[i], result[i], 1.e-11); - //QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testSG_mode_constant() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {1.22857142857143, 3.2, 3.542857142857, 2.857142857143, 0.657142857143, 0.171428571429, 1., 5.37142857142855, 5.65714285714284}; - - int status = nsl_smooth_savgol(data, n, m, order, nsl_smooth_pad_constant); - QCOMPARE(status, 0); - - for(int i = 0; i < n; i++) - FuzzyCompare(data[i], result[i], 1.e-11); - //QCOMPARE(data[i], result[i]); -} - -void NSLSmoothTest::testSG_mode_periodic() { - double data[] = {2, 2, 5, 2, 1, 0, 1, 4, 9}; - double result[] = {3.97142857142858, 2.42857142857144, 3.542857142857, 2.857142857143, 0.657142857143, 0.171428571429, 1., 5.2, 6.17142857142856}; - - int status = nsl_smooth_savgol(data, n, m, order, nsl_smooth_pad_periodic); - QCOMPARE(status, 0); - - for(int i = 0; i < n; i++) - FuzzyCompare(data[i], result[i], 1.e-11); - //QCOMPARE(data[i], result[i]); -} - -//############################################################################## -//################# performance -//############################################################################## - -const int nn = 1e6; - -void NSLSmoothTest::testPerformance_interp() { - double* data = new double[nn]; - - QBENCHMARK { - for (int i = 0; i < nn; i++) - data[i] = i; - int status = nsl_smooth_savgol(data, nn, m, order, nsl_smooth_pad_interp); - QCOMPARE(status, 0); - } - delete[] data; -} - -void NSLSmoothTest::testPerformance_mirror() { - double* data = new double[nn]; - - QBENCHMARK { - for (int i = 0; i < nn; i++) - data[i] = i; - int status = nsl_smooth_savgol(data, nn, m, order, nsl_smooth_pad_mirror); - QCOMPARE(status, 0); - } - delete[] data; -} - -void NSLSmoothTest::testPerformance_nearest() { - double* data = new double[nn]; - - QBENCHMARK { - for (int i = 0; i < nn; i++) - data[i] = i; - int status = nsl_smooth_savgol(data, nn, m, order, nsl_smooth_pad_nearest); - QCOMPARE(status, 0); - } - delete[] data; -} - -void NSLSmoothTest::testPerformance_constant() { - double* data = new double[nn]; - - QBENCHMARK { - for (int i = 0; i < nn; i++) - data[i] = i; - int status = nsl_smooth_savgol(data, nn, m, order, nsl_smooth_pad_constant); - QCOMPARE(status, 0); - } - delete[] data; -} - -void NSLSmoothTest::testPerformance_periodic() { - double* data = new double[nn]; - - QBENCHMARK { - for (int i = 0; i < nn; i++) - data[i] = i; - int status = nsl_smooth_savgol(data, nn, m, order, nsl_smooth_pad_periodic); - QCOMPARE(status, 0); - } - delete[] data; -} - -QTEST_MAIN(NSLSmoothTest)