Changeset - ba9e01b30ba2
[Not reviewed]
Merge default
0 23 0
Hasan Yavuz Ă–ZDERYA - 7 years ago 2019-01-07 23:34:32
hy@ozderya.net
Merge with value-tracker
23 files changed with 403 insertions and 115 deletions:
0 comments (0 inline, 0 general)
CMakeLists.txt
Show inline comments
 
@@ -97,24 +97,25 @@ add_executable(${PROGRAM_NAME} WIN32
 
  src/dataformatpanel.cpp
 
  src/plotcontrolpanel.cpp
 
  src/recordpanel.cpp
 
  src/datarecorder.cpp
 
  src/tooltipfilter.cpp
 
  src/sneakylineedit.cpp
 
  src/stream.cpp
 
  src/streamchannel.cpp
 
  src/channelinfomodel.cpp
 
  src/ringbuffer.cpp
 
  src/ringbuffer.cpp
 
  src/indexbuffer.cpp
 
  src/linindexbuffer.cpp
 
  src/readonlybuffer.cpp
 
  src/framebufferseries.cpp
 
  src/numberformatbox.cpp
 
  src/endiannessbox.cpp
 
  src/abstractreader.cpp
 
  src/binarystreamreader.cpp
 
  src/binarystreamreadersettings.cpp
 
  src/asciireader.cpp
 
  src/asciireadersettings.cpp
 
  src/demoreader.cpp
 
  src/demoreadersettings.cpp
 
  src/framedreader.cpp
src/framebuffer.h
Show inline comments
 
@@ -52,13 +52,33 @@ public:
 
    virtual void resize(unsigned n) = 0;
 
};
 

	
 
/// Abstract base class for writable frame buffers
 
class WFrameBuffer : public ResizableBuffer
 
{
 
    /// Add samples to the buffer
 
    virtual void addSamples(double* samples, unsigned n) = 0;
 
    /// Reset all data to 0
 
    virtual void clear() = 0;
 
};
 

	
 
/**
 
 * Abstract base class for X buffers.
 
 *
 
 * These buffers only contain increasing or equal (to previous) values.
 
 */
 
class XFrameBuffer : public ResizableBuffer
 
{
 
public:
 
    enum Index {OUT_OF_RANGE = -1};
 

	
 
    /**
 
     * Finds index for given value.
 
     *
 
     * If given value is bigger than max or smaller than minimum
 
     * returns `OUT_OF_RANGE`. If it's in between values, smaller
 
     * index is returned (not closer one).
 
     */
 
    virtual int findIndex(double value) const = 0;
 
};
 

	
 
#endif // FRAMEBUFFER_H
src/framebufferseries.cpp
Show inline comments
 
@@ -11,84 +11,73 @@
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <math.h>
 
#include "framebufferseries.h"
 

	
 
FrameBufferSeries::FrameBufferSeries(const FrameBuffer* buffer)
 
FrameBufferSeries::FrameBufferSeries(const XFrameBuffer* x, const FrameBuffer* y)
 
{
 
    xAsIndex = true;
 
    _xmin = 0;
 
    _xmax = 1;
 
    _buffer = buffer;
 
    _x = x;
 
    _y = y;
 

	
 
    int_index_start = 0;
 
    int_index_end = _buffer->size();
 
    int_index_end = _y->size();
 
}
 

	
 
void FrameBufferSeries::setXAxis(bool asIndex, double xmin, double xmax)
 
void FrameBufferSeries::setX(const XFrameBuffer* x)
 
{
 
    xAsIndex = asIndex;
 
    _xmin = xmin;
 
    _xmax = xmax;
 
    _x = x;
 
}
 

	
 
size_t FrameBufferSeries::size() const
 
{
 
    return int_index_end - int_index_start;
 
    return int_index_end - int_index_start + 1;
 
}
 

	
 
QPointF FrameBufferSeries::sample(size_t i) const
 
{
 
    i += int_index_start;
 
    if (xAsIndex)
 
    {
 
        return QPointF(i, _buffer->sample(i));
 
    }
 
    else
 
    {
 
        return QPointF(i * (_xmax - _xmin) / _buffer->size() + _xmin, _buffer->sample(i));
 
    }
 
    return QPointF(_x->sample(i), _y->sample(i));
 
}
 

	
 
QRectF FrameBufferSeries::boundingRect() const
 
{
 
    QRectF rect;
 
    auto yLim = _buffer->limits();
 
    auto yLim = _y->limits();
 
    auto xLim = _x->limits();
 
    rect.setBottom(yLim.start);
 
    rect.setTop(yLim.end);
 
    if (xAsIndex)
 
    {
 
        rect.setLeft(0);
 
        rect.setRight(size());
 
    }
 
    else
 
    {
 
        rect.setLeft(_xmin);
 
        rect.setRight(_xmax);
 
    }
 
    rect.setLeft(xLim.start);
 
    rect.setRight(xLim.end);
 

	
 
    return rect.normalized();
 
}
 

	
 
void FrameBufferSeries::setRectOfInterest(const QRectF& rect)
 
{
 
    if (xAsIndex)
 
    int_index_start = _x->findIndex(rect.left());
 
    int_index_end = _x->findIndex(rect.right());
 

	
 
    if (int_index_start == XFrameBuffer::OUT_OF_RANGE)
 
    {
 
        int_index_start = floor(rect.left())-1;
 
        int_index_end = ceil(rect.right())+1;
 
        int_index_start = 0;
 
    }
 
    else
 
    else if (int_index_start > 0)
 
    {
 
        double xsize = _xmax - _xmin;
 
        size_t bsize = _buffer->size();
 
        int_index_start =  floor(bsize * (rect.left()-_xmin) / xsize)-1;
 
        int_index_end = ceil(bsize * (rect.right()-_xmin) / xsize)+1;
 
        int_index_start -= 1;
 
    }
 

	
 
    int_index_start = std::max(int_index_start, 0);
 
    int_index_end = std::min((int) _buffer->size(), int_index_end);
 
    if (int_index_end == XFrameBuffer::OUT_OF_RANGE)
 
    {
 
        int_index_end = _x->size()-1;
 
    }
 
    else if (int_index_end < (int)_x->size()-1)
 
    {
 
        int_index_end += 1;
 
    }
 
}
src/framebufferseries.h
Show inline comments
 
@@ -26,34 +26,31 @@
 

	
 
#include "framebuffer.h"
 

	
 
/**
 
 * This class provides an interface for actual FrameBuffer
 
 * object. That way we can keep our data structures relatively
 
 * isolated from Qwt. Otherwise QwtPlotCurve owns FrameBuffer
 
 * structures.
 
 */
 
class FrameBufferSeries : public QwtSeriesData<QPointF>
 
{
 
public:
 
    FrameBufferSeries(const FrameBuffer* buffer);
 
    FrameBufferSeries(const XFrameBuffer* x, const FrameBuffer* y);
 

	
 
    /// Behavior of X axis
 
    void setXAxis(bool asIndex, double xmin, double xmax);
 
    void setX(const XFrameBuffer* x);
 

	
 
    // QwtSeriesData implementations
 
    size_t size() const;
 
    QPointF sample(size_t i) const;
 
    QRectF boundingRect() const;
 
    void setRectOfInterest(const QRectF& rect);
 

	
 
private:
 
    const FrameBuffer* _buffer;
 
    bool xAsIndex;
 
    double _xmin;
 
    double _xmax;
 
    const XFrameBuffer* _x;
 
    const FrameBuffer* _y;
 

	
 
    int int_index_start; ///< starting index of "rectangle of interest"
 
    int int_index_end;   ///< ending index of "rectangle of interest"
 
};
 

	
 
#endif // FRAMEBUFFERSERIES_H
src/indexbuffer.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -38,12 +38,24 @@ void IndexBuffer::resize(unsigned n)
 

	
 
double IndexBuffer::sample(unsigned i) const
 
{
 
    Q_ASSERT(i < _size);
 

	
 
    return i;
 
}
 

	
 
Range IndexBuffer::limits() const
 
{
 
    return Range{0, _size-1.};
 
}
 

	
 
int IndexBuffer::findIndex(double value) const
 
{
 
    if (value < 0 || value > size() - 1)
 
    {
 
        return OUT_OF_RANGE;
 
    }
 
    else
 
    {
 
        return value;
 
    }
 
}
src/indexbuffer.h
Show inline comments
 
@@ -17,27 +17,28 @@
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef INDEXBUFFER_H
 
#define INDEXBUFFER_H
 

	
 
#include "framebuffer.h"
 

	
 
/// A simple frame buffer that simply returns requested index as
 
/// sample value.
 
///
 
/// @note This buffer isn't for storing data.
 
class IndexBuffer : public ResizableBuffer
 
class IndexBuffer : public XFrameBuffer
 
{
 
public:
 
    IndexBuffer(unsigned n);
 

	
 
    unsigned size() const;
 
    double sample(unsigned i) const;
 
    Range limits() const;
 
    void resize(unsigned n);
 
    unsigned size() const override;
 
    double sample(unsigned i) const override;
 
    Range limits() const override;
 
    void resize(unsigned n) override;
 
    int findIndex(double value) const override;
 

	
 
private:
 
    unsigned _size;
 
};
 

	
 
#endif
src/linindexbuffer.cpp
Show inline comments
 
 /*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 
#include <algorithm>
 

	
 
#include "linindexbuffer.h"
 

	
 
LinIndexBuffer::LinIndexBuffer(unsigned n, Range lim)
 
{
 
    Q_ASSERT(n > 0);
 
    // Note that calculation of _step would cause divide by 0
 
    Q_ASSERT(n > 1);
 

	
 
    _size = n;
 
    setLimits(lim);
 
}
 

	
 
unsigned LinIndexBuffer::size() const
 
{
 
    return _size;
 
}
 

	
 
double LinIndexBuffer::sample(unsigned i) const
 
{
 
@@ -41,17 +43,29 @@ double LinIndexBuffer::sample(unsigned i
 

	
 
Range LinIndexBuffer::limits() const
 
{
 
    return _limits;
 
}
 

	
 
void LinIndexBuffer::resize(unsigned n)
 
{
 
    _size = n;
 
    setLimits(_limits);         // called to update `_step`
 
}
 

	
 
int LinIndexBuffer::findIndex(double value) const
 
{
 
    if (value < _limits.start || value > _limits.end)
 
    {
 
        return OUT_OF_RANGE;
 
    }
 

	
 
    int r = (value - _limits.start) / _step;
 
    // Note: we are limiting return value because of floating point in-accuracies
 
    return std::min<int>(std::max<int>(r, 0), (_size-1));
 
}
 

	
 
void LinIndexBuffer::setLimits(Range lim)
 
{
 
    _limits = lim;
 
    _step = (lim.end - lim.start) / (_size-1);
 
}
src/linindexbuffer.h
Show inline comments
 
@@ -17,33 +17,35 @@
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef LININDEXBUFFER_H
 
#define LININDEXBUFFER_H
 

	
 
#include "framebuffer.h"
 

	
 
/// A dynamic frame buffer that start and end values can be set and
 
/// intermediate values are calculated linearly.
 
///
 
/// @note This buffer isn't for storing data.
 
class LinIndexBuffer : public ResizableBuffer
 
class LinIndexBuffer : public XFrameBuffer
 
{
 
public:
 
    LinIndexBuffer(unsigned n, Range lim);
 
    LinIndexBuffer(unsigned n, double min, double max) :
 
        LinIndexBuffer(n, {min, max}) {};
 

	
 
    unsigned size() const;
 
    double sample(unsigned i) const;
 
    Range limits() const;
 
    void resize(unsigned n);
 
    unsigned size() const override;
 
    double sample(unsigned i) const override;
 
    Range limits() const override;
 
    void resize(unsigned n) override;
 
    int findIndex(double value) const override;
 

	
 
    /// Sets minimum and maximum sample values of the buffer.
 
    void setLimits(Range lim);
 

	
 
private:
 
    unsigned _size;
 
    Range _limits;
 
    double _step;
 
};
 

	
 
#endif
src/mainwindow.cpp
Show inline comments
 
@@ -160,24 +160,27 @@ MainWindow::MainWindow(QWidget *parent) 
 

	
 
    // plot control signals
 
    connect(&plotControlPanel, &PlotControlPanel::numOfSamplesChanged,
 
            this, &MainWindow::onNumOfSamplesChanged);
 

	
 
    connect(&plotControlPanel, &PlotControlPanel::numOfSamplesChanged,
 
            plotMan, &PlotManager::setNumOfSamples);
 

	
 
    connect(&plotControlPanel, &PlotControlPanel::yScaleChanged,
 
            plotMan, &PlotManager::setYAxis);
 

	
 
    connect(&plotControlPanel, &PlotControlPanel::xScaleChanged,
 
            &stream, &Stream::setXAxis);
 

	
 
    connect(&plotControlPanel, &PlotControlPanel::xScaleChanged,
 
            plotMan, &PlotManager::setXAxis);
 

	
 
    connect(&plotControlPanel, &PlotControlPanel::plotWidthChanged,
 
            plotMan, &PlotManager::setPlotWidth);
 

	
 
    // plot toolbar signals
 
    QObject::connect(ui->actionClear, SIGNAL(triggered(bool)),
 
                     this, SLOT(clearPlot()));
 

	
 
    QObject::connect(snapshotMan.takeSnapshotAction(), &QAction::triggered,
 
                     plotMan, &PlotManager::flashSnapshotOverlay);
 

	
 
@@ -206,24 +209,27 @@ MainWindow::MainWindow(QWidget *parent) 
 
                         }
 
                     });
 

	
 
    connect(&serialPort, &QIODevice::aboutToClose,
 
            &recordPanel, &RecordPanel::onPortClose);
 

	
 
    // init plot
 
    numOfSamples = plotControlPanel.numOfSamples();
 
    stream.setNumSamples(numOfSamples);
 
    plotControlPanel.setChannelInfoModel(stream.infoModel());
 

	
 
    // init scales
 
    stream.setXAxis(plotControlPanel.xAxisAsIndex(),
 
                    plotControlPanel.xMin(), plotControlPanel.xMax());
 

	
 
    plotMan->setYAxis(plotControlPanel.autoScale(),
 
                      plotControlPanel.yMin(), plotControlPanel.yMax());
 
    plotMan->setXAxis(plotControlPanel.xAxisAsIndex(),
 
                      plotControlPanel.xMin(), plotControlPanel.xMax());
 
    plotMan->setNumOfSamples(numOfSamples);
 
    plotMan->setPlotWidth(plotControlPanel.plotWidth());
 

	
 
    // Init sps (sample per second) counter
 
    spsLabel.setText("0sps");
 
    spsLabel.setToolTip("samples per second (per channel)");
 
    ui->statusBar->addPermanentWidget(&spsLabel);
 
    connect(&sampleCounter, &SampleCounter::spsChanged,
src/plot.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -82,24 +82,29 @@ Plot::Plot(QWidget* parent) :
 
    noChannelText.setBorderRadius(4);
 
    noChannelText.setRenderFlags(Qt::AlignHCenter | Qt::AlignVCenter);
 
    noChannelIndicator.setText(noChannelText);
 
    noChannelIndicator.hide();
 
    noChannelIndicator.attach(this);
 
}
 

	
 
Plot::~Plot()
 
{
 
    if (snapshotOverlay != NULL) delete snapshotOverlay;
 
}
 

	
 
void Plot::setDispChannels(QVector<const StreamChannel*> channels)
 
{
 
    zoomer.setDispChannels(channels);
 
}
 

	
 
void Plot::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax)
 
{
 
    this->isAutoScaled = autoScaled;
 

	
 
    if (!autoScaled)
 
    {
 
        yMin = yAxisMin;
 
        yMax = yAxisMax;
 
    }
 

	
 
    zoomer.zoom(0);
 
    resetAxes();
src/plot.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -41,24 +41,27 @@ class Plot : public QwtPlot
 

	
 
public:
 
    enum ShowSymbols
 
    {
 
        ShowSymbolsAuto,
 
        ShowSymbolsShow,
 
        ShowSymbolsHide
 
    };
 

	
 
    Plot(QWidget* parent = 0);
 
    ~Plot();
 

	
 
    /// Set displayed channels for value tracking (can be null)
 
    void setDispChannels(QVector<const StreamChannel*> channels);
 

	
 
public slots:
 
    void showGrid(bool show = true);
 
    void showMinorGrid(bool show = true);
 
    void showLegend(bool show = true);
 
    void showDemoIndicator(bool show = true);
 
    void showNoChannel(bool show = true);
 
    void unzoom();
 
    void darkBackground(bool enabled = true);
 
    void setYAxis(bool autoScaled, double yMin = 0, double yMax = 1);
 
    void setXAxis(double xMin, double xMax);
 
    void setSymbols(ShowSymbols shown);
 

	
src/plotmanager.cpp
Show inline comments
 
@@ -10,101 +10,98 @@
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <algorithm>
 
#include <QMetaEnum>
 
#include <QtDebug>
 
#include "qwt_symbol.h"
 

	
 
#include "plot.h"
 
#include "plotmanager.h"
 
#include "utils.h"
 
#include "setting_defines.h"
 

	
 
PlotManager::PlotManager(QWidget* plotArea, PlotMenu* menu,
 
                         const Stream* stream, QObject* parent) :
 
    QObject(parent)
 
{
 
    _stream = stream;
 
    construct(plotArea, menu);
 
    _stream = stream;
 
    if (_stream == NULL) return;
 
    if (_stream == nullptr) return;
 

	
 
    // connect to ChannelInfoModel
 
    infoModel = _stream->infoModel();
 
    connect(infoModel, &QAbstractItemModel::dataChanged,
 
                this, &PlotManager::onChannelInfoChanged);
 
    connect(infoModel, &QAbstractItemModel::modelReset,
 
            [this]()
 
            {
 
                onChannelInfoChanged(infoModel->index(0, 0), // start
 
                                     infoModel->index(infoModel->rowCount()-1, 0), // end
 
                                     {}); // roles ignored
 
            });
 

	
 
    connect(stream, &Stream::numChannelsChanged, this, &PlotManager::onNumChannelsChanged);
 
    connect(stream, &Stream::dataAdded, this, &PlotManager::replot);
 

	
 
    // add initial curves if any?
 
    for (unsigned int i = 0; i < stream->numChannels(); i++)
 
    {
 
        addCurve(stream->channel(i)->name(), stream->channel(i)->yData());
 
        addCurve(stream->channel(i)->name(), stream->channel(i)->xData(), stream->channel(i)->yData());
 
    }
 

	
 
}
 

	
 
PlotManager::PlotManager(QWidget* plotArea, PlotMenu* menu,
 
                         Snapshot* snapshot, QObject *parent) :
 
    QObject(parent)
 
{
 
    _stream = nullptr;
 
    construct(plotArea, menu);
 

	
 
    setNumOfSamples(snapshot->numSamples());
 
    setPlotWidth(snapshot->numSamples());
 
    infoModel = snapshot->infoModel();
 

	
 
    for (unsigned ci = 0; ci < snapshot->numChannels(); ci++)
 
    {
 
        addCurve(snapshot->channelName(ci), snapshot->yData[ci]);
 
        addCurve(snapshot->channelName(ci), snapshot->xData[ci], snapshot->yData[ci]);
 
    }
 

	
 
    connect(infoModel, &QAbstractItemModel::dataChanged,
 
            this, &PlotManager::onChannelInfoChanged);
 
}
 

	
 
void PlotManager::construct(QWidget* plotArea, PlotMenu* menu)
 
{
 
    _menu = menu;
 
    _plotArea = plotArea;
 
    _autoScaled = true;
 
    _yMin = 0;
 
    _yMax = 1;
 
    _xAxisAsIndex = true;
 
    isDemoShown = false;
 
    _numOfSamples = 1;
 
    _plotWidth = 1;
 
    showSymbols = Plot::ShowSymbolsAuto;
 
    emptyPlot = NULL;
 

	
 
    // initalize layout and single widget
 
    isMulti = false;
 
    scrollArea = NULL;
 
    setupLayout(isMulti);
 
    addPlotWidget();
 

	
 
    // connect to  menu
 
    connect(menu, &PlotMenu::symbolShowChanged, this, &PlotManager:: setSymbols);
 

	
 
    connect(&menu->showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::showGrid);
 
    connect(&menu->showMinorGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::showMinorGrid);
 
    connect(&menu->darkBackgroundAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::darkBackground);
 
    connect(&menu->showLegendAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::showLegend);
 
@@ -139,25 +136,25 @@ PlotManager::~PlotManager()
 
}
 

	
 
void PlotManager::onNumChannelsChanged(unsigned value)
 
{
 
    unsigned int oldNum = numOfCurves();
 
    unsigned numOfChannels = value;
 

	
 
    if (numOfChannels > oldNum)
 
    {
 
        // add new channels
 
        for (unsigned int i = oldNum; i < numOfChannels; i++)
 
        {
 
            addCurve(_stream->channel(i)->name(), _stream->channel(i)->yData());
 
            addCurve(_stream->channel(i)->name(), _stream->channel(i)->xData(), _stream->channel(i)->yData());
 
        }
 
    }
 
    else if(numOfChannels < oldNum)
 
    {
 
        removeCurves(oldNum - numOfChannels);
 
    }
 

	
 
    replot();
 
}
 

	
 
void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
 
                                       const QModelIndex &bottomRight,
 
@@ -208,58 +205,64 @@ void PlotManager::checkNoVisChannels()
 
                                  [](QwtPlotCurve* c) {return c->isVisible();});
 

	
 
    plotWidgets[0]->showNoChannel(allhidden);
 
    if (isMulti)
 
    {
 
        plotWidgets[0]->showNoChannel(allhidden);
 
        plotWidgets[0]->setVisible(true);
 
    }
 
}
 

	
 
void PlotManager::setMulti(bool enabled)
 
{
 
    if (enabled == isMulti) return;
 

	
 
    isMulti = enabled;
 

	
 
    // detach all curves
 
    for (auto curve : curves)
 
    {
 
        curve->detach();
 
    }
 

	
 
    // remove all widgets
 
    while (plotWidgets.size())
 
    {
 
        delete plotWidgets.takeLast();
 
    }
 

	
 
    // setup new layout
 
    setupLayout(isMulti);
 

	
 
    if (isMulti)
 
    {
 
        // add new widgets and attach
 
        int i = 0;
 
        for (auto curve : curves)
 
        {
 
            auto plot = addPlotWidget();
 
            plot->setVisible(curve->isVisible());
 
            plot->setDispChannels(QVector<const StreamChannel*>(1, _stream->channel(i)));
 
            curve->attach(plot);
 
            i++;
 
        }
 
    }
 
    else
 
    {
 
        // add a single widget
 
        auto plot = addPlotWidget();
 

	
 
        if (_stream != nullptr)
 
        {
 
            plot->setDispChannels(_stream->allChannels());
 
        }
 

	
 
        // attach all curves
 
        for (auto curve : curves)
 
        {
 
            curve->attach(plot);
 
        }
 
    }
 

	
 
    // will skip if no plot widgets exist (can happen during constructor)
 
    if (plotWidgets.length())
 
    {
 
        checkNoVisChannels();
 
    }
 
@@ -323,52 +326,53 @@ Plot* PlotManager::addPlotWidget()
 
    if (_xAxisAsIndex)
 
    {
 
        plot->setXAxis(0, _numOfSamples);
 
    }
 
    else
 
    {
 
        plot->setXAxis(_xMin, _xMax);
 
    }
 

	
 
    return plot;
 
}
 

	
 
void PlotManager::addCurve(QString title, const FrameBuffer* buffer)
 
void PlotManager::addCurve(QString title, const XFrameBuffer* xBuf, const FrameBuffer* yBuf)
 
{
 
    auto curve = new QwtPlotCurve(title);
 
    auto series = new FrameBufferSeries(buffer);
 
    series->setXAxis(_xAxisAsIndex, _xMin, _xMax);
 
    auto series = new FrameBufferSeries(xBuf, yBuf);
 
    curve->setSamples(series);
 
    _addCurve(curve);
 
}
 

	
 
void PlotManager::_addCurve(QwtPlotCurve* curve)
 
{
 
    // store and init the curve
 
    curves.append(curve);
 

	
 
    unsigned index = curves.size()-1;
 
    auto color = infoModel->color(index);
 
    curve->setPen(color);
 

	
 
    // create the plot for the curve if we are on multi display
 
    Plot* plot;
 
    if (isMulti)
 
    {
 
        // create a new plot widget
 
        plot = addPlotWidget();
 
        plot->setDispChannels(QVector<const StreamChannel*>(1, _stream->channel(index)));
 
    }
 
    else
 
    {
 
        plot = plotWidgets[0];
 
        plot->setDispChannels(_stream->allChannels());
 
    }
 

	
 
    // show the curve
 
    curve->attach(plot);
 
    plot->replot();
 
}
 

	
 
void PlotManager::removeCurves(unsigned number)
 
{
 
    for (unsigned i = 0; i < number; i++)
 
    {
 
        if (!curves.isEmpty())
 
@@ -472,28 +476,31 @@ void PlotManager::setYAxis(bool autoScal
 
    _yMax = yAxisMax;
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->setYAxis(autoScaled, yAxisMin, yAxisMax);
 
    }
 
}
 

	
 
void PlotManager::setXAxis(bool asIndex, double xMin, double xMax)
 
{
 
    _xAxisAsIndex = asIndex;
 
    _xMin = xMin;
 
    _xMax = xMax;
 

	
 
    int ci = 0;
 
    for (auto curve : curves)
 
    {
 
        FrameBufferSeries* series = static_cast<FrameBufferSeries*>(curve->data());
 
        series->setXAxis(asIndex, xMin, xMax);
 
        series->setX(_stream->channel(ci)->xData());
 
        ci++;
 
    }
 
    for (auto plot : plotWidgets)
 
    {
 
        if (asIndex)
 
        {
 
            plot->setXAxis(0, _numOfSamples);
 
        }
 
        else
 
        {
 
            plot->setXAxis(xMin, xMax);
 
        }
 
    }
src/plotmanager.h
Show inline comments
 
@@ -32,33 +32,33 @@
 
#include "plot.h"
 
#include "framebufferseries.h"
 
#include "stream.h"
 
#include "snapshot.h"
 
#include "plotmenu.h"
 

	
 
class PlotManager : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit PlotManager(QWidget* plotArea, PlotMenu* menu,
 
                         const Stream* stream = NULL,
 
                         const Stream* stream = nullptr,
 
                         QObject *parent = 0);
 
    explicit PlotManager(QWidget* plotArea, PlotMenu* menu,
 
                         Snapshot* snapshot,
 
                         QObject *parent = 0);
 
    ~PlotManager();
 
    /// Add a new curve with title and buffer. A color is
 
    /// automatically chosen for curve.
 
    void addCurve(QString title, const FrameBuffer* buffer);
 
    void addCurve(QString title, const XFrameBuffer* xBuf, const FrameBuffer* yBuf);
 
    /// Removes curves from the end
 
    void removeCurves(unsigned number);
 
    /// Returns current number of curves known by plot manager
 
    unsigned numOfCurves();
 

	
 
public slots:
 
    /// Enable/Disable multiple plot display
 
    void setMulti(bool enabled);
 
    /// Update all plot widgets
 
    void replot();
 
    /// Enable display of a "DEMO" label on each plot
 
    void showDemoIndicator(bool show = true);
 
@@ -73,25 +73,25 @@ public slots:
 
    /// Maximum width of X axis (limit of hscroll)
 
    void setPlotWidth(double width);
 

	
 
private:
 
    bool isMulti;
 
    QWidget* _plotArea;
 
    PlotMenu* _menu;
 
    QVBoxLayout* layout; ///< layout of the `plotArea`
 
    QScrollArea* scrollArea;
 
    QList<QwtPlotCurve*> curves;
 
    QList<Plot*> plotWidgets;
 
    Plot* emptyPlot;  ///< for displaying when all channels are hidden
 
    const Stream* _stream;       ///< attached stream, can be `NULL`
 
    const Stream* _stream;       ///< attached stream, can be `nullptr`
 
    const ChannelInfoModel* infoModel;
 
    bool isDemoShown;
 
    bool _autoScaled;
 
    double _yMin;
 
    double _yMax;
 
    bool _xAxisAsIndex;
 
    double _xMin;
 
    double _xMax;
 
    unsigned _numOfSamples;
 
    double _plotWidth;
 
    Plot::ShowSymbols showSymbols;
 

	
src/snapshot.h
Show inline comments
 
@@ -19,37 +19,39 @@
 

	
 
#ifndef SNAPSHOT_H
 
#define SNAPSHOT_H
 

	
 
#include <QObject>
 
#include <QAction>
 
#include <QVector>
 
#include <QString>
 
#include <QStringList>
 

	
 
#include "channelinfomodel.h"
 
#include "readonlybuffer.h"
 
#include "indexbuffer.h"
 

	
 
class SnapshotView;
 
class MainWindow;
 

	
 
class Snapshot : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved = false);
 
    ~Snapshot();
 

	
 
    // TODO: yData of snapshot shouldn't be public, preferable should be handled in constructor
 
    // TODO: yData and xData of snapshot shouldn't be public, preferable should be handled in constructor
 
    QVector<IndexBuffer*> xData;
 
    QVector<ReadOnlyBuffer*> yData;
 
    QAction* showAction();
 
    QAction* deleteAction();
 

	
 
    QString name();
 
    QString displayName(); ///< `name()` plus '*' if snapshot is not saved
 
    unsigned numChannels() const; ///< number of channels in this snapshot
 
    unsigned numSamples() const;  ///< number of samples in every channel
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 
    void setName(QString name);
 
    QString channelName(unsigned channel);
src/snapshotmanager.cpp
Show inline comments
 
@@ -62,24 +62,25 @@ SnapshotManager::~SnapshotManager()
 
    {
 
        delete snapshot;
 
    }
 
}
 

	
 
Snapshot* SnapshotManager::makeSnapshot() const
 
{
 
    QString name = QTime::currentTime().toString("'Snapshot ['HH:mm:ss']'");
 
    auto snapshot = new Snapshot(_mainWindow, name, *(_stream->infoModel()));
 

	
 
    for (unsigned ci = 0; ci < _stream->numChannels(); ci++)
 
    {
 
        snapshot->xData.append(new IndexBuffer(_stream->numSamples()));
 
        snapshot->yData.append(new ReadOnlyBuffer(_stream->channel(ci)->yData()));
 
    }
 

	
 
    return snapshot;
 
}
 

	
 
void SnapshotManager::takeSnapshot()
 
{
 
    addSnapshot(makeSnapshot());
 
}
 

	
 
void SnapshotManager::addSnapshot(Snapshot* snapshot, bool update_menu)
 
@@ -185,24 +186,25 @@ void SnapshotManager::loadSnapshotFromFi
 
            data[ci].append(y);
 
        }
 
        lineNum++;
 
    }
 

	
 
    // create snapshot
 
    auto snapshot = new Snapshot(
 
        _mainWindow, QFileInfo(fileName).baseName(),
 
        ChannelInfoModel(channelNames), true);
 

	
 
    for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    {
 
        snapshot->xData.append(new IndexBuffer(data[ci].size()));
 
        snapshot->yData.append(new ReadOnlyBuffer(data[ci].data(), data[ci].size()));
 
    }
 

	
 
    addSnapshot(snapshot, false);
 
}
 

	
 
QMenu* SnapshotManager::menu()
 
{
 
    return &_menu;
 
}
 

	
 
QAction* SnapshotManager::takeSnapshotAction()
src/stream.cpp
Show inline comments
 
@@ -11,40 +11,46 @@
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "stream.h"
 
#include "ringbuffer.h"
 
#include "indexbuffer.h"
 
#include "linindexbuffer.h"
 

	
 
Stream::Stream(unsigned nc, bool x, unsigned ns) :
 
    _infoModel(nc)
 
{
 
    _numSamples = ns;
 
    _paused = false;
 

	
 
    xAsIndex = true;
 
    xMin = 0;
 
    xMax = 1;
 

	
 
    // create xdata buffer
 
    _hasx = x;
 
    if (x)
 
    {
 
        xData = new RingBuffer(ns);
 
        // TODO: implement XRingBuffer (binary search)
 
        Q_ASSERT(false);
 
    }
 
    else
 
    {
 
        xData = new IndexBuffer(ns);
 
        xData = makeXBuffer();
 
    }
 

	
 
    // create channels
 
    for (unsigned i = 0; i < nc; i++)
 
    {
 
        auto c = new StreamChannel(i, xData, new RingBuffer(ns), &_infoModel);
 
        channels.append(c);
 
    }
 
}
 

	
 
Stream::~Stream()
 
{
 
@@ -72,24 +78,34 @@ unsigned Stream::numSamples() const
 

	
 
const StreamChannel* Stream::channel(unsigned index) const
 
{
 
    Q_ASSERT(index < numChannels());
 
    return channels[index];
 
}
 

	
 
StreamChannel* Stream::channel(unsigned index)
 
{
 
    return const_cast<StreamChannel*>(static_cast<const Stream&>(*this).channel(index));
 
}
 

	
 
QVector<const StreamChannel*> Stream::allChannels() const
 
{
 
    QVector<const StreamChannel*> result(numChannels());
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        result[ci] = channel(ci);
 
    }
 
    return result;
 
}
 

	
 
const ChannelInfoModel* Stream::infoModel() const
 
{
 
    return &_infoModel;
 
}
 

	
 
ChannelInfoModel* Stream::infoModel()
 
{
 
    return const_cast<ChannelInfoModel*>(static_cast<const Stream&>(*this).infoModel());
 
}
 

	
 
void Stream::setNumChannels(unsigned nc, bool x)
 
{
 
@@ -109,48 +125,61 @@ void Stream::setNumChannels(unsigned nc,
 
    {
 
        for (unsigned i = oldNum-1; i > nc-1; i--)
 
        {
 
            delete channels.takeLast();
 
        }
 
    }
 

	
 
    // change the xdata
 
    if (x != _hasx)
 
    {
 
        if (x)
 
        {
 
            xData = new RingBuffer(_numSamples);
 
            // TODO: implement XRingBuffer (binary search)
 
            Q_ASSERT(false);
 
        }
 
        else
 
        {
 
            xData = new IndexBuffer(_numSamples);
 
            xData = makeXBuffer();
 
        }
 

	
 
        for (auto c : channels)
 
        {
 
            c->setX(xData);
 
        }
 
        _hasx = x;
 
    }
 

	
 
    if (nc != oldNum)
 
    {
 
        _infoModel.setNumOfChannels(nc);
 
        // TODO: how about X change?
 
        emit numChannelsChanged(nc);
 
    }
 

	
 
    Sink::setNumChannels(nc, x);
 
}
 

	
 
XFrameBuffer* Stream::makeXBuffer() const
 
{
 
    if (xAsIndex)
 
    {
 
        return new IndexBuffer(_numSamples);
 
    }
 
    else
 
    {
 
        return new LinIndexBuffer(_numSamples, xMin, xMax);
 
    }
 
}
 

	
 
const SamplePack* Stream::applyGainOffset(const SamplePack& pack) const
 
{
 
    Q_ASSERT(infoModel()->gainOrOffsetEn());
 

	
 
    SamplePack* mPack = new SamplePack(pack);
 
    unsigned ns = pack.numSamples();
 

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        // TODO: we could use some kind of map (int32, int64 would suffice) to speed things up
 
        bool gainEn = infoModel()->gainEn(ci);
 
        bool offsetEn = infoModel()->offsetEn(ci);
 
@@ -182,25 +211,27 @@ const SamplePack* Stream::applyGainOffse
 
}
 

	
 
void Stream::feedIn(const SamplePack& pack)
 
{
 
    Q_ASSERT(pack.numChannels() == numChannels() &&
 
             pack.hasX() == hasX());
 

	
 
    if (_paused) return;
 

	
 
    unsigned ns = pack.numSamples();
 
    if (_hasx)
 
    {
 
        static_cast<RingBuffer*>(xData)->addSamples(pack.xData(), ns);
 
        // TODO: implement XRingBuffer (binary search)
 
        Q_ASSERT(false);
 
        // static_cast<RingBuffer*>(xData)->addSamples(pack.xData(), ns);
 
    }
 

	
 
    // modified pack that gain and offset is applied to
 
    const SamplePack* mPack = nullptr;
 
    if (infoModel()->gainOrOffsetEn())
 
        mPack = applyGainOffset(pack);
 

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        auto buf = static_cast<RingBuffer*>(channels[ci]->yData());
 
        double* data = (mPack == nullptr) ? pack.data(ci) : mPack->data(ci);
 
        buf->addSamples(data, ns);
 
@@ -228,21 +259,39 @@ void Stream::clear()
 
void Stream::setNumSamples(unsigned value)
 
{
 
    if (value == _numSamples) return;
 
    _numSamples = value;
 

	
 
    xData->resize(value);
 
    for (auto c : channels)
 
    {
 
        static_cast<RingBuffer*>(c->yData())->resize(value);
 
    }
 
}
 

	
 
void Stream::setXAxis(bool asIndex, double min, double max)
 
{
 
    xAsIndex = asIndex;
 
    xMin = min;
 
    xMax = max;
 

	
 
    // Note that x axis scaling is ignored when X is provided from source as data
 
    // TODO: assert (UI options for x axis should be disabled)
 
    if (!hasX())
 
    {
 
        xData = makeXBuffer();
 
        for (auto c : channels)
 
        {
 
            c->setX(xData);
 
        }
 
    }
 
}
 

	
 
void Stream::saveSettings(QSettings* settings) const
 
{
 
    _infoModel.saveSettings(settings);
 
}
 

	
 
void Stream::loadSettings(QSettings* settings)
 
{
 
    _infoModel.loadSettings(settings);
 
}
src/stream.h
Show inline comments
 
@@ -39,79 +39,88 @@
 
 * connected to a `Device` source.
 
 */
 
class Stream : public QObject, public Sink
 
{
 
    Q_OBJECT
 

	
 
public:
 
    /**
 
     * @param nc number of channels
 
     * @param x has X data input
 
     * @param ns number of samples
 
     */
 
    Stream(unsigned nc = 0, bool x = false, unsigned ns = 0);
 
    Stream(unsigned nc = 1, bool x = false, unsigned ns = 2);
 
    ~Stream();
 

	
 
    // implementations for `Source`
 
    virtual bool hasX() const;
 
    virtual unsigned numChannels() const;
 
    bool hasX() const;
 
    unsigned numChannels() const;
 

	
 
    unsigned numSamples() const;
 
    const StreamChannel* channel(unsigned index) const;
 
    StreamChannel* channel(unsigned index);
 
    QVector<const StreamChannel*> allChannels() const;
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 

	
 
    /// Saves channel information
 
    void saveSettings(QSettings* settings) const;
 
    /// Load channel information
 
    void loadSettings(QSettings* settings);
 

	
 
protected:
 
    // implementations for `Sink`
 
    virtual void setNumChannels(unsigned nc, bool x);
 
    virtual void feedIn(const SamplePack& pack);
 

	
 
signals:
 
    void numChannelsChanged(unsigned value);
 
    void numSamplesChanged(unsigned value);
 
    void channelAdded(const StreamChannel* chan);
 
    void channelNameChanged(unsigned channel, QString name); // TODO: does it stay?
 
    void dataAdded(); ///< emitted when data added to channel man.
 

	
 
public slots:
 
    // TODO: these won't be public
 
    // void setNumChannels(unsigned number);
 
    /// Change number of samples (buffer size)
 
    void setNumSamples(unsigned value);
 

	
 
    /// Change X axis style
 
    /// @note Ignored when X is provided by source (hasX == true)
 
    void setXAxis(bool asIndex, double min, double max);
 

	
 
    /// When paused data feed is ignored
 
    void pause(bool paused);
 

	
 
    /// Clears buffer data (fills with 0)
 
    void clear();
 

	
 
private:
 
    unsigned _numSamples;
 
    bool _paused;
 

	
 
    bool _hasx;
 
    ResizableBuffer* xData;
 
    XFrameBuffer* xData;
 
    QList<StreamChannel*> channels;
 

	
 
    ChannelInfoModel _infoModel;
 

	
 
    bool xAsIndex;
 
    double xMin, xMax;
 

	
 
    /**
 
     * Applies gain and offset to given pack.
 
     *
 
     * Caller is responsible for deleting returned `SamplePack`.
 
     *
 
     * @note Should be called only when gain or offset is enabled. Guard with
 
     * `ChannelInfoModel::gainOrOffsetEn()`.
 
     *
 
     * @param pack input data
 
     * @return modified data
 
     */
 
    const SamplePack* applyGainOffset(const SamplePack& pack) const;
 

	
 
    /// Returns a new virtual X buffer for settings
 
    XFrameBuffer* makeXBuffer() const;
 
};
 

	
 

	
 
#endif // STREAM_H
src/streamchannel.cpp
Show inline comments
 
@@ -8,37 +8,67 @@
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <limits>
 
#include "streamchannel.h"
 

	
 
StreamChannel::StreamChannel(unsigned i, const FrameBuffer* x,
 
StreamChannel::StreamChannel(unsigned i, const XFrameBuffer* x,
 
              FrameBuffer* y, ChannelInfoModel* info)
 
{
 
    _index = i;
 
    _x = x;
 
    _y = y;
 
    _info = info;
 
}
 

	
 
StreamChannel::~StreamChannel()
 
{
 
    delete _y;
 
}
 

	
 
unsigned StreamChannel::index() const {return _index;}
 
QString StreamChannel::name() const {return _info->name(_index);};
 
QColor StreamChannel::color() const {return _info->color(_index);};
 
bool StreamChannel::visible() const {return _info->isVisible(_index);};
 
const FrameBuffer* StreamChannel::xData() const {return _x;}
 
const XFrameBuffer* StreamChannel::xData() const {return _x;}
 
const FrameBuffer* StreamChannel::yData() const {return _y;}
 
FrameBuffer* StreamChannel::yData() {return _y;}
 
const ChannelInfoModel* StreamChannel::info() const {return _info;}
 
void StreamChannel::setX(const FrameBuffer* x) {_x = x;};
 
void StreamChannel::setX(const XFrameBuffer* x) {_x = x;};
 

	
 
double StreamChannel::findValue(double x) const
 
{
 
    int index = _x->findIndex(x);
 
    Q_ASSERT(index < (int) _x->size());
 

	
 
    if (index >= 0)
 
    {
 
        // can't do estimation for last sample
 
        if (index == (int) _x->size() - 1)
 
        {
 
            return _y->sample(index);
 
        }
 
        else
 
        {
 
            // calculate middle of the line
 
            double prev_x = _x->sample(index);
 
            double next_x = _x->sample(index+1);
 
            double ratio = (x - prev_x) / (next_x - prev_x);
 
            double prev_y = _y->sample(index);
 
            double next_y = _y->sample(index+1);
 
            return ratio * (next_y - prev_y) + prev_y;
 
        }
 
    }
 
    else
 
    {
 
        return std::numeric_limits<double>::quiet_NaN();
 
    }
 
}
src/streamchannel.h
Show inline comments
 
@@ -26,35 +26,43 @@
 
class StreamChannel
 
{
 
public:
 
    /**
 
     * Creates a stream channel.
 
     *
 
     * @param i index of the channel
 
     * @param x x axis buffer
 
     * @param y data buffer of this channel, takes ownership
 
     * @param info channel info model
 
     */
 
    StreamChannel(unsigned i,
 
                  const FrameBuffer* x,
 
                  const XFrameBuffer* x,
 
                  FrameBuffer* y,
 
                  ChannelInfoModel* info);
 
    ~StreamChannel();
 

	
 
    unsigned index() const;
 
    QString name() const;
 
    QColor color() const;
 
    bool visible() const;
 
    const FrameBuffer* xData() const;
 
    const XFrameBuffer* xData() const;
 
    FrameBuffer* yData();
 
    const FrameBuffer* yData() const;
 
    const ChannelInfoModel* info() const;
 
    void setX(const FrameBuffer* x);
 
    void setX(const XFrameBuffer* x);
 

	
 
    /**
 
     * Returns sample value for `x`.
 
     *
 
     * If `x` is out of range `NaN` is returned. A calculated (linear)
 
     * value is returned when `x` is in between two data points.
 
     */
 
    double findValue(double x) const;
 

	
 
private:
 
    unsigned _index;
 
    const FrameBuffer* _x;
 
    const XFrameBuffer* _x;
 
    FrameBuffer* _y;
 
    ChannelInfoModel* _info;
 
};
 

	
 
#endif // STREAMCHANNEL_H
src/zoomer.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "zoomer.h"
 
#include <qwt_plot.h>
 
#include <QtDebug>
 
#include <QPen>
 
#include <QMouseEvent>
 
#include <QtMath>
 

	
 
#include <QMouseEvent>
 
static const int VALUE_POINT_DIAM = 4;
 
static const int VALUE_TEXT_MARGIN = VALUE_POINT_DIAM + 2;
 

	
 
Zoomer::Zoomer(QWidget* widget, bool doReplot) :
 
    ScrollZoomer(widget)
 
{
 
    is_panning = false;
 

	
 
    setTrackerMode(AlwaysOn);
 

	
 
    // set corner widget between the scrollbars with default background color
 
    auto cornerWidget = new QWidget();
 
    auto bgColor = cornerWidget->palette().color(QPalette::Window).name();
 
    auto styleSheet = QString("background-color:%1;").arg(bgColor);
 
@@ -50,24 +53,29 @@ void Zoomer::zoom(int up)
 

	
 
void Zoomer::zoom( const QRectF & rect)
 
{
 
    // set the zoom base when user zooms in to first level
 
    if (zoomRectIndex() == 0)
 
    {
 
        this->setZoomBase(false);
 
    }
 

	
 
    ScrollZoomer::zoom(rect);
 
}
 

	
 
void Zoomer::setDispChannels(QVector<const StreamChannel*> channels)
 
{
 
    dispChannels = channels;
 
}
 

	
 
QwtText Zoomer::trackerTextF(const QPointF& pos) const
 
{
 
    QwtText b = ScrollZoomer::trackerTextF(pos);
 

	
 
    const QPolygon pa = selection();
 
    if (!isActive() || pa.count() < 2)
 
    {
 
        return b;
 
    }
 

	
 
    const QRectF rect = invTransform(QRect(pa.first(), pa.last()).normalized());
 

	
 
@@ -93,24 +101,114 @@ void Zoomer::drawRubberBand(QPainter* pa
 

	
 
QRegion Zoomer::rubberBandMask() const
 
{
 
    const QPolygon pa = selection();
 
    if (pa.count() < 2)
 
    {
 
        return QRegion();
 
    }
 
    const QRect r = QRect(pa.first(), pa.last()).normalized().adjusted(0, 0, 1, 1);
 
    return QRegion(r);
 
}
 

	
 
void Zoomer::drawTracker(QPainter* painter) const
 
{
 
    if (isActive())
 
    {
 
        QwtPlotZoomer::drawTracker(painter);
 
    }
 
    else if (dispChannels.length())
 
    {
 
        drawValues(painter);
 
    }
 
}
 

	
 
void Zoomer::drawValues(QPainter* painter) const
 
{
 
    painter->save();
 

	
 
    double x = invTransform(trackerPosition()).x();
 
    auto values = findValues(x);
 

	
 
    // draw vertical line
 
    auto linePen = rubberBandPen();
 
    linePen.setStyle(Qt::DotLine);
 
    painter->setPen(linePen);
 
    const QRect pRect = pickArea().boundingRect().toRect();
 
    int px = trackerPosition().x();
 
    painter->drawLine(px, pRect.top(), px, pRect.bottom());
 

	
 
    // draw sample values
 
    for (int ci = 0; ci < values.size(); ci++)
 
    {
 
        if (!dispChannels[ci]->visible()) continue;
 

	
 
        double val = values[ci];
 
        if (!std::isnan(val))
 
        {
 
            auto p = transform(QPointF(x, val));
 

	
 
            painter->setBrush(dispChannels[ci]->color());
 
            painter->setPen(Qt::NoPen);
 
            painter->drawEllipse(p, VALUE_POINT_DIAM, VALUE_POINT_DIAM);
 

	
 
            painter->setPen(rubberBandPen());
 
            // We give a very small (1x1) rectangle but disable clipping
 
            painter->drawText(QRectF(p.x() + VALUE_TEXT_MARGIN, p.y(), 1, 1),
 
                              Qt::AlignVCenter | Qt::TextDontClip,
 
                              QString("%1").arg(val));
 
        }
 
    }
 

	
 
    painter->restore();
 
}
 

	
 
QVector<double> Zoomer::findValues(double x) const
 
{
 
    unsigned nc = dispChannels.length();
 
    QVector<double> result(nc);
 
    for (unsigned ci = 0; ci < nc; ci++)
 
    {
 
        if (dispChannels[ci]->visible())
 
        {
 
            result[ci] = dispChannels[ci]->findValue(x);
 
        }
 

	
 
    }
 
    return result;
 
}
 

	
 
QRect Zoomer::trackerRect(const QFont& font) const
 
{
 
    if (isActive())
 
    {
 
        return QwtPlotZoomer::trackerRect(font);
 
    }
 
    else
 
    {
 
        return valueTrackerRect(font);
 
    }
 
}
 

	
 
QRect Zoomer::valueTrackerRect(const QFont& font) const
 
{
 
    // TODO: consider using actual tracker values for width calculation
 
    const int textWidth = qCeil(QwtText("-8.8888888").textSize(font).width());
 
    const int width = textWidth + VALUE_POINT_DIAM + VALUE_TEXT_MARGIN;
 
    const int x = trackerPosition().x() - VALUE_POINT_DIAM;
 
    const auto pickRect = pickArea().boundingRect();
 

	
 
    return QRect(x, pickRect.y(), width, pickRect.height());
 
}
 

	
 
void Zoomer::widgetMousePressEvent(QMouseEvent* mouseEvent)
 
{
 
    if (mouseEvent->modifiers() & Qt::ControlModifier)
 
    {
 
        is_panning = true;
 
        parentWidget()->setCursor(Qt::ClosedHandCursor);
 
        pan_point = invTransform(mouseEvent->pos());
 
    }
 
    else
 
    {
 
        ScrollZoomer::widgetMousePressEvent(mouseEvent);
 
    }
src/zoomer.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef ZOOMER_H
 
#define ZOOMER_H
 

	
 
#include <scrollzoomer.h>
 
#include <QVector>
 
#include <QRect>
 

	
 
#include "scrollzoomer.h"
 
#include "streamchannel.h"
 

	
 
class Zoomer : public ScrollZoomer
 
{
 
    Q_OBJECT
 

	
 
public:
 
    Zoomer(QWidget *, bool doReplot=true);
 
    Zoomer(QWidget*, bool doReplot=true);
 
    void zoom(int up);
 
    void zoom( const QRectF & );
 
    void zoom(const QRectF&);
 
    /// Set displayed channels for value tracking (can be null)
 
    void setDispChannels(QVector<const StreamChannel*> channels);
 

	
 
signals:
 
    void unzoomed();
 

	
 
protected:
 
    /// Re-implemented to display selection size in the tracker text.
 
    QwtText trackerTextF(const QPointF &pos) const;
 
    QwtText trackerTextF(const QPointF &pos) const override;
 
    /// Re-implemented for sample value tracker
 
    QRect trackerRect(const QFont&) const override;
 
    /// Re-implemented for alpha background
 
    void drawRubberBand(QPainter* painter) const;
 
    void drawRubberBand(QPainter* painter) const override;
 
    /// Re-implemented to draw sample values
 
    void drawTracker(QPainter* painter) const override;
 
    /// Re-implemented for alpha background (masking is basically disabled)
 
    QRegion rubberBandMask() const;
 
    /// Overloaded for panning
 
    void widgetMousePressEvent(QMouseEvent* mouseEvent);
 
    /// Overloaded for panning
 
    void widgetMouseReleaseEvent(QMouseEvent* mouseEvent);
 
    /// Overloaded for panning
 
    void widgetMouseMoveEvent(QMouseEvent* mouseEvent);
 

	
 
private:
 
    bool is_panning;
 
    QPointF pan_point;
 
    /// displayed channels for value tracking
 
    QVector<const StreamChannel*> dispChannels;
 

	
 
    /// Draw sample values
 
    void drawValues(QPainter* painter) const;
 
    /// Find sample values for given X value
 
    QVector<double> findValues(double x) const;
 
    /// Returns trackerRect for value tracker
 
    QRect valueTrackerRect(const QFont& font) const;
 
};
 

	
 
#endif // ZOOMER_H
tests/test.cpp
Show inline comments
 
@@ -212,24 +212,32 @@ TEST_CASE("LinIndexBuffer", "[memory, bu
 

	
 
    l = buf.limits();
 
    REQUIRE(l.start == 0.);
 
    REQUIRE(l.end == 3.0);
 

	
 
    buf.setLimits({-5., 5.});
 
    l = buf.limits();
 
    REQUIRE(l.start == -5.0);
 
    REQUIRE(l.end == 5.0);
 

	
 
    REQUIRE(buf.sample(0) == -5.0);
 
    REQUIRE(buf.sample(19) == 5.0);
 

	
 
    buf.resize(10);
 
    buf.setLimits({0., 3.});
 
    REQUIRE(buf.findIndex(0.01) == 0);
 
    REQUIRE(buf.findIndex(1.51) == 4);
 
    REQUIRE(buf.findIndex(2.99) == 8);
 
    REQUIRE(buf.findIndex(3.01) == XFrameBuffer::OUT_OF_RANGE);
 
    REQUIRE(buf.findIndex(-0.01) == XFrameBuffer::OUT_OF_RANGE);
 
}
 

	
 
TEST_CASE("RingBuffer sizing", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 

	
 
    REQUIRE(buf.size() == 10);
 

	
 
    buf.resize(5);
 
    REQUIRE(buf.size() == 5);
 

	
 
    buf.resize(15);
tests/test_stream.cpp
Show inline comments
 
@@ -18,35 +18,35 @@
 
*/
 

	
 
#include "stream.h"
 

	
 
#include "catch.hpp"
 
#include "test_helpers.h"
 

	
 
TEST_CASE("construction of stream with default values", "[memory, stream]")
 
{
 
    // default values are an empty stream with no channels
 
    Stream s;
 

	
 
    REQUIRE(s.numChannels() == 0);
 
    REQUIRE(s.numChannels() == 1);
 
    REQUIRE(!s.hasX());
 
    REQUIRE(s.numSamples() == 0);
 
    REQUIRE(s.numSamples() == 2);
 
}
 

	
 
TEST_CASE("construction of stream with parameters", "[memory, stream]")
 
{
 
    Stream s(4, true, 100);
 
    Stream s(4, false, 100);
 

	
 
    REQUIRE(s.numChannels() == 4);
 
    REQUIRE(s.hasX());
 
    REQUIRE(!s.hasX());
 
    REQUIRE(s.numSamples() == 100);
 

	
 
    for (unsigned i = 0; i < 4; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
    }
 
}
 

	
 
TEST_CASE("changing stream number of channels via sink", "[memory, stream, sink]")
 
{
 
@@ -55,36 +55,39 @@ TEST_CASE("changing stream number of cha
 
    so.connectSink(&s);
 

	
 
    // nc=3, x= false
 
    REQUIRE(s.numChannels() == 3);
 
    REQUIRE(!s.hasX());
 
    for (unsigned i = 0; i < 3; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
    }
 

	
 
// TODO: enable test when `Stream` supports X channel
 
#if 0
 
    // increase nc value, add X
 
    so._setNumChannels(5, true);
 

	
 
    REQUIRE(s.numChannels() == 5);
 
    REQUIRE(s.hasX());
 

	
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
    }
 
#endif
 

	
 
    // reduce nc value, remove X
 
    so._setNumChannels(1, false);
 

	
 
    REQUIRE(s.numChannels() == 1);
 
    REQUIRE(!s.hasX());
 

	
 
    for (unsigned i = 0; i < 1; i++)
 
    {
 
        const StreamChannel* c = s.channel(i);
 
        REQUIRE(c != NULL);
 
        REQUIRE(c->index() == i);
 
@@ -118,45 +121,47 @@ TEST_CASE("adding data to a stream with 
 

	
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
        }
 
        for (unsigned i = 5; i < 10; i++)
 
        {
 
            REQUIRE(y->sample(i) == i-5);
 
        }
 
    }
 
}
 

	
 
// TODO: enable test when `Stream` supports X channel
 
#if 0
 
TEST_CASE("adding data to a stream with X", "[memory, stream, data, sink]")
 
{
 
    Stream s(3, true, 10);
 
    Stream s(3, false, 10);
 

	
 
    // prepare data
 
    SamplePack pack(5, 3, true);
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            pack.data(ci)[i] = i;
 
        }
 
    }
 
    // x data
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        pack.xData()[i] = i+10;
 
    }
 

	
 
    TestSource so(3, true);
 
    so.connectSink(&s);
 
    REQUIRE_THROWS(so.connectSink(&s));
 

	
 
    // test
 
    so._feed(pack);
 

	
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        const StreamChannel* c = s.channel(ci);
 
        const FrameBuffer* y = c->yData();
 

	
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
@@ -169,24 +174,25 @@ TEST_CASE("adding data to a stream with 
 

	
 
    // check x
 
    const FrameBuffer* x = s.channel(0)->xData();
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(x->sample(i) == 0);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(x->sample(i) == (i-5)+10);
 
    }
 
}
 
#endif
 

	
 
TEST_CASE("paused stream shouldn't store data", "[memory, stream, pause]")
 
{
 
    Stream s(3, false, 10);
 

	
 
    // prepare data
 
    SamplePack pack(5, 3, false);
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            pack.data(ci)[i] = i;
0 comments (0 inline, 0 general)