diff --git a/lumberjack.pro b/lumberjack.pro index a7b4a59..303952f 100644 --- a/lumberjack.pro +++ b/lumberjack.pro @@ -47,6 +47,7 @@ SOURCES += \ src/main.cpp \ src/mainwindow.cpp \ src/plugins/plugin_exporter.cpp \ + src/plugins/plugin_filter.cpp \ src/plugins/plugin_importer.cpp \ src/plugins/plugin_registry.cpp \ src/widgets/about_dialog.cpp \ @@ -55,6 +56,7 @@ SOURCES += \ src/widgets/dataview_tree.cpp \ src/widgets/dataview_widget.cpp \ src/widgets/debug_widget.cpp \ + src/widgets/filters_widget.cpp \ src/widgets/plot_sampler.cpp \ src/widgets/plugins_dialog.cpp \ src/widgets/series_editor_dialog.cpp \ @@ -87,11 +89,12 @@ HEADERS += \ src/widgets/dataview_tree.hpp \ src/widgets/dataview_widget.hpp \ src/widgets/debug_widget.hpp \ + src/widgets/filters_widget.hpp \ src/widgets/plot_sampler.hpp \ src/widgets/plugins_dialog.hpp \ src/widgets/series_editor_dialog.hpp \ src/widgets/stats_widget.hpp \ - src/widgets/timeline_widget.hpp \ + src/widgets/timeline_widget.hpp # simple-fft includes HEADERS += \ @@ -109,6 +112,7 @@ FORMS += \ ui/curve_editor_dialog.ui \ ui/dataview_widget.ui \ ui/debug_widget.ui \ + ui/filters_widget.ui \ ui/mainwindow.ui \ ui/plugins_dialog.ui \ ui/stats_view.ui @@ -167,17 +171,19 @@ CONFIG(debug, debug|release) { COPIES += dllFiles -CONFIG(debug, debug | release) { - win32 { - # Copy required .DLL files - QMAKE_POST_LINK += $$[QT_INSTALL_BINS]\windeployqt --debug --opengl --openglwidgets --widgets --compiler-runtime $$shell_path($$quote($$DESTDIR))\lumberjack.exe $$escape_expand(\n\t) - } -} else { - win32 { - # Copy required .DLL files - QMAKE_POST_LINK += $$[QT_INSTALL_BINS]\windeployqt --release --opengl --openglwidgets --widgets --compiler-runtime $$shell_path($$quote($$DESTDIR))\lumberjack.exe $$escape_expand(\n\t) +win32 { + # Qt libraries required by windeployqt + QT_LIB = "--core --opengl --openglwidgets --qml --quick --quickwidgets --widgets --compiler-runtime" + + CONFIG(release, debug|release) { + # Release mode - run windeployqt + QMAKE_POST_LINK += $$[QT_INSTALL_BINS]\windeployqt --force $$quote($$QT_LIB) $$shell_path($$quote($$DESTDIR))\lumberjack.exe $$escape_expand(\n\t) + } else { + # Debug mode - do not run windeployqt + # Expectation is that the code is run from within QtCreator } } + RESOURCES += \ resources.qrc diff --git a/plugins/bitmask_filter/bitmask_filter.cpp b/plugins/bitmask_filter/bitmask_filter.cpp new file mode 100644 index 0000000..fd19bac --- /dev/null +++ b/plugins/bitmask_filter/bitmask_filter.cpp @@ -0,0 +1,100 @@ +#include + + +#include "bitmask_filter.hpp" + + +bool BitmaskFilter::beforeProcessStep() +{ + // TODO - set filter options manually + auto gen = QRandomGenerator::securelySeeded(); + + // Generate a random mask + m_mask = gen.bounded(0xFFFFFFFF); + + return true; +} + + +void BitmaskFilter::afterProcessStep() +{ + // TODO +} + + +void BitmaskFilter::cancelProcessing() +{ + // TODO + m_processing = false; +} + + +uint8_t BitmaskFilter::getProgress() const +{ + if (m_input.isNull() || m_output.isNull()) return 0; + if (m_input->size() == 0) return 0; + + float progress = (float) m_output->size() / (float) m_input->size(); + + return (uint8_t) (progress * 100); +} + + +bool BitmaskFilter::setFilterInputs(QList inputs, QStringList &errors) +{ + if (!FilterPlugin::setFilterInputs(inputs, errors)) + { + return false; + } + + m_input = inputs.first(); + + if (m_input.isNull()) + { + errors.append(tr("Null data series provided")); + return false; + } + + return true; +} + + +bool BitmaskFilter::processData() +{ + QString label = m_input->getLabel() + " - Bitmask"; + m_output = DataSeriesPointer(new DataSeries(label)); + + uint64_t idx = 0; + + m_processing = true; + + while (m_processing && idx < m_input->size()) + { + DataPoint point = m_input->getDataPoint(idx); + + // TODO: Support negative values? + uint32_t value = point.value > 0 ? (uint32_t) point.value : 0; + + // Hack for now, apply a "random" AND mask + value &= m_mask; + + point.value = (double) value; + + m_output->addData(point); + + idx++; + } + + m_processing = false; + return true; + +} + + +QList BitmaskFilter::getFilterOutputs(void) +{ + QList outputs; + + outputs.append(m_output); + return outputs; +} diff --git a/plugins/bitmask_filter/bitmask_filter.hpp b/plugins/bitmask_filter/bitmask_filter.hpp new file mode 100644 index 0000000..ab5eb9a --- /dev/null +++ b/plugins/bitmask_filter/bitmask_filter.hpp @@ -0,0 +1,57 @@ +#ifndef LUMBERJACK_CSV_IMPORTER_HPP +#define LUMBERJACK_CSV_IMPORTER_HPP + +#include "bitmask_filter_global.h" +#include "plugin_filter.hpp" + + +/** + * TODO: + * - Apply different bitmask operations (XOR / AND / OR) + * - Specify bitmask + */ + + +/** + * @brief The ScaleOffsetFilter class provides simple scaling and offset functionality + */ +class CUSTOM_FILTER_EXPORT BitmaskFilter : public FilterPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID FilterInterface_iid) + Q_INTERFACES(FilterPlugin) +public: + virtual ~BitmaskFilter() = default; + + // PluginBase functionality + virtual QString pluginName(void) const override { return m_name; } + virtual QString pluginDescription(void) const override { return m_description; } + virtual QString pluginVersion(void) const override { return m_version; } + + // DataProcessingPlugin functionality + virtual bool beforeProcessStep(void) override; + virtual void afterProcessStep(void) override; + virtual void cancelProcessing(void) override; + virtual uint8_t getProgress(void) const override; + + // FilterPlugin functionality + virtual unsigned int getMinInputCount(void) const override { return 1; } + virtual unsigned int getMaxInputCount(void) const override { return 1; } + virtual bool setFilterInputs(QList inputs, QStringList &errors) override; + virtual bool processData(void) override; + virtual QList getFilterOutputs(void) override; + +protected: + const QString m_name = "Bitmask Filter"; + const QString m_description = "Apply custom bitmasking patterns"; + const QString m_version = "0.1.0"; + + uint32_t m_mask = 0x01234567; + + bool m_processing = false; + + DataSeriesPointer m_input; + DataSeriesPointer m_output; +}; + +#endif // LUMBERJACK_CSV_IMPORTER_HPP diff --git a/plugins/bitmask_filter/bitmask_filter.json b/plugins/bitmask_filter/bitmask_filter.json new file mode 100644 index 0000000..5119d14 --- /dev/null +++ b/plugins/bitmask_filter/bitmask_filter.json @@ -0,0 +1 @@ +{ "Keys": [ "bitmask_filter" ] } diff --git a/plugins/scaler_filter/scaler_filter.pro b/plugins/bitmask_filter/bitmask_filter.pro similarity index 79% rename from plugins/scaler_filter/scaler_filter.pro rename to plugins/bitmask_filter/bitmask_filter.pro index 47b156d..5f9481d 100644 --- a/plugins/scaler_filter/scaler_filter.pro +++ b/plugins/bitmask_filter/bitmask_filter.pro @@ -13,13 +13,16 @@ INCLUDEPATH += \ ../../src/plugins HEADERS += \ - scaler_filter_global.h \ - scaler_filter.hpp \ + ../../src/data_series.hpp \ ../../src/plugins/plugin_base.hpp \ ../../src/plugins/plugin_filter.hpp \ + bitmask_filter_global.h \ + bitmask_filter.hpp \ SOURCES += \ - scaler_filter.cpp + ../../src/data_series.cpp \ + ../../src/plugins/plugin_filter.cpp \ + bitmask_filter.cpp # Default rules for deployment. unix { @@ -30,7 +33,6 @@ unix { CONFIG(debug, debug|release) { CONFIG += debug DESTDIR = build/debug - } else { CONFIG += release DESTDIR = ../build/release @@ -46,4 +48,4 @@ UI_DIR = build/ui !isEmpty(target.path): INSTALLS += target DISTFILES += \ - scaler_filter.json + bitmask_filter.json diff --git a/plugins/offset_filter/offset_filter_global.h b/plugins/bitmask_filter/bitmask_filter_global.h similarity index 100% rename from plugins/offset_filter/offset_filter_global.h rename to plugins/bitmask_filter/bitmask_filter_global.h diff --git a/plugins/csv_exporter/lumberjack_csv_exporter.cpp b/plugins/csv_exporter/lumberjack_csv_exporter.cpp index 2cd83a7..400f485 100644 --- a/plugins/csv_exporter/lumberjack_csv_exporter.cpp +++ b/plugins/csv_exporter/lumberjack_csv_exporter.cpp @@ -20,8 +20,9 @@ QStringList LumberjackCSVExporter::supportedFileTypes() const } -bool LumberjackCSVExporter::beforeExport(void) +bool LumberjackCSVExporter::beforeProcessStep(void) { + // TODO: Set export options return true; } @@ -30,19 +31,13 @@ bool LumberjackCSVExporter::beforeExport(void) /* * Export the provided series to a CSV file */ -bool LumberjackCSVExporter::exportData(QList &series, QStringList &errors) +bool LumberjackCSVExporter::processData() { - if (m_filename.isEmpty()) - { - errors.append(tr("Filename is empty")); - return false; - } - QFile outputFile(m_filename); if (!outputFile.open(QIODevice::WriteOnly) || !outputFile.isOpen() || !outputFile.isWritable()) { - errors.append(tr("Could not open file for writing")); + qCritical() << tr("Could not open file for writing"); return false; } @@ -50,7 +45,7 @@ bool LumberjackCSVExporter::exportData(QList &series, QString m_data.clear(); m_indices.clear(); - for (auto s : series) + for (auto s : m_series) { if (!s.isNull()) { @@ -75,7 +70,7 @@ bool LumberjackCSVExporter::exportData(QList &series, QString double tMin = LONG_MAX; double tMax = -LONG_MAX; - for (auto s : series) + for (auto s : m_series) { if (s.isNull()) continue; if (s->size() == 0) continue; @@ -238,13 +233,13 @@ QStringList LumberjackCSVExporter::nextDataRow(bool &valid) } -void LumberjackCSVExporter::cancelExport() +void LumberjackCSVExporter::cancelProcessing() { m_isExporting = false; } -uint8_t LumberjackCSVExporter::getExportProgress(void) const +uint8_t LumberjackCSVExporter::getProgress(void) const { double dt = m_maxTimestamp - m_minTimestamp; diff --git a/plugins/csv_exporter/lumberjack_csv_exporter.hpp b/plugins/csv_exporter/lumberjack_csv_exporter.hpp index 3fa05fa..34775b4 100644 --- a/plugins/csv_exporter/lumberjack_csv_exporter.hpp +++ b/plugins/csv_exporter/lumberjack_csv_exporter.hpp @@ -21,10 +21,11 @@ class CSV_EXPORTER_EXPORT LumberjackCSVExporter : public ExportPlugin // Exporter plugin functionality virtual QStringList supportedFileTypes(void) const override; - virtual bool beforeExport(void) override; - virtual bool exportData(QList &series, QStringList &errors) override; - virtual void cancelExport(void) override; - virtual uint8_t getExportProgress(void) const override; + virtual bool processData(void) override; + + virtual bool beforeProcessStep(void) override; + virtual void cancelProcessing(void) override; + virtual uint8_t getProgress(void) const override; protected: const QString m_name = "CSV Exporter"; diff --git a/plugins/csv_importer/lumberjack_csv_importer.cpp b/plugins/csv_importer/lumberjack_csv_importer.cpp index 02f8ac9..1bf02df 100644 --- a/plugins/csv_importer/lumberjack_csv_importer.cpp +++ b/plugins/csv_importer/lumberjack_csv_importer.cpp @@ -31,7 +31,7 @@ QStringList LumberjackCSVImporter::supportedFileTypes() const * @brief LumberjackCSVImporter::beforeLoadData - Open configuration dialog * @return */ -bool LumberjackCSVImporter::beforeImport(void) +bool LumberjackCSVImporter::beforeProcessStep(void) { CSVImportOptionsDialog dlg(m_filename); @@ -47,12 +47,12 @@ bool LumberjackCSVImporter::beforeImport(void) /** - * @brief LumberjackCSVImporter::loadDataFile + * @brief LumberjackCSVImporter::processData * @param filename - The filename to load * @param errors - * @return */ -bool LumberjackCSVImporter::importData(QStringList &errors) +bool LumberjackCSVImporter::processData(void) { // Reset importer to initial conditions m_headers.clear(); @@ -64,7 +64,7 @@ bool LumberjackCSVImporter::importData(QStringList &errors) if (!fi.exists() || !fi.isFile()) { - errors.append(tr("File does not exist")); + qCritical() << tr("File does not exist"); return false; } @@ -75,7 +75,7 @@ bool LumberjackCSVImporter::importData(QStringList &errors) if (!m_file->open(QIODevice::ReadOnly) || !m_file->isOpen() || !m_file->isReadable()) { - errors.append(tr("Could not open file for reading")); + qCritical() << tr("Could not open file for reading"); m_file->close(); return false; } @@ -107,7 +107,7 @@ bool LumberjackCSVImporter::importData(QStringList &errors) row = line.split(delimiter); - if (!processRow(lineCount, row, errors)) + if (!processRow(lineCount, row)) { badLineCount++; } @@ -124,7 +124,7 @@ bool LumberjackCSVImporter::importData(QStringList &errors) if (badLineCount > 0) { - errors.append(QString("Lines with errors: " + QString::number(badLineCount))); + qWarning() << QString("Lines with errors: " + QString::number(badLineCount)); } return true; @@ -138,15 +138,12 @@ bool LumberjackCSVImporter::importData(QStringList &errors) * @param errors * @return */ -bool LumberjackCSVImporter::processRow(int rowIndex, const QStringList &row, QStringList& errors) +bool LumberjackCSVImporter::processRow(int rowIndex, const QStringList &row) { - // TODO: error messages - Q_UNUSED(errors) - if (rowIndex == m_options.rowHeaders) { - return extractHeaders(rowIndex, row, errors); + return extractHeaders(rowIndex, row); } else if (rowIndex == m_options.rowUnits) { @@ -156,7 +153,7 @@ bool LumberjackCSVImporter::processRow(int rowIndex, const QStringList &row, QSt } else if (rowIndex >= m_options.rowDataStart) { - return extractData(rowIndex, row, errors); + return extractData(rowIndex, row); } } @@ -168,13 +165,10 @@ bool LumberjackCSVImporter::processRow(int rowIndex, const QStringList &row, QSt * @param errors * @return */ -bool LumberjackCSVImporter::extractHeaders(int rowIndex, const QStringList &row, QStringList &errors) +bool LumberjackCSVImporter::extractHeaders(int rowIndex, const QStringList &row) { Q_UNUSED(rowIndex); - // TODO: Error messages - Q_UNUSED(errors); - m_headers.clear(); for (int ii = 0; ii < row.length(); ii++) @@ -219,11 +213,8 @@ bool LumberjackCSVImporter::extractHeaders(int rowIndex, const QStringList &row, * @param errors * @return */ -bool LumberjackCSVImporter::extractData(int rowIndex, const QStringList &row, QStringList &errors) +bool LumberjackCSVImporter::extractData(int rowIndex, const QStringList &row) { - // TODO: Error messages - Q_UNUSED(errors) - double timestamp = 0; QString text; @@ -418,7 +409,7 @@ bool LumberjackCSVImporter::extractTimestamp(int rowIndex, const QStringList &ro } -void LumberjackCSVImporter::afterImport(void) +void LumberjackCSVImporter::afterProcessStep(void) { if (m_file) { @@ -433,13 +424,13 @@ void LumberjackCSVImporter::afterImport(void) } -void LumberjackCSVImporter::cancelImport(void) +void LumberjackCSVImporter::cancelProcessing(void) { m_isImporting = false; } -uint8_t LumberjackCSVImporter::getImportProgress(void) const +uint8_t LumberjackCSVImporter::getProgress(void) const { if (!m_isImporting) return 0; diff --git a/plugins/csv_importer/lumberjack_csv_importer.hpp b/plugins/csv_importer/lumberjack_csv_importer.hpp index 606cdbd..916c54d 100644 --- a/plugins/csv_importer/lumberjack_csv_importer.hpp +++ b/plugins/csv_importer/lumberjack_csv_importer.hpp @@ -25,12 +25,13 @@ class CSV_IMPORTER_EXPORT LumberjackCSVImporter : public ImportPlugin // Importer plugin functionality virtual QStringList supportedFileTypes(void) const override; - virtual bool beforeImport(void) override; - virtual bool importData(QStringList &errors) override; - virtual void afterImport(void) override; - virtual void cancelImport(void) override; + virtual bool processData(void) override; - virtual uint8_t getImportProgress(void) const override; + virtual bool beforeProcessStep(void) override; + virtual void afterProcessStep(void) override; + virtual void cancelProcessing(void) override; + + virtual uint8_t getProgress(void) const override; virtual QList> getDataSeries(void) const override; @@ -46,9 +47,9 @@ class CSV_IMPORTER_EXPORT LumberjackCSVImporter : public ImportPlugin QStringList m_headers; //! Internal functions for processing data - bool processRow(int rowIndex, const QStringList &row, QStringList &errors); - bool extractHeaders(int rowIndex, const QStringList &row, QStringList &errors); - bool extractData(int rowIndex, const QStringList &row, QStringList &errors); + bool processRow(int rowIndex, const QStringList &row); + bool extractHeaders(int rowIndex, const QStringList &row); + bool extractData(int rowIndex, const QStringList &row); bool extractTimestamp(int rowIndex, const QStringList &row, double ×tamp); // Keep track of data columns while loading diff --git a/plugins/offset_filter/offset_filter.cpp b/plugins/offset_filter/offset_filter.cpp deleted file mode 100644 index 93862e2..0000000 --- a/plugins/offset_filter/offset_filter.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "offset_filter.hpp" - - -OffsetFilter::OffsetFilter() -{ - // TODO -} diff --git a/plugins/offset_filter/offset_filter.hpp b/plugins/offset_filter/offset_filter.hpp deleted file mode 100644 index 2e1bf38..0000000 --- a/plugins/offset_filter/offset_filter.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef LUMBERJACK_CSV_IMPORTER_HPP -#define LUMBERJACK_CSV_IMPORTER_HPP - -#include "offset_filter_global.h" -#include "plugin_filter.hpp" - - -/** - * @brief The ScaleOffsetFilter class provides simple scaling and offset functionality - */ -class CUSTOM_FILTER_EXPORT OffsetFilter : public FilterPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID FilterInterface_iid) - Q_INTERFACES(FilterPlugin) -public: - OffsetFilter(); - - // Base plugin functionality - virtual QString pluginName(void) const override { return m_name; } - virtual QString pluginDescription(void) const override { return m_description; } - virtual QString pluginVersion(void) const override { return m_version; } - -protected: - const QString m_name = "Offset Filter"; - const QString m_description = "Apply custom offset to a dataset"; - const QString m_version = "0.1.0"; - -}; - -#endif // LUMBERJACK_CSV_IMPORTER_HPP diff --git a/plugins/offset_filter/offset_filter.json b/plugins/offset_filter/offset_filter.json deleted file mode 100644 index 3aff03c..0000000 --- a/plugins/offset_filter/offset_filter.json +++ /dev/null @@ -1 +0,0 @@ -{ "Keys": [ "offset_filter" ] } diff --git a/plugins/offset_scaler_filter/offset_scaler_filter.cpp b/plugins/offset_scaler_filter/offset_scaler_filter.cpp new file mode 100644 index 0000000..05ed22c --- /dev/null +++ b/plugins/offset_scaler_filter/offset_scaler_filter.cpp @@ -0,0 +1,99 @@ +#include + + +#include "offset_scaler_filter.hpp" + + +bool OffsetScalerFilter::beforeProcessStep() +{ + // TODO - set filter options manually + auto gen = QRandomGenerator(); + + m_scaler = (double) gen.bounded(-10, 10) / 10; + m_offset = (double) gen.bounded(-100, 100) / 10; + + return true; +} + + +void OffsetScalerFilter::afterProcessStep() +{ + // TODO +} + + +void OffsetScalerFilter::cancelProcessing() +{ + // TODO + m_processing = false; +} + + +uint8_t OffsetScalerFilter::getProgress() const +{ + if (m_input.isNull() || m_output.isNull()) return 0; + if (m_input->size() == 0) return 0; + + float progress = (float) m_output->size() / (float) m_input->size(); + + return (uint8_t) (progress * 100); +} + + +bool OffsetScalerFilter::setFilterInputs(QList inputs, QStringList &errors) +{ + if (!FilterPlugin::setFilterInputs(inputs, errors)) + { + return false; + } + + m_input = inputs.first(); + + if (m_input.isNull()) + { + errors.append(tr("Null data series provided")); + return false; + } + + return true; +} + + +bool OffsetScalerFilter::processData() +{ + QString label = m_input->getLabel() + " - Scale + Offset"; + m_output = DataSeriesPointer(new DataSeries(label)); + + uint64_t idx = 0; + + m_processing = true; + + qDebug() << "filtering data"; + + while (m_processing && idx < m_input->size()) + { + DataPoint point = m_input->getDataPoint(idx); + + point.value *= m_scaler; + point.value += m_offset; + + m_output->addData(point); + + idx++; + } + + qDebug() << "Done:" << m_input->size() << m_output->size(); + + m_processing = false; + return true; + +} + + +QList OffsetScalerFilter::getFilterOutputs(void) +{ + QList outputs; + + outputs.append(m_output); + return outputs; +} diff --git a/plugins/offset_scaler_filter/offset_scaler_filter.hpp b/plugins/offset_scaler_filter/offset_scaler_filter.hpp new file mode 100644 index 0000000..992c49c --- /dev/null +++ b/plugins/offset_scaler_filter/offset_scaler_filter.hpp @@ -0,0 +1,51 @@ +#ifndef LUMBERJACK_CSV_IMPORTER_HPP +#define LUMBERJACK_CSV_IMPORTER_HPP + +#include "offset_scaler_filter_global.h" +#include "plugin_filter.hpp" + + +/** + * @brief The ScaleOffsetFilter class provides simple scaling and offset functionality + */ +class CUSTOM_FILTER_EXPORT OffsetScalerFilter : public FilterPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID FilterInterface_iid) + Q_INTERFACES(FilterPlugin) +public: + virtual ~OffsetScalerFilter() = default; + + // PluginBase functionality + virtual QString pluginName(void) const override { return m_name; } + virtual QString pluginDescription(void) const override { return m_description; } + virtual QString pluginVersion(void) const override { return m_version; } + + // DataProcessingPlugin functionality + virtual bool beforeProcessStep(void) override; + virtual void afterProcessStep(void) override; + virtual void cancelProcessing(void) override; + virtual uint8_t getProgress(void) const override; + + // FilterPlugin functionality + virtual unsigned int getMinInputCount(void) const override { return 1; } + virtual unsigned int getMaxInputCount(void) const override { return 1; } + virtual bool setFilterInputs(QList inputs, QStringList &errors) override; + virtual bool processData(void) override; + virtual QList getFilterOutputs(void) override; + +protected: + const QString m_name = "Offset / Scaler Filter"; + const QString m_description = "Apply custom offset and scaler to a dataset"; + const QString m_version = "0.1.0"; + + double m_scaler = 1.0; + double m_offset = 0.0; + + bool m_processing = false; + + DataSeriesPointer m_input; + DataSeriesPointer m_output; +}; + +#endif // LUMBERJACK_CSV_IMPORTER_HPP diff --git a/plugins/offset_scaler_filter/offset_scaler_filter.json b/plugins/offset_scaler_filter/offset_scaler_filter.json new file mode 100644 index 0000000..b2880ff --- /dev/null +++ b/plugins/offset_scaler_filter/offset_scaler_filter.json @@ -0,0 +1 @@ +{ "Keys": [ "offset_scaler_filter" ] } diff --git a/plugins/offset_filter/offset_filter.pro b/plugins/offset_scaler_filter/offset_scaler_filter.pro similarity index 77% rename from plugins/offset_filter/offset_filter.pro rename to plugins/offset_scaler_filter/offset_scaler_filter.pro index 638422a..3c46844 100644 --- a/plugins/offset_filter/offset_filter.pro +++ b/plugins/offset_scaler_filter/offset_scaler_filter.pro @@ -13,13 +13,16 @@ INCLUDEPATH += \ ../../src/plugins HEADERS += \ - offset_filter_global.h \ - offset_filter.hpp \ + ../../src/data_series.hpp \ ../../src/plugins/plugin_base.hpp \ ../../src/plugins/plugin_filter.hpp \ + offset_scaler_filter_global.h \ + offset_scaler_filter.hpp \ SOURCES += \ - offset_filter.cpp + ../../src/data_series.cpp \ + ../../src/plugins/plugin_filter.cpp \ + offset_scaler_filter.cpp # Default rules for deployment. unix { @@ -30,7 +33,6 @@ unix { CONFIG(debug, debug|release) { CONFIG += debug DESTDIR = build/debug - } else { CONFIG += release DESTDIR = ../build/release @@ -46,4 +48,4 @@ UI_DIR = build/ui !isEmpty(target.path): INSTALLS += target DISTFILES += \ - offset_filter.json + offset_scaler_filter.json diff --git a/plugins/offset_filter/offset_filter.user b/plugins/offset_scaler_filter/offset_scaler_filter.user similarity index 100% rename from plugins/offset_filter/offset_filter.user rename to plugins/offset_scaler_filter/offset_scaler_filter.user diff --git a/plugins/scaler_filter/scaler_filter_global.h b/plugins/offset_scaler_filter/offset_scaler_filter_global.h similarity index 100% rename from plugins/scaler_filter/scaler_filter_global.h rename to plugins/offset_scaler_filter/offset_scaler_filter_global.h diff --git a/plugins/plugins.pro b/plugins/plugins.pro index 8f01348..73e4956 100644 --- a/plugins/plugins.pro +++ b/plugins/plugins.pro @@ -5,6 +5,6 @@ TEMPLATE = subdirs SUBDIRS += \ csv_importer \ csv_exporter \ - offset_filter \ - scaler_filter \ + bitmask_filter \ + offset_scaler_filter \ diff --git a/plugins/scaler_filter/.gitignore b/plugins/scaler_filter/.gitignore deleted file mode 100644 index b182c97..0000000 --- a/plugins/scaler_filter/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Build directories -./build/ \ No newline at end of file diff --git a/plugins/scaler_filter/scaler_filter.cpp b/plugins/scaler_filter/scaler_filter.cpp deleted file mode 100644 index 9bde7c0..0000000 --- a/plugins/scaler_filter/scaler_filter.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "scaler_filter.hpp" - - - -ScalerFilter::ScalerFilter() -{ - // TODO -} diff --git a/plugins/scaler_filter/scaler_filter.hpp b/plugins/scaler_filter/scaler_filter.hpp deleted file mode 100644 index c597da0..0000000 --- a/plugins/scaler_filter/scaler_filter.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef LUMBERJACK_CSV_IMPORTER_HPP -#define LUMBERJACK_CSV_IMPORTER_HPP - -#include "scaler_filter_global.h" -#include "plugin_filter.hpp" - - -/** - * @brief The ScaleOffsetFilter class provides simple scaling and offset functionality - */ -class CUSTOM_FILTER_EXPORT ScalerFilter : public FilterPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID FilterInterface_iid) - Q_INTERFACES(FilterPlugin) -public: - ScalerFilter(); - - // Base plugin functionality - virtual QString pluginName(void) const override { return m_name; } - virtual QString pluginDescription(void) const override { return m_description; } - virtual QString pluginVersion(void) const override { return m_version; } - -protected: - const QString m_name = "Scaler Filter"; - const QString m_description = "Apply custom scaler to a dataset"; - const QString m_version = "0.1.0"; - -}; - -#endif // LUMBERJACK_CSV_IMPORTER_HPP diff --git a/plugins/scaler_filter/scaler_filter.json b/plugins/scaler_filter/scaler_filter.json deleted file mode 100644 index cda4cf5..0000000 --- a/plugins/scaler_filter/scaler_filter.json +++ /dev/null @@ -1 +0,0 @@ -{ "Keys": [ "scaler_filter" ] } diff --git a/plugins/scaler_filter/scaler_filter.user b/plugins/scaler_filter/scaler_filter.user deleted file mode 100644 index ec10eea..0000000 --- a/plugins/scaler_filter/scaler_filter.user +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - EnvironmentId - {cc3e2f05-85bf-4229-a4de-54f19ed6baad} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 0 - 80 - true - true - 1 - 0 - false - true - false - 2 - true - true - 0 - 8 - true - false - 1 - true - true - true - *.md, *.MD, Makefile - false - true - true - - - - ProjectExplorer.Project.PluginSettings - - - true - false - true - true - true - true - - false - - - 0 - true - - true - true - Builtin.DefaultTidyAndClazy - 6 - true - - - - true - - - - - ProjectExplorer.Project.Target.0 - - Desktop - Desktop Qt 6.8.1 MinGW 64-bit - Desktop Qt 6.8.1 MinGW 64-bit - qt.qt6.681.win64_mingw_kit - 0 - 0 - 0 - - 0 - C:\lumberjack\plugins\scale_offset_filter - C:/lumberjack/plugins/scale_offset_filter - - - true - QtProjectManager.QMakeBuildStep - false - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - - - C:\lumberjack\plugins\scale_offset_filter\build\Desktop_Qt_6_8_1_MinGW_64_bit-Release - C:/lumberjack/plugins/scale_offset_filter/build/Desktop_Qt_6_8_1_MinGW_64_bit-Release - - - true - QtProjectManager.QMakeBuildStep - false - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - - 2 - - - 0 - Deploy - Deploy - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - 1 - - true - true - 0 - true - - 2 - - false - -e cpu-cycles --call-graph "dwarf,4096" -F 250 - - ProjectExplorer.CustomExecutableRunConfiguration - - false - true - true - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 22 - - - Version - 22 - - diff --git a/src/data_source_manager.cpp b/src/data_source_manager.cpp index 6ff6514..dcfb767 100644 --- a/src/data_source_manager.cpp +++ b/src/data_source_manager.cpp @@ -11,18 +11,16 @@ -DataImportWorker::DataImportWorker(QSharedPointer plugin) : m_plugin(plugin) +DataProcessWorker::DataProcessWorker(QSharedPointer plugin) : m_plugin(plugin) { } -void DataImportWorker::runImport() +void DataProcessWorker::run() { - m_errors.clear(); - - if (m_plugin) + if (m_plugin && !m_plugin.isNull()) { - m_result = m_plugin->importData(m_errors); + m_result = m_plugin->processData(); } else { @@ -31,58 +29,18 @@ void DataImportWorker::runImport() m_complete = true; - emit importCompleted(); -} - - -void DataImportWorker::cancelImport() -{ - if (m_plugin) - { - m_plugin->cancelImport(); - m_result = false; - } - - m_errors.append(tr("Import process cancelled")); - - m_complete = true; -} - - -DataExportWorker::DataExportWorker(QSharedPointer plugin, QList &series) - : m_plugin(plugin), m_series(series) -{ -} - - -void DataExportWorker::runExport() -{ - m_errors.clear(); - - if (m_plugin) - { - m_result = m_plugin->exportData(m_series, m_errors); - } - else - { - m_result = false; - } - - m_complete = true; - emit exportCompleted(); + emit processingComplete(); } -void DataExportWorker::cancelExport() +void DataProcessWorker::cancel() { if (m_plugin) { - m_plugin->cancelExport(); + m_plugin->cancelProcessing(); m_result = false; } - m_errors.append(tr("Export process cancelled")); - m_complete = true; } @@ -351,63 +309,19 @@ bool DataSourceManager::importData(QString filename) importer->setFilename(filename); - if (!importer->beforeImport()) + if (!importer->beforeProcessStep()) { // TODO: error message? return false; } - QProgressDialog progress; - - progress.setWindowTitle(tr("Importing Data")); - progress.setMinimum(0); - progress.setMaximum(100); - progress.setValue(0); - progress.setLabelText(tr("Importing data from file")); - - progress.show(); - - QApplication::processEvents(); - - // Spawn a new thread for importing - auto worker = DataImportWorker(importer); - auto *thread = new QThread; - - worker.moveToThread(thread); + bool result = runDataProcess( + importer, + tr("Importing Data"), + tr("Importing data from file") + ); - connect(&worker, &DataImportWorker::importCompleted, thread, &QThread::quit); - connect(thread, &QThread::started, &worker, &DataImportWorker::runImport); - connect(thread, &QThread::finished, thread, &QThread::deleteLater); - - thread->start(); - - qDebug() << "Importing data from" << filename; - - while (!thread->isFinished() && !worker.isComplete()) - { - // Check for manual cancel of import process - if (progress.wasCanceled()) - { - worker.cancelImport(); - thread->wait(); - } - - progress.setValue(importer->getImportProgress()); - - QApplication::processEvents(); - QThread::msleep(100); - } - - progress.cancel(); - progress.close(); - - for (QString err : worker.getErrors()) - { - // TODO: Display these better? - qWarning() << "Import err:" << err; - } - - if (worker.getResult()) + if (result) { // Create a new instance of the provided importer DataSource *source = new DataSource( @@ -495,48 +409,115 @@ bool DataSourceManager::exportData(QList &series, QString fil } exporter->setFilename(filename); + exporter->setDataSeries(series); - if (!exporter->beforeExport()) + if (!exporter->beforeProcessStep()) { // TODO: error mesage? return false; } + bool result = runDataProcess( + exporter, + tr("Exporting Data"), + tr("Exporting data to file") + ); + + return result; +} + + +bool DataSourceManager::filterData(QList &series) +{ + auto registry = PluginRegistry::getInstance(); + // TODO : Ensure correct plugin is selected + + // TODO : Handle selection of *other* data series (if required) + auto plugins = registry->FilterPlugins(); + + if (plugins.count() == 0) + { + return false; + } + + // TODO: Hack for now + auto plugin = plugins.first(); + + QStringList errors; + + if (!plugin->setFilterInputs(series, errors)) + { + // TODO: Error messages? + + return false; + } + + if (!plugin->beforeProcessStep()) + { + return false; + } + + bool result = runDataProcess( + plugin, + tr("Filtering Data"), + tr("Applying custom data filter") + ); + + if (result) + { + DataSourcePointer src = getSourceByLabel("internal"); + + if (src.isNull()) + { + src = DataSourcePointer(new DataSource("internal", "internal")); + addSource(src); + } + + for (auto output : plugin->getFilterOutputs()) + { + src->addSeries(output); + } + } + + return result; +} + + +bool DataSourceManager::runDataProcess(QSharedPointer plugin, QString title, QString message) +{ QProgressDialog progress; - progress.setWindowTitle(tr("Importing Data")); + progress.setWindowTitle(title); + progress.setLabelText(message); + + progress.setValue(0); progress.setMinimum(0); progress.setMaximum(100); - progress.setValue(0); - progress.setLabelText(tr("Importing data from file")); progress.show(); QApplication::processEvents(); - // Spawn a new thread for data export - auto worker = DataExportWorker(exporter, series); + // Spawn a new thread for data processing + auto worker = DataProcessWorker(plugin); auto *thread = new QThread(); worker.moveToThread(thread); - connect(&worker, &DataExportWorker::exportCompleted, thread, &QThread::quit); - connect(thread, &QThread::started, &worker, &DataExportWorker::runExport); + connect(&worker, &DataProcessWorker::processingComplete, thread, &QThread::quit); + connect(thread, &QThread::started, &worker, &DataProcessWorker::run); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); - qDebug() << "Exporting data to" << filename; - while (!thread->isFinished() && !worker.isComplete()) { if (progress.wasCanceled()) { - worker.cancelExport(); - thread->wait(); + worker.cancel(); } - progress.setValue(exporter->getExportProgress()); + progress.setValue(plugin->getProgress()); QApplication::processEvents(); QThread::msleep(100); @@ -545,13 +526,5 @@ bool DataSourceManager::exportData(QList &series, QString fil progress.cancel(); progress.close(); - for (QString err : worker.getErrors()) - { - // TODO: Display these better? - qWarning() << "Export err:" << err; - } - - bool result = worker.getResult(); - - return result; + return worker.getResult(); } diff --git a/src/data_source_manager.hpp b/src/data_source_manager.hpp index 7cb8ec0..05a9bb1 100644 --- a/src/data_source_manager.hpp +++ b/src/data_source_manager.hpp @@ -3,66 +3,36 @@ #include +#include "plugin_base.hpp" #include "data_source.hpp" -#include "plugin_importer.hpp" -#include "plugin_exporter.hpp" - - -class DataIOWorker : public QObject -{ -public: - bool getResult(void) const { return m_result; } - QStringList getErrors(void) const { return m_errors; } - bool isComplete(void) const { return m_complete; } -protected: - QStringList m_errors; - bool m_complete = false; - bool m_result = false; -}; - /** - * @brief The DataImportWorker class manages a data import session + * @brief The DataProcessWorker class is a simple worker class for managing data processing */ -class DataImportWorker : public DataIOWorker +class DataProcessWorker : public QObject { Q_OBJECT - public: - DataImportWorker(QSharedPointer plugin); - -public slots: - void runImport(void); - void cancelImport(void); - -signals: - void importCompleted(void); + DataProcessWorker(QSharedPointer plugin); -protected: - QSharedPointer m_plugin; -}; - - -class DataExportWorker : public DataIOWorker -{ - Q_OBJECT - -public: - DataExportWorker(QSharedPointer plugin, QList &series); + bool getResult(void) const { return m_result; } + bool isComplete(void) const { return m_complete; } public slots: - void runExport(void); - void cancelExport(void); + void run(); + void cancel(); signals: - void exportCompleted(void); + void processingComplete(); protected: - QSharedPointer m_plugin; - QList m_series; + QSharedPointer m_plugin; + bool m_complete = false; + bool m_result = false; }; + /* * Data source manager class: * - Manages all data sources @@ -125,6 +95,9 @@ public slots: // Data export functionality bool exportData(QList &series, QString filename = QString()); + // Data filtering functionality + bool filterData(QList &series); + void update(void) { emit sourcesChanged(); } signals: @@ -135,6 +108,8 @@ protected slots: protected: QVector sources; + + bool runDataProcess(QSharedPointer plugin, QString title, QString message); }; diff --git a/src/lumberjack_settings.cpp b/src/lumberjack_settings.cpp index 99d258d..ef56438 100644 --- a/src/lumberjack_settings.cpp +++ b/src/lumberjack_settings.cpp @@ -80,6 +80,12 @@ QVariant LumberjackSettings::loadSetting(QString group, QString key, QVariant de } +QString LumberjackSettings::loadString(QString group, QString key, QString defaultValue) +{ + return loadSetting(group, key, defaultValue).toString(); +} + + bool LumberjackSettings::loadBoolean(QString group, QString key, bool defaultValue) { QVariant result = loadSetting(group, key, defaultValue); diff --git a/src/lumberjack_settings.hpp b/src/lumberjack_settings.hpp index 010b377..574695d 100644 --- a/src/lumberjack_settings.hpp +++ b/src/lumberjack_settings.hpp @@ -37,6 +37,7 @@ class LumberjackSettings } QVariant loadSetting(QString group, QString key, QVariant defaultValue); + QString loadString(QString group, QString key, QString defaultValue = QString()); bool loadBoolean(QString group, QString key, bool defaultValue = false); void saveSetting(QString group, QString key, QVariant value); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4d2a652..445c5d0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -54,7 +54,11 @@ MainWindow::MainWindow(QWidget *parent) loadWorkspaceSettings(); // Load plugins - PluginRegistry::getInstance()->loadPlugins(); + auto registry = PluginRegistry::getInstance(); + + connect(registry, &PluginRegistry::pluginsLoaded, &filtersView, &FiltersWidget::loadPlugins); + + registry->loadPlugins(); } @@ -169,6 +173,7 @@ void MainWindow::saveWorkspaceSettings() auto *settings = LumberjackSettings::getInstance(); settings->saveSetting("mainwindow", "showDataView", dataView.isVisible()); + settings->saveSetting("mainwindow", "showFiltersView", filtersView.isVisible()); settings->saveSetting("mainwindow", "showTimelineView", timelineView.isVisible()); settings->saveSetting("mainwindow", "showStatsView", statsView.isVisible()); settings->saveSetting("mainwindow", "showDebugView", debugWidget.isVisible()); @@ -190,6 +195,7 @@ void MainWindow::initMenus() // View menu connect(ui->action_Data_View, &QAction::triggered, this, &MainWindow::toggleDataView); + connect(ui->action_Filters, &QAction::triggered, this, &MainWindow::toggleFiltersView); connect(ui->action_Timeline, &QAction::triggered, this, &MainWindow::toggleTimelineView); connect(ui->action_Statistics, &QAction::triggered, this, &MainWindow::toggleStatisticsView); connect(ui->action_FFT, &QAction::triggered, this, &MainWindow::toggleFftView); @@ -223,6 +229,11 @@ void MainWindow::initDocks() toggleDataView(); } + if (settings->loadBoolean("mainwindow", "showFiltersView")) + { + toggleFiltersView(); + } + if (settings->loadBoolean("mainwindow", "showTimelineView")) { toggleTimelineView(); @@ -500,6 +511,31 @@ void MainWindow::toggleFftView(void) } } +/** + * @brief MainWindow::toggleFiltersView toggles visibility of the "filters view" dock + */ +void MainWindow::toggleFiltersView(void) +{ + ui->action_Filters->setCheckable(true); + + if (filtersView.isVisible()) + { + hideDockedWidget(&filtersView); + ui->action_Filters->setChecked(false); + } + else + { + QDockWidget* dock = new QDockWidget(tr("Filters View"), this); + dock->setObjectName("filters-view"); + dock->setAllowedAreas(Qt::AllDockWidgetAreas); + dock->setWidget(&filtersView); + + addDockWidget(Qt::LeftDockWidgetArea, dock); + + ui->action_Filters->setChecked(true); + } +} + /** * @brief MainWindow::toggleDataView toggles visibility of the "data view" dock diff --git a/src/mainwindow.h b/src/mainwindow.h index 40a7abb..e83b9fe 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -11,6 +11,7 @@ #include "fft_widget.hpp" #include "stats_widget.hpp" #include "dataview_widget.hpp" +#include "filters_widget.hpp" #include "timeline_widget.hpp" QT_BEGIN_NAMESPACE @@ -53,6 +54,7 @@ protected slots: void toggleDebugView(void); void toggleDataView(void); + void toggleFiltersView(void); void toggleFftView(void); void toggleTimelineView(void); void toggleStatisticsView(void); @@ -77,6 +79,7 @@ protected slots: StatsWidget statsView; TimelineWidget timelineView; FFTWidget fftView; + FiltersWidget filtersView; DebugWidget debugWidget; }; diff --git a/src/plot_legend.cpp b/src/plot_legend.cpp index 8d34da4..b3de2cb 100644 --- a/src/plot_legend.cpp +++ b/src/plot_legend.cpp @@ -131,8 +131,8 @@ bool PlotLegend::handleMousePressEvent(const QMouseEvent *event) // Ignore if the legend is not displayed if (rect.width() < 0 || rect.height() < 0) return false; - int x = event->x(); - int y = event->y(); + int x = (int) event->position().x(); + int y = (int) event->position().y(); // Return early if click is outside legend bounds if (x < rect.left() || x > rect.right()) return false; diff --git a/src/plot_widget.cpp b/src/plot_widget.cpp index d44ea76..c23571e 100644 --- a/src/plot_widget.cpp +++ b/src/plot_widget.cpp @@ -58,7 +58,7 @@ PlotWidget::PlotWidget() : QwtPlot() auto *settings = LumberjackSettings::getInstance(); - QString bgColor = settings->loadSetting("graph", "defaultBackgroundColor", "#F0F0F0").toString(); + QString bgColor = settings->loadString("graph", "defaultBackgroundColor", "#F0F0F0"); if (QColor::isValidColorName(bgColor)) { diff --git a/src/plugins/plugin_base.hpp b/src/plugins/plugin_base.hpp index 1a98859..5535b58 100644 --- a/src/plugins/plugin_base.hpp +++ b/src/plugins/plugin_base.hpp @@ -28,4 +28,32 @@ class PluginBase : public QObject typedef QList> PluginList; + +// Base plugin class which provides data processing functionality +class DataProcessingPlugin : public PluginBase +{ + Q_OBJECT + +public: + virtual ~DataProcessingPlugin() = default; + + // Optional function which is called before running data processing step + // Return False to cancel the process before it begins + virtual bool beforeProcessStep(void) { return true; } + + // Run the actual data processing + virtual bool processData(void) = 0; + + // Optional function which is called after running the data processing step + virtual void afterProcessStep(void) {} + + // Function to cancel the process step + // MUST be implemented by the particular plugin class + virtual void cancelProcessing(void) = 0; + + // Return the progress of the data processing stage (as a percentage {0:100}) + virtual uint8_t getProgress(void) const = 0; + +}; + #endif // PLUGIN_BASE_HPP diff --git a/src/plugins/plugin_exporter.hpp b/src/plugins/plugin_exporter.hpp index e27028b..8f51230 100644 --- a/src/plugins/plugin_exporter.hpp +++ b/src/plugins/plugin_exporter.hpp @@ -11,7 +11,7 @@ /** * @brief The ExportPlugin class defines an interface for exporting data */ -class ExportPlugin : public PluginBase +class ExportPlugin : public DataProcessingPlugin { Q_OBJECT public: @@ -20,22 +20,6 @@ class ExportPlugin : public PluginBase // Return a list of the support file types e.g. ['csv', 'tsv'] virtual QStringList supportedFileTypes(void) const = 0; - // Optional function called before data export - // Return False to cancel the data export process - virtual bool beforeExport(void) { return true; } - - // Export data to the provided filename - virtual bool exportData(QList &series, QStringList &errors) = 0; - - // Optional function called after data export - virtual void afterExport(void) {} - - // Cancel import process - plugin is expected to perform cleanup - virtual void cancelExport(void) = 0; - - // Return the progress of the data export process (as a percentage {0:100}) - virtual uint8_t getExportProgress(void) const = 0; - virtual QString pluginIID(void) const override { return QString(ExporterInterface_iid); @@ -45,12 +29,15 @@ class ExportPlugin : public PluginBase bool supportsFileType(QString fileType) const; - void setFilename(QString filename) { m_filename = filename; } + virtual void setFilename(QString filename) { m_filename = filename; } + virtual void setDataSeries(QList seriesList) { m_series = seriesList; } QString getFilename(void) const { return m_filename; } protected: // Stored filename, destination of exported data QString m_filename; + + QList m_series; }; typedef QList> ExportPluginList; diff --git a/src/plugins/plugin_filter.cpp b/src/plugins/plugin_filter.cpp new file mode 100644 index 0000000..ecf70ef --- /dev/null +++ b/src/plugins/plugin_filter.cpp @@ -0,0 +1,26 @@ +#include "plugin_filter.hpp" + + +/** + * @brief FilterPlugin::setFilterInputs - Default implementation to set filter inputs + * @param inputs + * @param errors + * @return + */ +bool FilterPlugin::setFilterInputs(QList inputs, QStringList &errors) +{ + if (inputs.count() < getMinInputCount()) + { + errors.append(tr("Not enough inputs provided")); + return false; + } + + if (inputs.count() > getMaxInputCount()) + { + errors.append(tr("Too many inputs provided")); + return false; + } + + m_inputs = inputs; + return true; +} diff --git a/src/plugins/plugin_filter.hpp b/src/plugins/plugin_filter.hpp index 0db63bb..0b691fe 100644 --- a/src/plugins/plugin_filter.hpp +++ b/src/plugins/plugin_filter.hpp @@ -3,6 +3,7 @@ #include +#include "data_series.hpp" #include "plugin_base.hpp" #define FilterInterface_iid "org.lumberjack.plugins.FilterPlugin/1.0" @@ -12,7 +13,7 @@ /** * @brief The FilterPlugin class defines an interface for applying custom data filters */ -class FilterPlugin : public PluginBase +class FilterPlugin : public DataProcessingPlugin { Q_OBJECT public: @@ -24,8 +25,20 @@ class FilterPlugin : public PluginBase return QString(FilterInterface_iid); } + // Return the minimum number of inputs supported by this plugin + virtual unsigned int getMinInputCount(void) const = 0; - // TODO + // Return the maximum number of inputs supported by this plugin + virtual unsigned int getMaxInputCount(void) const = 0; + + // Set the filter inputs + virtual bool setFilterInputs(QList inputs, QStringList &errors); + + // Return the generated data series output(s) + virtual QList getFilterOutputs(void) = 0; + +protected: + QList m_inputs; }; typedef QList> FilterPluginList; diff --git a/src/plugins/plugin_importer.hpp b/src/plugins/plugin_importer.hpp index 1d1f12a..ead9bbb 100644 --- a/src/plugins/plugin_importer.hpp +++ b/src/plugins/plugin_importer.hpp @@ -13,7 +13,7 @@ /** * @brief The ImportPlugin class defines an interface for importing data */ -class ImportPlugin : public PluginBase +class ImportPlugin : public DataProcessingPlugin { Q_OBJECT public: @@ -27,22 +27,6 @@ class ImportPlugin : public PluginBase // but this can be extended by the plugin virtual bool validateFile(QString filename, QStringList &errors) const; - // Optional function called before data import - // Return False to cancel the data import process - virtual bool beforeImport() { return true; } - - // Load data from the provided filename - virtual bool importData(QStringList &errors) = 0; - - // Optional function called after data import - virtual void afterImport(void) {} - - // Cancel import process - plugin is expected to perform cleanup - virtual void cancelImport(void) = 0; - - // Return the progress of the data import process (as a percentage {0:100}) - virtual uint8_t getImportProgress(void) const = 0; - // After import, plugin must return a list of DataSeries objects virtual QList getDataSeries(void) const = 0; diff --git a/src/plugins/plugin_registry.cpp b/src/plugins/plugin_registry.cpp index 16e18a0..29bf618 100644 --- a/src/plugins/plugin_registry.cpp +++ b/src/plugins/plugin_registry.cpp @@ -61,6 +61,8 @@ void PluginRegistry::loadPlugins() } } } + + emit pluginsLoaded(); } @@ -140,7 +142,7 @@ QString PluginRegistry::getFilenameForImport(void) const dialog.setWindowTitle(tr("Import Data from File")); - QString lastDir = settings->loadSetting("import", "lastDirectory", QString()).toString(); + QString lastDir = settings->loadString("import", "lastDirectory"); if (!lastDir.isEmpty()) { @@ -194,7 +196,7 @@ QString PluginRegistry::getFilenameForExport(void) const dialog.setWindowTitle(tr("Export Data to File")); - QString lastDir = settings->loadSetting("export", "lastDirectory", QString()).toString(); + QString lastDir = settings->loadString("export", "lastDirectory"); if (!lastDir.isEmpty()) { diff --git a/src/plugins/plugin_registry.hpp b/src/plugins/plugin_registry.hpp index 97803f3..28aa708 100644 --- a/src/plugins/plugin_registry.hpp +++ b/src/plugins/plugin_registry.hpp @@ -37,8 +37,6 @@ class PluginRegistry : public QObject } } - void loadPlugins(void); - void clearRegistry(void); const ImportPluginList& ImportPlugins(void) { return m_ImportPlugins; } const ExportPluginList& ExportPlugins(void) { return m_ExportPlugins; } @@ -47,6 +45,13 @@ class PluginRegistry : public QObject QString getFilenameForImport(void) const; QString getFilenameForExport(void) const; +public slots: + void loadPlugins(void); + void clearRegistry(void); + +signals: + void pluginsLoaded(); + protected: bool loadImportPlugin(QObject *instance); diff --git a/src/widgets/dataview_tree.cpp b/src/widgets/dataview_tree.cpp index 7f2890f..7cd535e 100644 --- a/src/widgets/dataview_tree.cpp +++ b/src/widgets/dataview_tree.cpp @@ -4,6 +4,7 @@ #include #include +#include "plugin_registry.hpp" #include "datatable_widget.hpp" #include "series_editor_dialog.hpp" #include "dataview_tree.hpp" @@ -129,6 +130,8 @@ void DataViewTree::onContextMenu(const QPoint &pos) // Edit series QAction *editSeries = new QAction(tr("Edit Series"), &menu); + QAction *filterSeries = new QAction(tr("Apply Filter"), &menu); + // View data QAction *viewSeriesData = new QAction(tr("View Data"), &menu); @@ -138,6 +141,7 @@ void DataViewTree::onContextMenu(const QPoint &pos) menu.addAction(exportSeries); menu.addSeparator(); menu.addAction(editSeries); + menu.addAction(filterSeries); menu.addAction(viewSeriesData); menu.addSeparator(); menu.addAction(deleteSeries); @@ -155,6 +159,14 @@ void DataViewTree::onContextMenu(const QPoint &pos) { editDataSeries(series); } + else if (action == filterSeries) + { + QList seriesList; + seriesList << series; + + auto manager = DataSourceManager::getInstance(); + manager->filterData(seriesList); + } else if (action == viewSeriesData) { DataSeriesTableView *table = new DataSeriesTableView(series); diff --git a/src/widgets/dataview_widget.hpp b/src/widgets/dataview_widget.hpp index ffc91e6..e1dcea9 100644 --- a/src/widgets/dataview_widget.hpp +++ b/src/widgets/dataview_widget.hpp @@ -3,8 +3,6 @@ #include -#include "data_source.hpp" - #include "ui_dataview_widget.h" class DataviewWidget : public QWidget @@ -30,9 +28,6 @@ public slots: virtual void dropEvent(QDropEvent *event) override; virtual void dragEnterEvent(QDragEnterEvent *event) override; - -// virtual void dragMoveEvent(QDragMoveEvent *event) override; -// virtual void dragLeaveEvent(QDragLeaveEvent *event) override; }; diff --git a/src/widgets/filters_widget.cpp b/src/widgets/filters_widget.cpp new file mode 100644 index 0000000..b25bae1 --- /dev/null +++ b/src/widgets/filters_widget.cpp @@ -0,0 +1,159 @@ +#include + +#include "filters_widget.hpp" + +#include "plugin_registry.hpp" +#include "lumberjack_settings.hpp" +#include "data_source_manager.hpp" + + +FiltersWidget::FiltersWidget(QWidget *parent) : QWidget(parent) +{ + ui.setupUi(this); + setAcceptDrops(true); + + refresh(); + + connect(ui.applyFilterButton, &QPushButton::released, this, &FiltersWidget::applyFilter); + connect(ui.clearItemsButton, &QPushButton::released, this, &FiltersWidget::clearItems); +} + + +void FiltersWidget::loadPlugins() +{ + auto registry = PluginRegistry::getInstance(); + auto settings = LumberjackSettings::getInstance(); + + ui.filterSelect->clear(); + + QString selectedPlugin = settings->loadString("filters", "selectedFilter"); + + for (auto plugin : registry->FilterPlugins()) + { + ui.filterSelect->addItem(plugin->pluginName()); + } + + + for (int idx = 0; idx < ui.filterSelect->count(); idx++) + { + if (ui.filterSelect->itemText(idx) == selectedPlugin) + { + ui.filterSelect->setCurrentIndex(idx); + break; + } + } + + refresh(); +} + + +void FiltersWidget::refresh() +{ + bool hasItems = !seriesList.isEmpty(); + + ui.applyFilterButton->setEnabled(hasItems); + ui.clearItemsButton->setEnabled(hasItems); + + updateSeriesList(); +} + + +void FiltersWidget::updateSeriesList(void) +{ + auto *list = ui.seriesList; + + list->clear(); + + list->setSelectionMode(QAbstractItemView::ExtendedSelection); + + for (auto series : seriesList) + { + if (series.isNull()) continue; + + QListWidgetItem *item = new QListWidgetItem(series->getLabel(), list); + list->addItem(item); + } +} + + +void FiltersWidget::clearItems() +{ + seriesList.clear(); + refresh(); + // TODO - other stuff too? +} + + +void FiltersWidget::applyFilter() +{ + // TODO: Apply selected filter + refresh(); +} + + +void FiltersWidget::addSeries(DataSeriesPointer series) +{ + seriesList.append(series); + refresh(); +} + + +/** + * @brief FiltersWidget::dragEnterEvent is called when a drag event enters the widget + * @param event + */ +void FiltersWidget::dragEnterEvent(QDragEnterEvent *event) +{ + auto *mime = event->mimeData(); + + // DataSeries is being dragged onto this PlotWidget + if (mime->hasFormat("source") && mime->hasFormat("series")) + { + event->acceptProposedAction(); + return; + } +} + + +/** + * @brief FiltersWidget::dragMoveEvent is called when an accepted drag event moves across the widget + * @param event + */ +void FiltersWidget::dragMoveEvent(QDragMoveEvent *event) +{ + Q_UNUSED(event) +} + + +/** + * @brief FiltersWidget::dropEvent is called when an accepted drag event is dropped on the widget + * @param event + */ +void FiltersWidget::dropEvent(QDropEvent *event) +{ + auto *mime = event->mimeData(); + auto *manager = DataSourceManager::getInstance(); + + if (!mime || !manager) + { + return; + } + + // // DataSeries is dropped onto this PlotWidget + if (mime->hasFormat("source") && mime->hasFormat("series")) + { + QString source_lbl = mime->data("source"); + QString series_lbl = mime->data("series"); + + auto series = manager->findSeries(source_lbl, series_lbl); + + if (series.isNull()) + { + qCritical() << "Could not find series matching" << source_lbl << ":" << series_lbl; + return; + } + + event->accept(); + addSeries(series); + } +} diff --git a/src/widgets/filters_widget.hpp b/src/widgets/filters_widget.hpp new file mode 100644 index 0000000..6f183bd --- /dev/null +++ b/src/widgets/filters_widget.hpp @@ -0,0 +1,44 @@ +#ifndef FILTERS_WIDGET_H +#define FILTERS_WIDGET_H + +#include +#include +#include +#include + +#include "data_series.hpp" + +#include "ui_filters_widget.h" + + +class FiltersWidget : public QWidget +{ + Q_OBJECT + +public: + FiltersWidget(QWidget *parent = nullptr); + +public slots: + void addSeries(DataSeriesPointer series); + void loadPlugins(void); + + void clearItems(void); + void applyFilter(void); + +signals: + +protected: + Ui::filtersview ui; + + QList seriesList; + + // Drag-n-drop actions + virtual void dragEnterEvent(QDragEnterEvent *event) override; + virtual void dragMoveEvent(QDragMoveEvent *event) override; + virtual void dropEvent(QDropEvent *event) override; + + void refresh(void); + void updateSeriesList(void); +}; + +#endif // FILTERS_WIDGET_H diff --git a/src/widgets/plugins_dialog.cpp b/src/widgets/plugins_dialog.cpp index 8a09dc4..5a2b20a 100644 --- a/src/widgets/plugins_dialog.cpp +++ b/src/widgets/plugins_dialog.cpp @@ -8,6 +8,8 @@ PluginsDialog::PluginsDialog(QWidget *parent) : QDialog(parent) initPluginsTable(); + ui.iid->clear(); + // Add in each type of plugin interface ui.plugin_select->addItem(tr("Data Import")); ui.plugin_select->addItem(tr("Data Export")); @@ -17,7 +19,15 @@ PluginsDialog::PluginsDialog(QWidget *parent) : QDialog(parent) connect(ui.closeButton, &QPushButton::released, this, &PluginsDialog::close); // Display plugin locations - QStringList paths = QCoreApplication::libraryPaths(); + QStringList paths; + + for (QString path : QCoreApplication::libraryPaths()) + { + if (!paths.contains(path)) + { + paths.append(path); + } + } ui.library_paths->setText( tr("Library Paths") + ":\n- " + paths.join("\n- ") @@ -72,30 +82,45 @@ void PluginsDialog::selectPluginType(int idx) PluginList plugins; + QString iid; + switch (idx) { case 1: // Importer plugins + iid = QString(ImporterInterface_iid); for (auto plugin : registry->ImportPlugins()) { plugins.append(plugin); } break; case 2: // Exporter plugins + iid = QString(ExporterInterface_iid); for (auto plugin : registry->ExportPlugins()) { plugins.append(plugin); } break; case 3: // Filter plugins + iid = QString(FilterInterface_iid); for (auto plugin : registry->FilterPlugins()) { plugins.append(plugin); } break; default: + iid = QString(); break; } loadPluginsTable(plugins); + + if (iid.isEmpty()) + { + ui.iid->clear(); + } + else + { + ui.iid->setText(tr("Plugin Version") + ": " + iid); + } } diff --git a/ui/dataview_widget.ui b/ui/dataview_widget.ui index c121d8a..468df69 100644 --- a/ui/dataview_widget.ui +++ b/ui/dataview_widget.ui @@ -10,6 +10,12 @@ 380 + + + 254 + 0 + + Form diff --git a/ui/filters_widget.ui b/ui/filters_widget.ui new file mode 100644 index 0000000..d956e87 --- /dev/null +++ b/ui/filters_widget.ui @@ -0,0 +1,131 @@ + + + filtersview + + + + 0 + 0 + 411 + 462 + + + + + 0 + 222 + + + + Form + + + + + + + + + 100 + 0 + + + + Select Filter + + + + + + + + 1 + 0 + + + + + 150 + 0 + + + + false + + + + + + + + + + + Clear Items + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Apply Filter + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Qt::Orientation::Horizontal + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + + Selected Data Series + + + + + + + + diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 74517cd..c7c7b08 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -26,7 +26,7 @@ 0 0 600 - 22 + 21 @@ -54,6 +54,7 @@ + @@ -123,6 +124,11 @@ &Plugins + + + F&ilters + + diff --git a/ui/plugins_dialog.ui b/ui/plugins_dialog.ui index ef86035..226ec58 100644 --- a/ui/plugins_dialog.ui +++ b/ui/plugins_dialog.ui @@ -37,7 +37,7 @@ - + <IID String>