Commit 57f55429a9dd072757ffc87d223cb0c0cbec6362

Authored by David Graeff
Committed by David Gräff
1 parent 832fe655

Exporting refactored: Plugin like structure with a central registry and GUI independence

openhantek/src/exporting/exportcsv.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#include "exportcsv.h"
  4 +#include "exporterregistry.h"
  5 +#include "post/ppresult.h"
  6 +#include "settings.h"
  7 +#include "iconfont/QtAwesome.h"
  8 +
  9 +#include <QCoreApplication>
  10 +#include <QTextStream>
  11 +#include <QFile>
  12 +#include <QFileDialog>
  13 +
  14 +ExporterCSV::ExporterCSV() {}
  15 +
  16 +void ExporterCSV::create(ExporterRegistry *registry) { this->registry = registry; data.reset(); }
  17 +
  18 +QIcon ExporterCSV::icon() { return iconFont->icon(fa::filetexto); }
  19 +
  20 +QString ExporterCSV::name() { return QCoreApplication::tr("Export CSV"); }
  21 +
  22 +ExporterInterface::Type ExporterCSV::type() { return Type::SnapshotExport; }
  23 +
  24 +bool ExporterCSV::samples(const std::shared_ptr<PPresult> data) {
  25 + this->data = std::move(data);
  26 + return false;
  27 +}
  28 +
  29 +bool ExporterCSV::save() {
  30 + QStringList filters;
  31 + filters << QCoreApplication::tr("Comma-Separated Values (*.csv)");
  32 +
  33 + QFileDialog fileDialog(nullptr, QCoreApplication::tr("Export file..."), QString(), filters.join(";;"));
  34 + fileDialog.setFileMode(QFileDialog::AnyFile);
  35 + fileDialog.setAcceptMode(QFileDialog::AcceptSave);
  36 + if (fileDialog.exec() != QDialog::Accepted) return false;
  37 +
  38 + QFile csvFile(fileDialog.selectedFiles().first());
  39 + if (!csvFile.open(QIODevice::WriteOnly | QIODevice::Text)) return false;
  40 +
  41 + QTextStream csvStream(&csvFile);
  42 + csvStream.setRealNumberNotation(QTextStream::FixedNotation);
  43 + csvStream.setRealNumberPrecision(10);
  44 +
  45 + size_t chCount = registry->settings->scope.voltage.size();
  46 + std::vector<const SampleValues *> voltageData(size_t(chCount), nullptr);
  47 + std::vector<const SampleValues *> spectrumData(size_t(chCount), nullptr);
  48 + size_t maxRow = 0;
  49 + bool isSpectrumUsed = false;
  50 + double timeInterval = 0;
  51 + double freqInterval = 0;
  52 +
  53 + for (ChannelID channel = 0; channel < chCount; ++channel) {
  54 + if (data->data(channel)) {
  55 + if (registry->settings->scope.voltage[channel].used) {
  56 + voltageData[channel] = &(data->data(channel)->voltage);
  57 + maxRow = std::max(maxRow, voltageData[channel]->sample.size());
  58 + timeInterval = data->data(channel)->voltage.interval;
  59 + }
  60 + if (registry->settings->scope.spectrum[channel].used) {
  61 + spectrumData[channel] = &(data->data(channel)->spectrum);
  62 + maxRow = std::max(maxRow, spectrumData[channel]->sample.size());
  63 + freqInterval = data->data(channel)->spectrum.interval;
  64 + isSpectrumUsed = true;
  65 + }
  66 + }
  67 + }
  68 +
  69 + // Start with channel names
  70 + csvStream << "\"t\"";
  71 + for (ChannelID channel = 0; channel < chCount; ++channel) {
  72 + if (voltageData[channel] != nullptr) { csvStream << ",\"" << registry->settings->scope.voltage[channel].name << "\""; }
  73 + }
  74 + if (isSpectrumUsed) {
  75 + csvStream << ",\"f\"";
  76 + for (ChannelID channel = 0; channel < chCount; ++channel) {
  77 + if (spectrumData[channel] != nullptr) {
  78 + csvStream << ",\"" << registry->settings->scope.spectrum[channel].name << "\"";
  79 + }
  80 + }
  81 + }
  82 + csvStream << "\n";
  83 +
  84 + for (unsigned int row = 0; row < maxRow; ++row) {
  85 +
  86 + csvStream << timeInterval * row;
  87 + for (ChannelID channel = 0; channel < chCount; ++channel) {
  88 + if (voltageData[channel] != nullptr) {
  89 + csvStream << ",";
  90 + if (row < voltageData[channel]->sample.size()) { csvStream << voltageData[channel]->sample[row]; }
  91 + }
  92 + }
  93 +
  94 + if (isSpectrumUsed) {
  95 + csvStream << "," << freqInterval * row;
  96 + for (ChannelID channel = 0; channel < chCount; ++channel) {
  97 + if (spectrumData[channel] != nullptr) {
  98 + csvStream << ",";
  99 + if (row < spectrumData[channel]->sample.size()) { csvStream << spectrumData[channel]->sample[row]; }
  100 + }
  101 + }
  102 + }
  103 + csvStream << "\n";
  104 + }
  105 +
  106 + csvFile.close();
  107 +
  108 + return true;
  109 +}
  110 +
  111 +float ExporterCSV::progress() { return data ? 1.0f : 0; }
... ...
openhantek/src/exporting/exportcsv.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +#include "exporterinterface.h"
  5 +
  6 +class ExporterCSV : public ExporterInterface
  7 +{
  8 +public:
  9 + ExporterCSV();
  10 + virtual void create(ExporterRegistry *registry) override;
  11 + virtual QIcon icon() override;
  12 + virtual QString name() override;
  13 + virtual Type type() override;
  14 + virtual bool samples(const std::shared_ptr<PPresult>data) override;
  15 + virtual bool save() override;
  16 + virtual float progress() override;
  17 +private:
  18 + std::shared_ptr<PPresult> data;
  19 +};
... ...
openhantek/src/exporting/exporter.h deleted
1   -// SPDX-License-Identifier: GPL-2.0+
2   -
3   -#pragma once
4   -
5   -#include <QPainter>
6   -#include <QPrinter>
7   -#include <QSize>
8   -#include <memory>
9   -#include "exportsettings.h"
10   -
11   -class DsoSettings;
12   -class PPresult;
13   -struct DsoSettingsColorValues;
14   -namespace Dso { struct ControlSpecification; }
15   -
16   -/// \brief Exports the oscilloscope screen to a file or prints it.
17   -class Exporter {
18   - public:
19   - static Exporter *createPrintExporter(const Dso::ControlSpecification* deviceSpecification, DsoSettings *settings);
20   - static Exporter *createSaveToFileExporter(const Dso::ControlSpecification* deviceSpecification, DsoSettings *settings);
21   -
22   - /// \brief Print the document (May be a file too)
23   - bool exportSamples(const PPresult *result);
24   -
25   - private:
26   - Exporter(const Dso::ControlSpecification* deviceSpecification, DsoSettings *settings, const QString &filename, ExportFormat format);
27   - void setFormat(ExportFormat format);
28   - bool exportCSV(const PPresult *result);
29   - static std::unique_ptr<QPrinter> printPaintDevice(DsoSettings *settings);
30   - void drawGrids(QPainter &painter, DsoSettingsColorValues *colorValues, double lineHeight, double scopeHeight,
31   - int scopeWidth);
32   - const Dso::ControlSpecification* deviceSpecification;
33   - DsoSettings *settings;
34   - std::unique_ptr<QPrinter> selectedPrinter;
35   -
36   - QString filename;
37   - ExportFormat format;
38   -};
openhantek/src/exporting/exporterinterface.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +
  5 +#include <QIcon>
  6 +#include <QString>
  7 +
  8 +#include <memory>
  9 +
  10 +class ExporterRegistry;
  11 +class PPresult;
  12 +
  13 +/**
  14 + * Implement this interface and register your Exporter to the ExporterRegistry instance
  15 + * in the main routine to make an Exporter available.
  16 + */
  17 +class ExporterInterface {
  18 +public:
  19 +
  20 + /**
  21 + * Starts up this exporter. Aquires resources etc. Do not call this directly, it
  22 + * will be called by the exporter registry at some point. Release your resources in the
  23 + * destructor as usual.
  24 + * @param registry The exporter registry instance. This is used to obtain a reference
  25 + * to the settings.
  26 + */
  27 + virtual void create(ExporterRegistry *registry) = 0;
  28 +
  29 + /**
  30 + * @return Return the icon representation of this exporter. Will be used in graphical
  31 + * interfaces.
  32 + */
  33 + virtual QIcon icon() = 0;
  34 +
  35 + /**
  36 + * @return Return the text representation / name of this exporter. Will be used in graphical
  37 + * interfaces.
  38 + */
  39 + virtual QString name() = 0;
  40 +
  41 + /**
  42 + * Exporters can save only a single sample set or save data continously.
  43 + */
  44 + enum class Type { SnapshotExport, ContinousExport };
  45 +
  46 + /**
  47 + * @return Return the type of this exporter.
  48 + * @see ExporterInterface::Type
  49 + */
  50 + virtual Type type() = 0;
  51 +
  52 + /**
  53 + * A new sample set from the ExporterRegistry. The exporter needs to be active to receive samples.
  54 + * If it is a snapshot exporter, only one set of samples will be received.
  55 + * @return Return true if you want to receive another sample or false if you are done (progres()==1).
  56 + */
  57 + virtual bool samples(const std::shared_ptr<PPresult>) = 0;
  58 +
  59 + /**
  60 + * Exporter: Save your received data and perform any conversions necessary.
  61 + * This method will be called in the
  62 + * GUI thread context and can create and show dialogs if required.
  63 + * @return Return true if saving succedded otherwise false.
  64 + */
  65 + virtual bool save() = 0;
  66 +
  67 + /**
  68 + * @brief The progress of receiving and processing samples. If the exporter returns 1, it will
  69 + * be called back by the GUI via the save() method.
  70 + *
  71 + * @return A number between 0..1 indicating the used capacity of this exporter. If this is a
  72 + * snapshot exporter, only 0 for "no samples processed yet" or 1 for "finished" will be returned.
  73 + * A continous exporter may report the used memory / reservered memory ratio here.
  74 + */
  75 + virtual float progress() = 0;
  76 +protected:
  77 + ExporterRegistry *registry;
  78 +};
... ...
openhantek/src/exporting/exporterprocessor.cpp 0 → 100644
  1 +#include "exporterprocessor.h"
  2 +#include "exporterregistry.h"
  3 +
  4 +ExporterProcessor::ExporterProcessor(ExporterRegistry *registry) : registry(registry) {}
  5 +
  6 +void ExporterProcessor::process(PPresult *data) { registry->addRawSamples(data); }
... ...
openhantek/src/exporting/exporterprocessor.h 0 → 100644
  1 +#pragma once
  2 +
  3 +#include "post/processor.h"
  4 +
  5 +class ExporterRegistry;
  6 +
  7 +class ExporterProcessor : public Processor
  8 +{
  9 +public:
  10 + ExporterProcessor(ExporterRegistry* registry);
  11 + virtual void process(PPresult *) override;
  12 +private:
  13 + ExporterRegistry* registry;
  14 +};
... ...
openhantek/src/exporting/exporterregistry.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#include "exporterregistry.h"
  4 +#include "exporterinterface.h"
  5 +
  6 +#include <algorithm>
  7 +
  8 +#include "controlspecification.h"
  9 +#include "post/ppresult.h"
  10 +#include "settings.h"
  11 +
  12 +ExporterRegistry::ExporterRegistry(const Dso::ControlSpecification *deviceSpecification, DsoSettings *settings,
  13 + QObject *parent)
  14 + : QObject(parent), deviceSpecification(deviceSpecification), settings(settings) {}
  15 +
  16 +bool ExporterRegistry::processData(std::shared_ptr<PPresult> &data, ExporterInterface *const &exporter) {
  17 + if (!exporter->samples(data)) {
  18 + waitToSaveExporters.insert(exporter);
  19 + emit exporterProgressChanged();
  20 + return true;
  21 + }
  22 + return false;
  23 +}
  24 +
  25 +void ExporterRegistry::addRawSamples(PPresult *d) {
  26 + if (settings->exporting.useProcessedSamples) return;
  27 + std::shared_ptr<PPresult> data(d);
  28 + enabledExporters.remove_if([&data, this](ExporterInterface *const &i) { return processData(data, i); });
  29 +}
  30 +
  31 +void ExporterRegistry::input(std::shared_ptr<PPresult> data) {
  32 + if (!settings->exporting.useProcessedSamples) return;
  33 + enabledExporters.remove_if([&data, this](ExporterInterface *const &i) { return processData(data, i); });
  34 +}
  35 +
  36 +void ExporterRegistry::registerExporter(ExporterInterface *exporter) {
  37 + exporters.push_back(exporter);
  38 + exporter->create(this);
  39 +}
  40 +
  41 +void ExporterRegistry::setExporterEnabled(ExporterInterface *exporter, bool enabled) {
  42 + bool wasInList = false;
  43 + enabledExporters.remove_if([exporter, &wasInList](ExporterInterface *inlist) {
  44 + if (inlist == exporter) {
  45 + wasInList = true;
  46 + return true;
  47 + } else
  48 + return false;
  49 + });
  50 +
  51 + if (enabled) {
  52 + // If the exporter was waiting for the user save confirmation,
  53 + // reset it instead.
  54 + auto localFind = waitToSaveExporters.find(exporter);
  55 + if (localFind != waitToSaveExporters.end()) {
  56 + waitToSaveExporters.erase(localFind);
  57 + exporter->create(this);
  58 + }
  59 + enabledExporters.push_back(exporter);
  60 + } else if (wasInList) {
  61 + // If exporter made some progress: Add to waiting for GUI list
  62 + if (exporter->progress() > 0) {
  63 + waitToSaveExporters.insert(exporter);
  64 + emit exporterProgressChanged();
  65 + } else // Reset exporter
  66 + exporter->create(this);
  67 + }
  68 +}
  69 +
  70 +void ExporterRegistry::checkForWaitingExporters() {
  71 + for (ExporterInterface *exporter : waitToSaveExporters) {
  72 + if (exporter->save()) {
  73 + emit exporterStatusChanged(exporter->name(), tr("Data saved"));
  74 + } else {
  75 + emit exporterStatusChanged(exporter->name(), tr("No data exported"));
  76 + }
  77 + exporter->create(this);
  78 + }
  79 + waitToSaveExporters.clear();
  80 +}
  81 +
  82 +std::vector<ExporterInterface *>::const_iterator ExporterRegistry::begin() { return exporters.begin(); }
  83 +
  84 +std::vector<ExporterInterface *>::const_iterator ExporterRegistry::end() { return exporters.end(); }
... ...
openhantek/src/exporting/exporterregistry.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +
  5 +#include <QObject>
  6 +#include <memory>
  7 +#include <set>
  8 +#include <vector>
  9 +
  10 +// Post processing forwards
  11 +class Processor;
  12 +class PPresult;
  13 +
  14 +// Settings forwards
  15 +class DsoSettings;
  16 +namespace Dso {
  17 +struct ControlSpecification;
  18 +}
  19 +
  20 +// Exporter forwards
  21 +class ExporterInterface;
  22 +
  23 +class ExporterRegistry : public QObject {
  24 + Q_OBJECT
  25 + public:
  26 + explicit ExporterRegistry(const Dso::ControlSpecification *deviceSpecification, DsoSettings *settings,
  27 + QObject *parent = nullptr);
  28 +
  29 + // Sample input. This will proably be performed in the post processing
  30 + // thread context. Do not open GUI dialogs or interrupt the control flow.
  31 + void addRawSamples(PPresult *data);
  32 + void input(std::shared_ptr<PPresult> data);
  33 +
  34 + void registerExporter(ExporterInterface *exporter);
  35 + void setExporterEnabled(ExporterInterface *exporter, bool enabled);
  36 +
  37 + void checkForWaitingExporters();
  38 +
  39 + // Iterate over this class object
  40 + std::vector<ExporterInterface *>::const_iterator begin();
  41 + std::vector<ExporterInterface *>::const_iterator end();
  42 +
  43 + /// Device specifications
  44 + const Dso::ControlSpecification *deviceSpecification;
  45 + const DsoSettings *settings;
  46 +
  47 + private:
  48 + /// List of all available exporters
  49 + std::vector<ExporterInterface *> exporters;
  50 + /// List of exporters that collect samples at the moment
  51 + std::list<ExporterInterface *> enabledExporters;
  52 + /// List of exporters that wait to be called back by the user to save their work
  53 + std::set<ExporterInterface *> waitToSaveExporters;
  54 +
  55 + /// Process data from addRawSamples() or input() in the given exporter. Add the
  56 + /// exporter to waitToSaveExporters if it finishes.
  57 + ///
  58 + /// @return Return true if the exporter has finished and want to be removed from the
  59 + /// enabledExporters list.
  60 + bool processData(std::shared_ptr<PPresult> &data, ExporterInterface *const &exporter);
  61 + signals:
  62 + void exporterStatusChanged(const QString &exporterName, const QString &status);
  63 + void exporterProgressChanged();
  64 +};
... ...
openhantek/src/exporting/exportimage.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#include "exportimage.h"
  4 +#include "exporterregistry.h"
  5 +#include "iconfont/QtAwesome.h"
  6 +#include "legacyexportdrawer.h"
  7 +#include "post/ppresult.h"
  8 +#include "settings.h"
  9 +#include "viewsettings.h"
  10 +
  11 +#include <QCoreApplication>
  12 +#include <QFileDialog>
  13 +#include <QPrinter>
  14 +
  15 +ExporterImage::ExporterImage() {}
  16 +
  17 +void ExporterImage::create(ExporterRegistry *registry) { this->registry = registry; data.reset(); }
  18 +
  19 +QIcon ExporterImage::icon() { return iconFont->icon(fa::image); }
  20 +
  21 +QString ExporterImage::name() { return QCoreApplication::tr("Export Image/PDF"); }
  22 +
  23 +ExporterInterface::Type ExporterImage::type() { return Type::SnapshotExport; }
  24 +
  25 +bool ExporterImage::samples(const std::shared_ptr<PPresult> data) {
  26 + this->data = std::move(data);
  27 + return false;
  28 +}
  29 +
  30 +bool ExporterImage::save() {
  31 + QStringList filters;
  32 + filters << QCoreApplication::tr("Portable Document Format (*.pdf)")
  33 + << QCoreApplication::tr("Image (*.png *.xpm *.jpg)");
  34 +
  35 + QFileDialog fileDialog(nullptr, QCoreApplication::tr("Export file..."), QString(), filters.join(";;"));
  36 + fileDialog.setFileMode(QFileDialog::AnyFile);
  37 + fileDialog.setAcceptMode(QFileDialog::AcceptSave);
  38 + if (fileDialog.exec() != QDialog::Accepted) return false;
  39 +
  40 + bool isPdf = filters.indexOf(fileDialog.selectedNameFilter()) == 0;
  41 + std::unique_ptr<QPaintDevice> paintDevice;
  42 +
  43 + const DsoSettingsColorValues *colorValues = &(registry->settings->view.print);
  44 +
  45 + if (!isPdf) {
  46 + // We need a QPixmap for image-export
  47 + QPixmap *qPixmap = new QPixmap(registry->settings->exporting.imageSize);
  48 + qPixmap->fill(colorValues->background);
  49 + paintDevice = std::unique_ptr<QPaintDevice>(qPixmap);
  50 + } else {
  51 + // We need a QPrinter for printing, pdf- and ps-export
  52 + std::unique_ptr<QPrinter> printer = std::unique_ptr<QPrinter>(new QPrinter(QPrinter::HighResolution));
  53 + printer->setOrientation(registry->settings->view.zoom ? QPrinter::Portrait : QPrinter::Landscape);
  54 + printer->setPageMargins(20, 20, 20, 20, QPrinter::Millimeter);
  55 + printer->setOutputFileName(fileDialog.selectedFiles().first());
  56 + printer->setOutputFormat(QPrinter::PdfFormat);
  57 + paintDevice = std::move(printer);
  58 + }
  59 +
  60 + if (!paintDevice) return false;
  61 +
  62 + LegacyExportDrawer::exportSamples(data.get(), paintDevice.get(), registry->deviceSpecification, registry->settings,
  63 + false, colorValues);
  64 +
  65 + if (!isPdf) static_cast<QPixmap *>(paintDevice.get())->save(fileDialog.selectedFiles().first());
  66 + return true;
  67 +}
  68 +
  69 +float ExporterImage::progress() { return data ? 1.0f : 0; }
... ...
openhantek/src/exporting/exportimage.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +#include "exporterinterface.h"
  5 +
  6 +class ExporterImage : public ExporterInterface
  7 +{
  8 +public:
  9 + ExporterImage();
  10 + virtual void create(ExporterRegistry *registry) override;
  11 + virtual QIcon icon() override;
  12 + virtual QString name() override;
  13 + virtual Type type() override;
  14 + virtual bool samples(const std::shared_ptr<PPresult>data) override;
  15 + virtual bool save() override;
  16 + virtual float progress() override;
  17 +private:
  18 + std::shared_ptr<PPresult> data;
  19 +};
... ...
openhantek/src/exporting/exportprint.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#include "exportprint.h"
  4 +#include "exporterregistry.h"
  5 +#include "iconfont/QtAwesome.h"
  6 +#include "legacyexportdrawer.h"
  7 +#include "post/ppresult.h"
  8 +#include "settings.h"
  9 +#include "viewsettings.h"
  10 +
  11 +#include <QCoreApplication>
  12 +#include <QPrintDialog>
  13 +#include <QPrinter>
  14 +
  15 +ExporterPrint::ExporterPrint() {}
  16 +
  17 +void ExporterPrint::create(ExporterRegistry *registry) { this->registry = registry; data.reset(); }
  18 +
  19 +QIcon ExporterPrint::icon() { return iconFont->icon(fa::print); }
  20 +
  21 +QString ExporterPrint::name() { return QCoreApplication::tr("Print"); }
  22 +
  23 +ExporterInterface::Type ExporterPrint::type() { return Type::SnapshotExport; }
  24 +
  25 +bool ExporterPrint::samples(const std::shared_ptr<PPresult> data) {
  26 + this->data = std::move(data);
  27 + return false;
  28 +}
  29 +
  30 +bool ExporterPrint::save() {
  31 + // We need a QPrinter for printing, pdf- and ps-export
  32 + std::unique_ptr<QPrinter> printer = std::unique_ptr<QPrinter>(new QPrinter(QPrinter::HighResolution));
  33 + printer->setOrientation(registry->settings->view.zoom ? QPrinter::Portrait : QPrinter::Landscape);
  34 + printer->setPageMargins(20, 20, 20, 20, QPrinter::Millimeter);
  35 +
  36 + // Show the printing dialog
  37 + QPrintDialog dialog(printer.get());
  38 + dialog.setWindowTitle(QCoreApplication::tr("Print oscillograph"));
  39 + if (dialog.exec() != QDialog::Accepted) { return false; }
  40 +
  41 + const DsoSettingsColorValues *colorValues = &(registry->settings->view.print);
  42 +
  43 + LegacyExportDrawer::exportSamples(data.get(), printer.get(), registry->deviceSpecification, registry->settings,
  44 + true, colorValues);
  45 +
  46 + return true;
  47 +}
  48 +
  49 +float ExporterPrint::progress() { return data ? 1.0f : 0; }
... ...
openhantek/src/exporting/exportprint.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +#include "exporterinterface.h"
  5 +
  6 +class ExporterPrint : public ExporterInterface
  7 +{
  8 +public:
  9 + ExporterPrint();
  10 + virtual void create(ExporterRegistry *registry) override;
  11 + virtual QIcon icon() override;
  12 + virtual QString name() override;
  13 + virtual Type type() override;
  14 + virtual bool samples(const std::shared_ptr<PPresult>data) override;
  15 + virtual bool save() override;
  16 + virtual float progress() override;
  17 +private:
  18 + std::shared_ptr<PPresult> data;
  19 +};
... ...
openhantek/src/exporting/exportsettings.h
... ... @@ -4,10 +4,9 @@
4 4  
5 5 #include <QSize>
6 6  
7   -/// \brief Holds the general options of the program.
  7 +/// \brief Holds the export options of the program.
8 8 struct DsoSettingsExport {
9 9 QSize imageSize = QSize(640, 480); ///< Size of exported images in pixels
  10 + unsigned exportSizeBytes = 1024*1024*10; ///< For exporters that save a continous stream. Default: 10 Megabytes
  11 + bool useProcessedSamples = true; ///< Export raw or processed samples
10 12 };
11   -
12   -/// \brief Possible file formats for the export.
13   -enum ExportFormat { EXPORT_FORMAT_PRINTER, EXPORT_FORMAT_PDF, EXPORT_FORMAT_IMAGE, EXPORT_FORMAT_CSV };
... ...
openhantek/src/exporting/exporter.cpp renamed to openhantek/src/exporting/legacyexportdrawer.cpp
... ... @@ -5,101 +5,29 @@
5 5 #include <memory>
6 6  
7 7 #include <QCoreApplication>
8   -#include <QFile>
9   -#include <QFileDialog>
10 8 #include <QImage>
11   -#include <QMutex>
12 9 #include <QPainter>
13   -#include <QPixmap>
14   -#include <QPrintDialog>
15   -#include <QPrinter>
16   -#include <QTextStream>
17 10  
18   -#include "exporter.h"
  11 +#include "legacyexportdrawer.h"
19 12  
20 13 #include "controlspecification.h"
21 14 #include "post/graphgenerator.h"
22 15 #include "post/ppresult.h"
23 16 #include "settings.h"
24   -#include "utils/dsoStrings.h"
25 17 #include "utils/printutils.h"
26 18 #include "viewconstants.h"
27 19  
28 20 #define tr(msg) QCoreApplication::translate("Exporter", msg)
29 21  
30   -Exporter::Exporter(const Dso::ControlSpecification *deviceSpecification, DsoSettings *settings, const QString &filename,
31   - ExportFormat format)
32   - : deviceSpecification(deviceSpecification), settings(settings), filename(filename), format(format) {}
33   -
34   -Exporter *Exporter::createPrintExporter(const Dso::ControlSpecification *deviceSpecification, DsoSettings *settings) {
35   - std::unique_ptr<QPrinter> printer = printPaintDevice(settings);
36   - // Show the printing dialog
37   - QPrintDialog dialog(printer.get());
38   - dialog.setWindowTitle(tr("Print oscillograph"));
39   - if (dialog.exec() != QDialog::Accepted) { return nullptr; }
40   -
41   - Exporter *exporter = new Exporter(deviceSpecification, settings, QString(), EXPORT_FORMAT_PRINTER);
42   - exporter->selectedPrinter = std::move(printer);
43   - return exporter;
44   -}
45   -
46   -Exporter *Exporter::createSaveToFileExporter(const Dso::ControlSpecification *deviceSpecification,
47   - DsoSettings *settings) {
48   - QStringList filters;
49   - filters << tr("Portable Document Format (*.pdf)") << tr("Image (*.png *.xpm *.jpg)")
50   - << tr("Comma-Separated Values (*.csv)");
51   -
52   - QFileDialog fileDialog(nullptr, tr("Export file..."), QString(), filters.join(";;"));
53   - fileDialog.setFileMode(QFileDialog::AnyFile);
54   - fileDialog.setAcceptMode(QFileDialog::AcceptSave);
55   - if (fileDialog.exec() != QDialog::Accepted) return nullptr;
56   -
57   - return new Exporter(deviceSpecification, settings, fileDialog.selectedFiles().first(),
58   - (ExportFormat)(EXPORT_FORMAT_PDF + filters.indexOf(fileDialog.selectedNameFilter())));
59   -}
60   -
61   -std::unique_ptr<QPrinter> Exporter::printPaintDevice(DsoSettings *settings) {
62   - // We need a QPrinter for printing, pdf- and ps-export
63   - std::unique_ptr<QPrinter> printer = std::unique_ptr<QPrinter>(new QPrinter(QPrinter::HighResolution));
64   - printer->setOrientation(settings->view.zoom ? QPrinter::Portrait : QPrinter::Landscape);
65   - printer->setPageMargins(20, 20, 20, 20, QPrinter::Millimeter);
66   - return printer;
67   -}
68   -
69   -bool Exporter::exportSamples(const PPresult *result) {
70   - if (this->format == EXPORT_FORMAT_CSV) { return exportCSV(result); }
71   -
72   - // Choose the color values we need
73   - DsoSettingsColorValues *colorValues;
74   - if (this->format == EXPORT_FORMAT_IMAGE && settings->view.screenColorImages)
75   - colorValues = &(settings->view.screen);
76   - else
77   - colorValues = &(settings->view.print);
78   -
79   - std::unique_ptr<QPaintDevice> paintDevice;
80   -
81   - if (this->format == EXPORT_FORMAT_IMAGE) {
82   - // We need a QPixmap for image-export
83   - QPixmap *qPixmap = new QPixmap(settings->exporting.imageSize);
84   - qPixmap->fill(colorValues->background);
85   - paintDevice = std::unique_ptr<QPaintDevice>(qPixmap);
86   - } else if (this->format == EXPORT_FORMAT_PRINTER) {
87   - paintDevice = std::move(selectedPrinter);
88   - } else {
89   - std::unique_ptr<QPrinter> printer = printPaintDevice(settings);
90   - printer->setOutputFileName(this->filename);
91   - printer->setOutputFormat((this->format == EXPORT_FORMAT_PDF) ? QPrinter::PdfFormat : QPrinter::NativeFormat);
92   - paintDevice = std::move(printer);
93   - }
94   -
95   - if (!paintDevice) return false;
96   -
  22 +bool LegacyExportDrawer::exportSamples(const PPresult *result, QPaintDevice* paintDevice,
  23 + const Dso::ControlSpecification *deviceSpecification,
  24 + const DsoSettings *settings, bool isPrinter, const DsoSettingsColorValues *colorValues) {
97 25 // Create a painter for our device
98   - QPainter painter(paintDevice.get());
  26 + QPainter painter(paintDevice);
99 27  
100 28 // Get line height
101 29 QFont font;
102   - QFontMetrics fontMetrics(font, paintDevice.get());
  30 + QFontMetrics fontMetrics(font, paintDevice);
103 31 double lineHeight = fontMetrics.height();
104 32  
105 33 painter.setBrush(Qt::SolidPattern);
... ... @@ -150,12 +78,12 @@ bool Exporter::exportSamples(const PPresult *result) {
150 78 painter.drawText(QRectF(0, top, lineHeight * 4, lineHeight), settings->scope.voltage[channel].name);
151 79 // Print coupling/math mode
152 80 if ((unsigned int)channel < deviceSpecification->channels)
  81 + painter.drawText(QRectF(lineHeight * 4, top, lineHeight * 2, lineHeight),
  82 + Dso::couplingString(settings->scope.coupling(channel, deviceSpecification)));
  83 + else
153 84 painter.drawText(
154 85 QRectF(lineHeight * 4, top, lineHeight * 2, lineHeight),
155   - Dso::couplingString(settings->scope.coupling(channel, deviceSpecification)));
156   - else
157   - painter.drawText(QRectF(lineHeight * 4, top, lineHeight * 2, lineHeight),
158   - Dso::mathModeString(settings->scope.voltage[channel].math));
  86 + Dso::mathModeString(Dso::getMathMode(settings->scope.voltage[channel])));
159 87  
160 88 // Print voltage gain
161 89 painter.drawText(QRectF(lineHeight * 6, top, stretchBase * 2, lineHeight),
... ... @@ -325,90 +253,17 @@ bool Exporter::exportSamples(const PPresult *result) {
325 253 }
326 254 } // dataanalyser mutex release
327 255  
328   - drawGrids(painter, colorValues, lineHeight, scopeHeight, paintDevice->width());
  256 + drawGrids(painter, colorValues, lineHeight, scopeHeight, paintDevice->width(),
  257 + isPrinter, settings->view.zoom);
329 258 painter.end();
330 259  
331   - if (this->format == EXPORT_FORMAT_IMAGE) static_cast<QPixmap *>(paintDevice.get())->save(this->filename);
332   -
333   - return true;
334   -}
335   -
336   -bool Exporter::exportCSV(const PPresult *result) {
337   - QFile csvFile(this->filename);
338   - if (!csvFile.open(QIODevice::WriteOnly | QIODevice::Text)) return false;
339   -
340   - QTextStream csvStream(&csvFile);
341   -
342   - size_t chCount = settings->scope.voltage.size();
343   - std::vector<const SampleValues *> voltageData(size_t(chCount), nullptr);
344   - std::vector<const SampleValues *> spectrumData(size_t(chCount), nullptr);
345   - size_t maxRow = 0;
346   - bool isSpectrumUsed = false;
347   - double timeInterval = 0;
348   - double freqInterval = 0;
349   -
350   - for (ChannelID channel = 0; channel < chCount; ++channel) {
351   - if (result->data(channel)) {
352   - if (settings->scope.voltage[channel].used) {
353   - voltageData[channel] = &(result->data(channel)->voltage);
354   - maxRow = std::max(maxRow, voltageData[channel]->sample.size());
355   - timeInterval = result->data(channel)->voltage.interval;
356   - }
357   - if (settings->scope.spectrum[channel].used) {
358   - spectrumData[channel] = &(result->data(channel)->spectrum);
359   - maxRow = std::max(maxRow, spectrumData[channel]->sample.size());
360   - freqInterval = result->data(channel)->spectrum.interval;
361   - isSpectrumUsed = true;
362   - }
363   - }
364   - }
365   -
366   - // Start with channel names
367   - csvStream << "\"t\"";
368   - for (ChannelID channel = 0; channel < chCount; ++channel) {
369   - if (voltageData[channel] != nullptr) { csvStream << ",\"" << settings->scope.voltage[channel].name << "\""; }
370   - }
371   - if (isSpectrumUsed) {
372   - csvStream << ",\"f\"";
373   - for (ChannelID channel = 0; channel < chCount; ++channel) {
374   - if (spectrumData[channel] != nullptr) {
375   - csvStream << ",\"" << settings->scope.spectrum[channel].name << "\"";
376   - }
377   - }
378   - }
379   - csvStream << "\n";
380   -
381   - for (unsigned int row = 0; row < maxRow; ++row) {
382   -
383   - csvStream << timeInterval * row;
384   - for (ChannelID channel = 0; channel < chCount; ++channel) {
385   - if (voltageData[channel] != nullptr) {
386   - csvStream << ",";
387   - if (row < voltageData[channel]->sample.size()) { csvStream << voltageData[channel]->sample[row]; }
388   - }
389   - }
390   -
391   - if (isSpectrumUsed) {
392   - csvStream << "," << freqInterval * row;
393   - for (ChannelID channel = 0; channel < chCount; ++channel) {
394   - if (spectrumData[channel] != nullptr) {
395   - csvStream << ",";
396   - if (row < spectrumData[channel]->sample.size()) { csvStream << spectrumData[channel]->sample[row]; }
397   - }
398   - }
399   - }
400   - csvStream << "\n";
401   - }
402   -
403   - csvFile.close();
404   -
405 260 return true;
406 261 }
407 262  
408   -void Exporter::drawGrids(QPainter &painter, DsoSettingsColorValues *colorValues, double lineHeight, double scopeHeight,
409   - int scopeWidth) {
  263 +void LegacyExportDrawer::drawGrids(QPainter &painter, const DsoSettingsColorValues *colorValues, double lineHeight, double scopeHeight,
  264 + int scopeWidth, bool isPrinter, bool zoom) {
410 265 painter.setRenderHint(QPainter::Antialiasing, false);
411   - for (int zoomed = 0; zoomed < (settings->view.zoom ? 2 : 1); ++zoomed) {
  266 + for (int zoomed = 0; zoomed < (zoom ? 2 : 1); ++zoomed) {
412 267 // Set DIVS_TIME x DIVS_VOLTAGE matrix for oscillograph
413 268 painter.setMatrix(QMatrix((scopeWidth - 1) / DIVS_TIME, 0, 0, -(scopeHeight - 1) / DIVS_VOLTAGE,
414 269 (double)(scopeWidth - 1) / 2,
... ... @@ -418,7 +273,7 @@ void Exporter::drawGrids(QPainter &amp;painter, DsoSettingsColorValues *colorValues,
418 273 // Grid lines
419 274 painter.setPen(QPen(colorValues->grid, 0));
420 275  
421   - if (this->format < EXPORT_FORMAT_IMAGE) {
  276 + if (isPrinter) {
422 277 // Draw vertical lines
423 278 for (int div = 1; div < DIVS_TIME / 2; ++div) {
424 279 for (int dot = 1; dot < DIVS_VOLTAGE / 2 * 5; ++dot) {
... ...
openhantek/src/exporting/legacyexportdrawer.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +
  5 +#include <QPainter>
  6 +#include <QPrinter>
  7 +#include <QSize>
  8 +#include <memory>
  9 +#include "exportsettings.h"
  10 +
  11 +class DsoSettings;
  12 +class PPresult;
  13 +struct DsoSettingsColorValues;
  14 +namespace Dso { struct ControlSpecification; }
  15 +
  16 +/// \brief Exports the oscilloscope screen to a file or prints it.
  17 +/// TODO
  18 +/// Rewrite image exporter with OpenGL drawn grid and graphs
  19 +///
  20 +/// Sources:
  21 +/// http://doc.qt.io/qt-5/qoffscreensurface.html
  22 +/// http://doc.qt.io/qt-5/qopenglframebufferobject.html
  23 +///
  24 +/// https://dangelog.wordpress.com/2013/02/10/using-fbos-instead-of-pbuffers-in-qt-5-2/
  25 +class LegacyExportDrawer {
  26 + public:
  27 + /// Draw the graphs coming from source and labels to the destination paintdevice.
  28 + static bool exportSamples(const PPresult *source, QPaintDevice* dest,
  29 + const Dso::ControlSpecification* deviceSpecification,
  30 + const DsoSettings *settings, bool isPrinter, const DsoSettingsColorValues *colorValues);
  31 +
  32 + private:
  33 + static void drawGrids(QPainter &painter, const DsoSettingsColorValues *colorValues, double lineHeight, double scopeHeight,
  34 + int scopeWidth, bool isPrinter, bool zoom);
  35 +};
... ...
openhantek/src/exporting/readme.md 0 → 100644
  1 +# Content
  2 +This directory contains exporting functionality and exporters, namely
  3 +
  4 +* Export to comma separated value file (CSV): Write to a user selected file,
  5 +* Export to an image/pdf: Writes an image/pdf to a user selected file,
  6 +* Print exporter: Creates a printable document and opens the print dialog.
  7 +
  8 +All export classes (exportcsv, exportimage, exportprint) implement the
  9 +ExporterInterface and are registered to the ExporterRegistry in the main.cpp.
  10 +
  11 +Some export classes are still using the legacyExportDrawer class to
  12 +draw the grid and paint all the labels, values and graphs.
  13 +
  14 +# Dependency
  15 +* Files in this directory depend on the result class of the post processing directory.
  16 +* Classes in here depend on the user settings (../viewsetting.h, ../scopesetting.h)
... ...
openhantek/src/main.cpp
... ... @@ -12,19 +12,34 @@
12 12 #include <libusb-1.0/libusb.h>
13 13 #include <memory>
14 14  
  15 +// Settings
  16 +#include "settings.h"
  17 +#include "viewconstants.h"
  18 +
  19 +// DSO core logic
  20 +#include "dsomodel.h"
  21 +#include "hantekdsocontrol.h"
  22 +#include "usb/usbdevice.h"
  23 +
  24 +// Post processing
15 25 #include "post/graphgenerator.h"
16 26 #include "post/mathchannelgenerator.h"
17 27 #include "post/postprocessing.h"
18 28 #include "post/spectrumgenerator.h"
19 29  
20   -#include "dsomodel.h"
21   -#include "hantekdsocontrol.h"
  30 +// Exporter
  31 +#include "exporting/exportcsv.h"
  32 +#include "exporting/exporterprocessor.h"
  33 +#include "exporting/exporterregistry.h"
  34 +#include "exporting/exportimage.h"
  35 +#include "exporting/exportprint.h"
  36 +
  37 +// GUI
  38 +#include "iconfont/QtAwesome.h"
22 39 #include "mainwindow.h"
23 40 #include "selectdevice/selectsupporteddevice.h"
24   -#include "settings.h"
25   -#include "usb/usbdevice.h"
26   -#include "viewconstants.h"
27 41  
  42 +// OpenGL setup
28 43 #include "glscope.h"
29 44  
30 45 #ifndef VERSION
... ... @@ -71,6 +86,10 @@ int main(int argc, char *argv[]) {
71 86 QCoreApplication::setOrganizationDomain("www.openhantek.org");
72 87 QCoreApplication::setApplicationName("OpenHantek");
73 88 QCoreApplication::setApplicationVersion(VERSION);
  89 + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
  90 +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
  91 + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
  92 +#endif
74 93  
75 94 bool useGles = false;
76 95 {
... ... @@ -126,34 +145,55 @@ int main(int argc, char *argv[]) {
126 145 &QCoreApplication::quit);
127 146  
128 147 //////// Create settings object ////////
129   - auto settings = std::unique_ptr<DsoSettings>(new DsoSettings(device->getModel()->spec()));
  148 + DsoSettings settings(device->getModel()->spec());
  149 +
  150 + //////// Create exporters ////////
  151 + ExporterRegistry exportRegistry(device->getModel()->spec(), &settings);
  152 +
  153 + ExporterCSV exporterCSV;
  154 + ExporterImage exportImage;
  155 + ExporterPrint exportPrint;
  156 +
  157 + ExporterProcessor samplesToExportRaw(&exportRegistry);
  158 +
  159 + exportRegistry.registerExporter(&exporterCSV);
  160 + exportRegistry.registerExporter(&exportImage);
  161 + exportRegistry.registerExporter(&exportPrint);
130 162  
131 163 //////// Create post processing objects ////////
132 164 QThread postProcessingThread;
133 165 postProcessingThread.setObjectName("postProcessingThread");
134   - PostProcessing postProcessing(settings->scope.countChannels());
  166 + PostProcessing postProcessing(settings.scope.countChannels());
135 167  
136   - SpectrumGenerator spectrumGenerator(&settings->scope);
137   - MathChannelGenerator mathchannelGenerator(&settings->scope, device->getModel()->spec()->channels);
138   - GraphGenerator graphGenerator(&settings->scope, device->getModel()->spec()->isSoftwareTriggerDevice);
  168 + SpectrumGenerator spectrumGenerator(&settings.scope, &settings.post);
  169 + MathChannelGenerator mathchannelGenerator(&settings.scope, device->getModel()->spec()->channels);
  170 + GraphGenerator graphGenerator(&settings.scope, device->getModel()->spec()->isSoftwareTriggerDevice);
139 171  
  172 + postProcessing.registerProcessor(&samplesToExportRaw);
140 173 postProcessing.registerProcessor(&mathchannelGenerator);
141 174 postProcessing.registerProcessor(&spectrumGenerator);
142 175 postProcessing.registerProcessor(&graphGenerator);
143 176  
144 177 postProcessing.moveToThread(&postProcessingThread);
145 178 QObject::connect(&dsoControl, &HantekDsoControl::samplesAvailable, &postProcessing, &PostProcessing::input);
  179 + QObject::connect(&postProcessing, &PostProcessing::processingFinished, &exportRegistry, &ExporterRegistry::input,
  180 + Qt::DirectConnection);
146 181  
147 182 //////// Create main window ////////
148   - MainWindow openHantekMainWindow(&dsoControl, settings.get());
  183 + iconFont->initFontAwesome();
  184 + MainWindow openHantekMainWindow(&dsoControl, &settings, &exportRegistry);
149 185 QObject::connect(&postProcessing, &PostProcessing::processingFinished, &openHantekMainWindow,
150 186 &MainWindow::showNewData);
  187 + QObject::connect(&exportRegistry, &ExporterRegistry::exporterProgressChanged, &openHantekMainWindow,
  188 + &MainWindow::exporterProgressChanged);
  189 + QObject::connect(&exportRegistry, &ExporterRegistry::exporterStatusChanged, &openHantekMainWindow,
  190 + &MainWindow::exporterStatusChanged);
151 191 openHantekMainWindow.show();
152 192  
153   - applySettingsToDevice(&dsoControl, &settings->scope, device->getModel()->spec());
  193 + applySettingsToDevice(&dsoControl, &settings.scope, device->getModel()->spec());
154 194  
155 195 //////// Start DSO thread and go into GUI main loop
156   - dsoControl.startSampling();
  196 + dsoControl.enableSampling(true);
157 197 postProcessingThread.start();
158 198 dsoControlThread.start();
159 199 int res = openHantekApplication.exec();
... ...