diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -11,3 +11,4 @@ fd5f1eb480ec372b49df58b497458de05c30057c
9c9a11cd15fd094e2b2b65dc51805fd8fd1d2460 v0.8.1
4cf9a1ee1f107a38e03dbe17c4f2882c43d827c9 v0.9.0
ef003f7af8f37f760c22dae776f5ff8e1b526deb v0.9.1
+7ce85527f3f2f6d19afc0401d2ac723f6c072481 v0.10.0
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
#
-# Copyright © 2017 Hasan Yavuz Özderya
+# Copyright © 2018 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -52,10 +52,18 @@ else ()
find_package(QtColorWidgets REQUIRED)
endif ()
+set(BUILD_LEDWIDGET true CACHE BOOL "Download and build LedWidget automatically.")
+if (BUILD_LEDWIDGET)
+ include(BuildLedWidget)
+else (BUILD_LEDWIDGET)
+ include(FindLedWidget)
+endif (BUILD_LEDWIDGET)
+
# includes
include_directories("./src"
${QWT_INCLUDE_DIR}
${QTCOLORWIDGETS_INCLUDE_DIRS}
+ ${LEDWIDGET_INCLUDE_DIR}
)
# flags
@@ -77,6 +85,8 @@ qt5_wrap_ui(UI_FILES
src/binarystreamreadersettings.ui
src/asciireadersettings.ui
src/framedreadersettings.ui
+ src/demoreadersettings.ui
+ src/updatecheckdialog.ui
)
if (WIN32)
@@ -94,7 +104,6 @@ add_executable(${PROGRAM_NAME} WIN32
src/scrollzoomer.cpp
src/scrollbar.cpp
src/hidabletabwidget.cpp
- src/framebuffer.cpp
src/scalepicker.cpp
src/scalezoomer.cpp
src/portlist.cpp
@@ -111,8 +120,13 @@ add_executable(${PROGRAM_NAME} WIN32
src/datarecorder.cpp
src/tooltipfilter.cpp
src/sneakylineedit.cpp
- src/channelmanager.cpp
+ src/stream.cpp
+ src/streamchannel.cpp
src/channelinfomodel.cpp
+ src/ringbuffer.cpp
+ src/ringbuffer.cpp
+ src/indexbuffer.cpp
+ src/readonlybuffer.cpp
src/framebufferseries.cpp
src/numberformatbox.cpp
src/endiannessbox.cpp
@@ -122,10 +136,22 @@ add_executable(${PROGRAM_NAME} WIN32
src/asciireader.cpp
src/asciireadersettings.cpp
src/demoreader.cpp
+ src/demoreadersettings.cpp
src/framedreader.cpp
src/framedreadersettings.cpp
src/plotmanager.cpp
+ src/plotmenu.cpp
+ src/barplot.cpp
+ src/barchart.cpp
+ src/barscaledraw.cpp
src/numberformat.cpp
+ src/updatechecker.cpp
+ src/versionnumber.cpp
+ src/updatecheckdialog.cpp
+ src/samplepack.cpp
+ src/source.cpp
+ src/sink.cpp
+ src/samplecounter.cpp
misc/windows_icon.rc
${UI_FILES}
${RES_FILES}
@@ -135,8 +161,9 @@ add_executable(${PROGRAM_NAME} WIN32
target_link_libraries(${PROGRAM_NAME}
${QWT_LIBRARY}
${QTCOLORWIDGETS_LIBRARIES}
+ ${LEDWIDGET_LIBRARY}
)
-qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort)
+qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Network)
if (BUILD_QWT)
add_dependencies(${PROGRAM_NAME} QWT)
@@ -146,6 +173,11 @@ if (BUILD_QTCOLORWIDGETS)
add_dependencies(${PROGRAM_NAME} QCW)
endif ()
+if (BUILD_LEDWIDGET)
+ add_dependencies(${PROGRAM_NAME} LEDW)
+endif (BUILD_LEDWIDGET)
+
+
# set compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
@@ -162,13 +194,16 @@ else()
endif()
# default version
-set(VERSION_STRING "0.9.1")
+set(VERSION_STRING "0.10.0")
set(VERSION_REVISION "0")
# get revision number from mercurial and parse version string
include(GetVersion)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_STRING=\\\"${VERSION_STRING}\\\" ")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MAJOR=${VERSION_MAJOR} ")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MINOR=${VERSION_MINOR} ")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_PATCH=${VERSION_PATCH} ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_REVISION=\\\"${VERSION_REVISION}\\\" ")
# add make run target
diff --git a/cmake/modules/BuildLedWidget.cmake b/cmake/modules/BuildLedWidget.cmake
new file mode 100644
--- /dev/null
+++ b/cmake/modules/BuildLedWidget.cmake
@@ -0,0 +1,30 @@
+#
+# Copyright © 2017 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 .
+#
+
+include(ExternalProject)
+
+ExternalProject_Add(LEDW
+ PREFIX ledw
+ HG_REPOSITORY https://bitbucket.org/hyOzd/ledwidget
+ UPDATE_COMMAND ""
+ INSTALL_COMMAND "")
+
+ExternalProject_Get_Property(LEDW binary_dir source_dir)
+set(LEDWIDGET_INCLUDE_DIR ${source_dir}/src)
+set(LEDWIDGET_LIBRARY ${binary_dir}/libledwidget.a)
diff --git a/cmake/modules/BuildLinuxAppImage.cmake b/cmake/modules/BuildLinuxAppImage.cmake
--- a/cmake/modules/BuildLinuxAppImage.cmake
+++ b/cmake/modules/BuildLinuxAppImage.cmake
@@ -1,7 +1,11 @@
# Based on: https://github.com/mhoeher/opentodolist
+#
+# Note: we extract linuxdeployqt appimage so that it can run in docker, that's
+# because fuse doesn't work in docker.
-set(LINUXDEPLOYQT_URL "https://github.com/probonopd/linuxdeployqt/releases/download/4/linuxdeployqt-4-x86_64.AppImage")
-set(LINUXDEPLOYQT_TOOL ${CMAKE_CURRENT_BINARY_DIR}/linuxdeployqt-4-x86_64.AppImage)
+set(LINUXDEPLOYQT_URL "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage")
+set(LINUXDEPLOYQT_APPIMAGE ${CMAKE_CURRENT_BINARY_DIR}/linuxdeployqt-continuous-x86_64.AppImage)
+set(LINUXDEPLOYQT_TOOL ${CMAKE_CURRENT_BINARY_DIR}/squashfs-root/AppRun)
set(APPIMAGE_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PROGRAM_NAME}-${VERSION_STRING}-${CMAKE_HOST_SYSTEM_PROCESSOR})
@@ -11,7 +15,9 @@ add_custom_command(
COMMAND
wget ${LINUXDEPLOYQT_URL}
COMMAND
- chmod a+x ${LINUXDEPLOYQT_TOOL})
+ chmod a+x ${LINUXDEPLOYQT_APPIMAGE}
+ COMMAND
+ ${LINUXDEPLOYQT_APPIMAGE} --appimage-extract)
add_custom_target(
appimage
diff --git a/cmake/modules/BuildQColorWidgets.cmake b/cmake/modules/BuildQColorWidgets.cmake
--- a/cmake/modules/BuildQColorWidgets.cmake
+++ b/cmake/modules/BuildQColorWidgets.cmake
@@ -1,5 +1,5 @@
#
-# Copyright © 2017 Hasan Yavuz Özderya
+# Copyright © 2018 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -21,8 +21,8 @@ include(ExternalProject)
ExternalProject_Add(QCW
PREFIX qcw
- GIT_REPOSITORY https://github.com/mbasaglia/Qt-Color-Widgets
- PATCH_COMMAND patch -t -p1 -i ${CMAKE_CURRENT_LIST_DIR}/qt_5_2_moc_creation_namespace_fix.diff
+ GIT_REPOSITORY https://gitlab.com/mattia.basaglia/Qt-Color-Widgets.git
+ GIT_TAG 2c49e1bb4e1f591e720e2132cc2aaeef3ba73f14
CMAKE_CACHE_ARGS "-DCMAKE_CXX_FLAGS:string=-D QTCOLORWIDGETS_STATICALLY_LINKED"
UPDATE_COMMAND ""
INSTALL_COMMAND "")
diff --git a/cmake/modules/FindLedWidget.cmake b/cmake/modules/FindLedWidget.cmake
new file mode 100644
--- /dev/null
+++ b/cmake/modules/FindLedWidget.cmake
@@ -0,0 +1,25 @@
+#
+# Copyright © 2017 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 .
+#
+
+find_library(LEDWIDGET_LIBRARY "ledwidget")
+find_path(LEDWIDGET_INCLUDE_DIR "ledwidget.h" PATH_SUFFIXES "ledwidget")
+
+mark_as_advanced(LEDWIDGET_LIBRARY LEDWIDGET_INCLUDE_DIR)
+
+find_package_handle_standard_args(LedWidget DEFAULT_MSG LEDWIDGET_LIBRARY LEDWIDGET_INCLUDE_DIR)
diff --git a/cmake/modules/qt_5_2_moc_creation_namespace_fix.diff b/cmake/modules/qt_5_2_moc_creation_namespace_fix.diff
deleted file mode 100644
--- a/cmake/modules/qt_5_2_moc_creation_namespace_fix.diff
+++ /dev/null
@@ -1,39 +0,0 @@
-diff --git a/include/color_dialog.hpp b/include/color_dialog.hpp
-index 5c7653d..895215c 100644
---- a/include/color_dialog.hpp
-+++ b/include/color_dialog.hpp
-@@ -30,6 +30,8 @@
-
- class QAbstractButton;
-
-+using namespace color_widgets;
-+
- namespace color_widgets {
-
- class QCP_EXPORT ColorDialog : public QDialog
-diff --git a/include/color_list_widget.hpp b/include/color_list_widget.hpp
-index 282bea5..7d8e0c5 100644
---- a/include/color_list_widget.hpp
-+++ b/include/color_list_widget.hpp
-@@ -25,6 +25,8 @@
- #include "abstract_widget_list.hpp"
- #include "color_wheel.hpp"
-
-+using namespace color_widgets;
-+
- namespace color_widgets {
-
- class QCP_EXPORT ColorListWidget : public AbstractWidgetList
-diff --git a/include/color_selector.hpp b/include/color_selector.hpp
-index db817d5..48b374d 100644
---- a/include/color_selector.hpp
-+++ b/include/color_selector.hpp
-@@ -25,6 +25,8 @@
- #include "color_preview.hpp"
- #include "color_wheel.hpp"
-
-+using namespace color_widgets;
-+
- namespace color_widgets {
-
- /**
diff --git a/misc/pseudo_device.py b/misc/pseudo_device.py
--- a/misc/pseudo_device.py
+++ b/misc/pseudo_device.py
@@ -6,7 +6,7 @@
#
# Currently this script only outputs ASCII(comma separated) data.
#
-# Copyright © 2015 Hasan Yavuz Özderya
+# Copyright © 2018 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -26,6 +26,22 @@
import os, pty, time, struct, math
+def ascii_test_str(port):
+ text = """
+1,2,3
+2,4,6
+3,8,11
+1,2,3
+-1,-1,-1
+nana
+0,0,0
+1,na,na
+ """
+ while True:
+ for line in text.splitlines():
+ os.write(port, bytes(line+"\r\n", 'utf8'))
+ time.sleep(1)
+
def ascii_test(port):
"""Put ASCII test data through pseudo terminal."""
print("\n")
@@ -109,8 +125,9 @@ def run():
try:
# float_sine(master)
- frame_test(master)
+ # frame_test(master)
# ascii_test(master)
+ ascii_test_str(master)
finally:
# close the pseudo terminal files
os.close(master)
diff --git a/serialplot.pro b/serialplot.pro
--- a/serialplot.pro
+++ b/serialplot.pro
@@ -68,7 +68,10 @@ SOURCES += \
src/framedreader.cpp \
src/plotmanager.cpp \
src/numberformat.cpp \
- src/recordpanel.cpp
+ src/recordpanel.cpp \
+ src/updatechecker.cpp \
+ src/updatecheckdialog.cpp \
+ src/demoreadersettings.cpp
HEADERS += \
src/mainwindow.h \
@@ -108,7 +111,10 @@ HEADERS += \
src/plotmanager.h \
src/setting_defines.h \
src/numberformat.h \
- src/recordpanel.h
+ src/recordpanel.h \
+ src/updatechecker.h \
+ src/updatecheckdialog.h \
+ src/demoreadersettings.h
FORMS += \
src/mainwindow.ui \
@@ -124,7 +130,9 @@ FORMS += \
src/framedreadersettings.ui \
src/binarystreamreadersettings.ui \
src/asciireadersettings.ui \
- src/recordpanel.ui
+ src/recordpanel.ui \
+ src/updatecheckdialog.ui \
+ src/demoreadersettings.ui
INCLUDEPATH += qmake/ src/
diff --git a/src/abstractreader.cpp b/src/abstractreader.cpp
--- a/src/abstractreader.cpp
+++ b/src/abstractreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -19,41 +19,27 @@
#include "abstractreader.h"
-AbstractReader::AbstractReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject* parent) :
+AbstractReader::AbstractReader(QIODevice* device, QObject* parent) :
QObject(parent)
{
_device = device;
- _channelMan = channelMan;
- _recorder = recorder;
- recording = false;
+}
- // initialize sps counter
- sampleCount = 0;
- samplesPerSecond = 0;
- QObject::connect(&spsTimer, &QTimer::timeout,
- this, &AbstractReader::spsTimerTimeout);
- // TODO: start sps timer when reader is enabled
- spsTimer.start(SPS_UPDATE_TIMEOUT * 1000);
+void AbstractReader::pause(bool enabled)
+{
+ paused = enabled;
}
-void AbstractReader::spsTimerTimeout()
+void AbstractReader::enable(bool enabled)
{
- unsigned currentSps = samplesPerSecond;
- samplesPerSecond = (sampleCount/numOfChannels())/SPS_UPDATE_TIMEOUT;
- if (currentSps != samplesPerSecond)
+ if (enabled)
{
- emit samplesPerSecondChanged(samplesPerSecond);
+ QObject::connect(_device, &QIODevice::readyRead,
+ this, &AbstractReader::onDataReady);
}
- sampleCount = 0;
+ else
+ {
+ QObject::disconnect(_device, 0, this, 0);
+ disconnectSinks();
+ }
}
-
-void AbstractReader::addData(double* samples, unsigned length)
-{
- _channelMan->addData(samples, length);
- if (recording)
- {
- _recorder->addData(samples, length, numOfChannels());
- }
- sampleCount += length;
-}
diff --git a/src/abstractreader.h b/src/abstractreader.h
--- a/src/abstractreader.h
+++ b/src/abstractreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -25,20 +25,16 @@
#include
#include
-#include "channelmanager.h"
-#include "datarecorder.h"
+#include "source.h"
/**
* All reader classes must inherit this class.
*/
-class AbstractReader : public QObject
+class AbstractReader : public QObject, public Source
{
Q_OBJECT
public:
- explicit AbstractReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject* parent = 0);
-
- bool recording; /// is recording started
+ explicit AbstractReader(QIODevice* device, QObject* parent = 0);
/**
* Returns a widget to be shown in data format panel when reader
@@ -46,31 +42,16 @@ public:
*/
virtual QWidget* settingsWidget() = 0;
- /**
- * Number of channels being read.
- *
- * This number may be user selected or automatically determined
- * from incoming stream.
- */
- virtual unsigned numOfChannels() = 0;
-
/// Reader should only read when enabled. Default state should be
/// 'disabled'.
- virtual void enable(bool enabled = true) = 0;
+ virtual void enable(bool enabled = true);
- /**
- * @brief Starts sending data to recorder.
- *
- * @note recorder must have been started!
- */
- void startRecording();
-
- /// Stops recording.
- void stopRecording();
+ /// None of the current readers support X channel at the moment
+ bool hasX() const final { return false; };
signals:
+ // TODO: should we keep this?
void numOfChannelsChanged(unsigned);
- void samplesPerSecondChanged(unsigned);
public slots:
/**
@@ -79,26 +60,15 @@ public slots:
* Reader should actually continue reading to keep the
* synchronization but shouldn't commit data.
*/
- virtual void pause(bool) = 0;
+ void pause(bool enabled);
protected:
QIODevice* _device;
-
- /// Should be called with read data
- void addData(double* samples, unsigned length);
-
-private:
- const int SPS_UPDATE_TIMEOUT = 1; // second
+ bool paused;
- unsigned sampleCount;
- unsigned samplesPerSecond;
-
- ChannelManager* _channelMan;
- DataRecorder* _recorder;
- QTimer spsTimer;
-
-private slots:
- void spsTimerTimeout();
+protected slots:
+ /// all derived readers has to override this function
+ virtual void onDataReady() = 0;
};
#endif // ABSTRACTREADER_H
diff --git a/src/asciireader.cpp b/src/asciireader.cpp
--- a/src/asciireader.cpp
+++ b/src/asciireader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -24,28 +24,33 @@
/// If set to this value number of channels is determined from input
#define NUMOFCHANNELS_AUTO (0)
-AsciiReader::AsciiReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject* parent) :
- AbstractReader(device, channelMan, recorder, parent)
+AsciiReader::AsciiReader(QIODevice* device, QObject* parent) :
+ AbstractReader(device, parent)
{
paused = false;
- discardFirstLine = true;
- _numOfChannels = _settingsWidget.numOfChannels();
- autoNumOfChannels = (_numOfChannels == NUMOFCHANNELS_AUTO);
+ _numChannels = _settingsWidget.numOfChannels();
+ autoNumOfChannels = (_numChannels == NUMOFCHANNELS_AUTO);
+ delimiter = _settingsWidget.delimiter();
connect(&_settingsWidget, &AsciiReaderSettings::numOfChannelsChanged,
[this](unsigned value)
{
- _numOfChannels = value;
- autoNumOfChannels = (_numOfChannels == NUMOFCHANNELS_AUTO);
+ _numChannels = value;
+ updateNumChannels(); // TODO: setting numchannels = 0, should remove all buffers
+ // do we want this?
+ autoNumOfChannels = (_numChannels == NUMOFCHANNELS_AUTO);
if (!autoNumOfChannels)
{
emit numOfChannelsChanged(value);
}
});
- connect(device, &QIODevice::aboutToClose, [this](){discardFirstLine=true;});
+ connect(&_settingsWidget, &AsciiReaderSettings::delimiterChanged,
+ [this](QChar d)
+ {
+ delimiter = d;
+ });
}
QWidget* AsciiReader::settingsWidget()
@@ -53,49 +58,38 @@ QWidget* AsciiReader::settingsWidget()
return &_settingsWidget;
}
-unsigned AsciiReader::numOfChannels()
+unsigned AsciiReader::numChannels() const
{
+ // TODO: an alternative is to never set _numChannels to '0'
// do not allow '0'
- if (_numOfChannels == 0)
- {
- return 1;
- }
- else
- {
- return _numOfChannels;
- }
+ return _numChannels == 0 ? 1 : _numChannels;
}
-// TODO: this could be a part of AbstractReader
void AsciiReader::enable(bool enabled)
{
if (enabled)
{
- discardFirstLine = true;
+ firstReadAfterEnable = true;
QObject::connect(_device, &QIODevice::readyRead,
this, &AsciiReader::onDataReady);
}
else
{
QObject::disconnect(_device, 0, this, 0);
+ disconnectSinks();
}
}
-void AsciiReader::pause(bool enabled)
-{
- paused = enabled;
-}
-
void AsciiReader::onDataReady()
{
while(_device->canReadLine())
{
- QByteArray line = _device->readLine();
+ QString line = QString(_device->readLine());
// discard only once when we just started reading
- if (discardFirstLine)
+ if (firstReadAfterEnable)
{
- discardFirstLine = false;
+ firstReadAfterEnable = false;
continue;
}
@@ -116,51 +110,57 @@ void AsciiReader::onDataReady()
continue;
}
- auto separatedValues = line.split(',');
+ const SamplePack* samples = parseLine(line);
+ if (samples != nullptr) {
+ // update number of channels if in auto mode
+ if (autoNumOfChannels ) {
+ unsigned nc = samples->numChannels();
+ if (nc != _numChannels) {
+ _numChannels = nc;
+ updateNumChannels();
+ // TODO: is `numOfChannelsChanged` signal still used?
+ emit numOfChannelsChanged(nc);
+ }
+ }
- unsigned numReadChannels; // effective number of channels to read
- unsigned numComingChannels = separatedValues.length();
+ Q_ASSERT(samples->numChannels() == _numChannels);
+
+ // commit data
+ feedOut(*samples);
+ }
+ }
+}
- if (autoNumOfChannels)
- {
- // did number of channels changed?
- if (numComingChannels != _numOfChannels)
- {
- _numOfChannels = numComingChannels;
- emit numOfChannelsChanged(numComingChannels);
- }
- numReadChannels = numComingChannels;
- }
- else if (numComingChannels >= _numOfChannels)
- {
- numReadChannels = _numOfChannels;
- }
- else // there is missing channel data
+SamplePack* AsciiReader::parseLine(const QString& line) const
+{
+ auto separatedValues = line.split(delimiter, QString::SkipEmptyParts);
+ unsigned numComingChannels = separatedValues.length();
+
+ // check number of channels (skipped if auto num channels is enabled)
+ if ((!numComingChannels) || (!autoNumOfChannels && numComingChannels != _numChannels))
+ {
+ qWarning() << "Line parsing error: invalid number of channels!";
+ qWarning() << "Read line: " << line;
+ return nullptr;
+ }
+
+ // parse data per channel
+ auto samples = new SamplePack(1, numComingChannels);
+ for (unsigned ci = 0; ci < numComingChannels; ci++)
+ {
+ bool ok;
+ samples->data(ci)[0] = separatedValues[ci].toDouble(&ok);
+ if (!ok)
{
- numReadChannels = separatedValues.length();
- qWarning() << "Incoming data is missing data for some channels!";
+ qWarning() << "Data parsing error for channel: " << ci;
qWarning() << "Read line: " << line;
- }
- // parse read line
- double* channelSamples = new double[_numOfChannels]();
- for (unsigned ci = 0; ci < numReadChannels; ci++)
- {
- bool ok;
- channelSamples[ci] = separatedValues[ci].toDouble(&ok);
- if (!ok)
- {
- qWarning() << "Data parsing error for channel: " << ci;
- qWarning() << "Read line: " << line;
- channelSamples[ci] = 0;
- }
+ delete samples;
+ return nullptr;
}
+ }
- // commit data
- addData(channelSamples, _numOfChannels);
-
- delete[] channelSamples;
- }
+ return samples;
}
void AsciiReader::saveSettings(QSettings* settings)
diff --git a/src/asciireader.h b/src/asciireader.h
--- a/src/asciireader.h
+++ b/src/asciireader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,7 +21,9 @@
#define ASCIIREADER_H
#include
+#include
+#include "samplepack.h"
#include "abstractreader.h"
#include "asciireadersettings.h"
@@ -30,32 +32,32 @@ class AsciiReader : public AbstractReade
Q_OBJECT
public:
- explicit AsciiReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject *parent = 0);
+ explicit AsciiReader(QIODevice* device, QObject *parent = 0);
QWidget* settingsWidget();
- unsigned numOfChannels();
- void enable(bool enabled = true);
+ unsigned numChannels() const;
+ void enable(bool enabled) override;
/// Stores settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads settings from a `QSettings`.
void loadSettings(QSettings* settings);
-public slots:
- void pause(bool);
-
private:
AsciiReaderSettings _settingsWidget;
- unsigned _numOfChannels;
+ unsigned _numChannels;
/// number of channels will be determined from incoming data
unsigned autoNumOfChannels;
- bool paused;
+ QChar delimiter; ///< selected column delimiter
- // We may have (usually true) started reading in the middle of a
- // line, so its a better idea to just discard first line.
- bool discardFirstLine;
+ bool firstReadAfterEnable = false;
private slots:
- void onDataReady();
+ void onDataReady() override;
+ /**
+ * Parses given line and returns sample pack.
+ *
+ * Returns `nullptr` in case of error.
+ */
+ SamplePack* parseLine(const QString& line) const;
};
#endif // ASCIIREADER_H
diff --git a/src/asciireadersettings.cpp b/src/asciireadersettings.cpp
--- a/src/asciireadersettings.cpp
+++ b/src/asciireadersettings.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,20 +17,35 @@
along with serialplot. If not, see .
*/
+#include
+#include
+
#include "utils.h"
#include "setting_defines.h"
#include "asciireadersettings.h"
#include "ui_asciireadersettings.h"
-#include
-
AsciiReaderSettings::AsciiReaderSettings(QWidget *parent) :
QWidget(parent),
ui(new Ui::AsciiReaderSettings)
{
ui->setupUi(this);
+ auto validator = new QRegularExpressionValidator(QRegularExpression("[^\\d]?"), this);
+ ui->leDelimiter->setValidator(validator);
+
+ connect(ui->rbComma, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->rbSpace, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->rbTab, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->rbOtherDelimiter, &QAbstractButton::toggled,
+ this, &AsciiReaderSettings::delimiterToggled);
+ connect(ui->leDelimiter, &QLineEdit::textChanged,
+ this, &AsciiReaderSettings::customDelimiterChanged);
+
// Note: if directly connected we get a runtime warning on incompatible signal arguments
connect(ui->spNumOfChannels, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged),
[this](int value)
@@ -44,11 +59,51 @@ AsciiReaderSettings::~AsciiReaderSetting
delete ui;
}
-unsigned AsciiReaderSettings::numOfChannels()
+unsigned AsciiReaderSettings::numOfChannels() const
{
return ui->spNumOfChannels->value();
}
+QChar AsciiReaderSettings::delimiter() const
+{
+ if (ui->rbComma->isChecked())
+ {
+ return QChar(',');
+ }
+ else if (ui->rbSpace->isChecked())
+ {
+ return QChar(' ');
+ }
+ else if (ui->rbTab->isChecked())
+ {
+ return QChar('\t');
+ }
+ else // rbOther
+ {
+ auto t = ui->leDelimiter->text();
+ return t.isEmpty() ? QChar() : t.at(0);
+ }
+}
+
+void AsciiReaderSettings::delimiterToggled(bool checked)
+{
+ if (!checked) return;
+
+ auto d = delimiter();
+ if (!d.isNull())
+ {
+ emit delimiterChanged(d);
+ }
+}
+
+void AsciiReaderSettings::customDelimiterChanged(const QString text)
+{
+ if (ui->rbOtherDelimiter->isChecked())
+ {
+ if (!text.isEmpty()) emit delimiterChanged(text.at(0));
+ }
+}
+
void AsciiReaderSettings::saveSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_ASCII);
@@ -58,6 +113,25 @@ void AsciiReaderSettings::saveSettings(Q
if (numOfChannelsSetting == "0") numOfChannelsSetting = "auto";
settings->setValue(SG_ASCII_NumOfChannels, numOfChannelsSetting);
+ // save delimiter
+ QString delimiterS;
+ if (ui->rbOtherDelimiter->isChecked())
+ {
+ delimiterS = "other";
+ }
+ else if (ui->rbTab->isChecked())
+ {
+ // Note: \t is not correctly loaded
+ delimiterS = "TAB";
+ }
+ else
+ {
+ delimiterS = delimiter();
+ }
+
+ settings->setValue(SG_ASCII_Delimiter, delimiterS);
+ settings->setValue(SG_ASCII_CustomDelimiter, ui->leDelimiter->text());
+
settings->endGroup();
}
@@ -83,5 +157,26 @@ void AsciiReaderSettings::loadSettings(Q
}
}
+ // load delimiter
+ auto delimiterS = settings->value(SG_ASCII_Delimiter, delimiter()).toString();
+ auto customDelimiter = settings->value(SG_ASCII_CustomDelimiter, delimiter()).toString();
+ if (!customDelimiter.isEmpty()) ui->leDelimiter->setText(customDelimiter);
+ if (delimiterS == ",")
+ {
+ ui->rbComma->setChecked(true);
+ }
+ else if (delimiterS == " ")
+ {
+ ui->rbSpace->setChecked(true);
+ }
+ else if (delimiterS == "TAB")
+ {
+ ui->rbTab->setChecked(true);
+ }
+ else
+ {
+ ui->rbOtherDelimiter->setChecked(true);
+ }
+
settings->endGroup();
}
diff --git a/src/asciireadersettings.h b/src/asciireadersettings.h
--- a/src/asciireadersettings.h
+++ b/src/asciireadersettings.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -22,6 +22,7 @@
#include
#include
+#include
namespace Ui {
class AsciiReaderSettings;
@@ -35,7 +36,8 @@ public:
explicit AsciiReaderSettings(QWidget *parent = 0);
~AsciiReaderSettings();
- unsigned numOfChannels();
+ unsigned numOfChannels() const;
+ QChar delimiter() const;
/// Stores settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads settings from a `QSettings`.
@@ -43,9 +45,15 @@ public:
signals:
void numOfChannelsChanged(unsigned);
+ /// Signaled only with a valid delimiter
+ void delimiterChanged(QChar);
private:
Ui::AsciiReaderSettings *ui;
+
+private slots:
+ void delimiterToggled(bool checked);
+ void customDelimiterChanged(const QString text);
};
#endif // ASCIIREADERSETTINGS_H
diff --git a/src/asciireadersettings.ui b/src/asciireadersettings.ui
--- a/src/asciireadersettings.ui
+++ b/src/asciireadersettings.ui
@@ -6,14 +6,17 @@
0
0
- 414
+ 493
171
Form
-
+
+
+ QFormLayout::ExpandingFieldsGrow
+
0
@@ -26,68 +29,105 @@
0
- -
+
-
+
+
+ Number Of Channels:
+
+
+
+ -
+
+
+
+ 60
+ 0
+
+
+
+ Select number of channels or set to 0 for Auto (determined from incoming data)
+
+
+ Auto
+
+
+ false
+
+
+ 0
+
+
+ 32
+
+
+
+ -
+
+
+ Column Delimiter:
+
+
+
+ -
-
-
+
- Number Of Channels:
+ comma
+
+
+ true
-
-
-
-
- 60
- 0
-
-
-
- Select number of channels or set to 0 for Auto (determined from incoming data)
+
+
+ space
-
- Auto
-
-
- false
+
+
+ -
+
+
+ tab
-
- 0
-
-
- 32
+
+
+ -
+
+
+ other:
-
-
-
- Qt::Horizontal
+
+
+
+ 0
+ 0
+
-
+
- 1
- 20
+ 30
+ 16777215
-
+
+ Enter a custom delimiter character
+
+
+
+
+
+ |
+
+
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 1
-
-
-
-
diff --git a/src/barchart.cpp b/src/barchart.cpp
new file mode 100644
--- /dev/null
+++ b/src/barchart.cpp
@@ -0,0 +1,96 @@
+/*
+ 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 .
+*/
+
+#include
+#include
+
+#include "barchart.h"
+
+BarChart::BarChart(const Stream* stream)
+{
+ _stream = stream;
+ setSpacing(0);
+}
+
+void BarChart::resample()
+{
+ setSamples(chartData());
+}
+
+QVector BarChart::chartData() const
+{
+ unsigned numChannels = _stream->numChannels();
+ unsigned numSamples = _stream->numSamples();
+ QVector data(numChannels);
+ for (unsigned i = 0; i < numChannels; i++)
+ {
+ data[i] = _stream->channel(i)->yData()->sample(numSamples-1);
+ }
+ return data;
+}
+
+QwtColumnSymbol* BarChart::specialSymbol(int sampleIndex, const QPointF& sample) const
+{
+ unsigned numChannels = _stream->numChannels();
+ if (sampleIndex < 0 || sampleIndex > (int) numChannels)
+ {
+ return NULL;
+ }
+
+ auto color = _stream->channel(sampleIndex)->color();
+
+ QwtColumnSymbol* symbol = new QwtColumnSymbol(QwtColumnSymbol::Box);
+ symbol->setLineWidth(1);
+ symbol->setFrameStyle(QwtColumnSymbol::Plain);
+ symbol->setPalette(QPalette(color));
+
+ return symbol;
+}
+
+void BarChart::drawSample(
+ QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, const QwtInterval &boundingInterval,
+ int index, const QPointF &sample ) const
+{
+ QwtPlotBarChart::drawSample(painter, xMap, yMap, canvasRect, boundingInterval, index, sample);
+
+ // calculate bar size
+ const double barWidth = sampleWidth(xMap, canvasRect.width(),
+ boundingInterval.width(), sample.y());
+ const double x = xMap.transform( sample.x() );
+ const double x1 = x - 0.5 * barWidth;
+ // const double x2 = x + 0.5 * barWidth;
+
+ const double y1 = yMap.transform(baseline());
+ const double y2 = yMap.transform(sample.y());
+ const double barHeight = fabs(y2 - y1);
+
+ // create and calculate text size
+ const auto valueStr = QString::number(sample.y());
+ auto valueText = QwtText(valueStr, QwtText::PlainText);
+ const auto textSize = valueText.textSize();
+ auto r = QRectF(x1, y2, barWidth, textSize.height());
+ if (y2 > y1) r.moveBottom(y2);
+
+ // display text if there is enough space
+ if (barHeight >= textSize.height() && barWidth >= textSize.width())
+ {
+ valueText.draw(painter, r);
+ }
+}
diff --git a/src/barchart.h b/src/barchart.h
new file mode 100644
--- /dev/null
+++ b/src/barchart.h
@@ -0,0 +1,47 @@
+/*
+ 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 .
+*/
+
+#ifndef BARCHART_H
+#define BARCHART_H
+
+#include
+#include
+#include "stream.h"
+
+class BarChart : public QwtPlotBarChart
+{
+public:
+ explicit BarChart(const Stream* stream);
+
+ void resample();
+ QwtColumnSymbol* specialSymbol(int sampleIndex, const QPointF&) const;
+
+ void drawSample(
+ QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, const QwtInterval &boundingInterval,
+ int index, const QPointF &sample ) const;
+
+private:
+ const Stream* _stream;
+
+ QVector chartData() const;
+};
+
+
+#endif // BARCHART_H
diff --git a/src/barplot.cpp b/src/barplot.cpp
new file mode 100644
--- /dev/null
+++ b/src/barplot.cpp
@@ -0,0 +1,74 @@
+/*
+ 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 .
+*/
+
+#include "barplot.h"
+#include "barscaledraw.h"
+#include "utils.h"
+
+BarPlot::BarPlot(Stream* stream, PlotMenu* menu, QWidget* parent) :
+ QwtPlot(parent), _menu(menu), barChart(stream)
+{
+ _stream = stream;
+ barChart.attach(this);
+ setAxisMaxMinor(QwtPlot::xBottom, 0);
+ setAxisScaleDraw(QwtPlot::xBottom, new BarScaleDraw(stream));
+
+ update();
+ connect(_stream, &Stream::dataAdded, this, &BarPlot::update);
+ connect(_stream, &Stream::numChannelsChanged, this, &BarPlot::update);
+
+ // connect to menu
+ connect(&menu->darkBackgroundAction, SELECT::OVERLOAD_OF(&QAction::toggled),
+ this, &BarPlot::darkBackground);
+ darkBackground(menu->darkBackgroundAction.isChecked());
+}
+
+void BarPlot::update()
+{
+ // Note: -0.99 is used instead of -1 to handle the case of `numOfChannels==1`
+ setAxisScale(QwtPlot::xBottom, 0, _stream->numChannels()-0.99, 1);
+ barChart.resample();
+ replot();
+}
+
+void BarPlot::setYAxis(bool autoScaled, double yMin, double yMax)
+{
+ if (autoScaled)
+ {
+ setAxisAutoScale(QwtPlot::yLeft);
+ }
+ else
+ {
+ setAxisScale(QwtPlot::yLeft, yMin, yMax);
+ }
+}
+
+
+void BarPlot::darkBackground(bool enabled)
+{
+ if (enabled)
+ {
+ setCanvasBackground(QBrush(Qt::black));
+ }
+ else
+ {
+ setCanvasBackground(QBrush(Qt::white));
+ }
+ replot();
+}
diff --git a/src/barplot.h b/src/barplot.h
new file mode 100644
--- /dev/null
+++ b/src/barplot.h
@@ -0,0 +1,55 @@
+/*
+ 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 .
+*/
+
+#ifndef BARPLOT_H
+#define BARPLOT_H
+
+#include
+
+#include "stream.h"
+#include "plotmenu.h"
+#include "barchart.h"
+
+class BarPlot : public QwtPlot
+{
+ Q_OBJECT
+
+public:
+ explicit BarPlot(Stream* stream,
+ PlotMenu* menu,
+ QWidget* parent = 0);
+
+public slots:
+ /// Set the Y axis
+ void setYAxis(bool autoScaled, double yMin = 0, double yMax = 1);
+ /// Enable/disable dark background
+ void darkBackground(bool enabled);
+
+private:
+ Stream* _stream;
+ PlotMenu* _menu;
+ BarChart barChart;
+
+ QVector chartData() const;
+
+private slots:
+ void update();
+};
+
+#endif // BARPLOT_H
diff --git a/src/barscaledraw.cpp b/src/barscaledraw.cpp
new file mode 100644
--- /dev/null
+++ b/src/barscaledraw.cpp
@@ -0,0 +1,51 @@
+/*
+ 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 .
+*/
+
+#include "barscaledraw.h"
+
+#include
+
+BarScaleDraw::BarScaleDraw(const Stream* stream)
+{
+ _stream = stream;
+ enableComponent(Backbone, false);
+ setLabelRotation(-90);
+ setLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+
+ QObject::connect(_stream, &Stream::channelNameChanged,
+ [this]()
+ {
+ invalidateCache();
+ });
+}
+
+QwtText BarScaleDraw::label(double value) const
+{
+ int index = value;
+ unsigned numChannels = _stream->numChannels();
+
+ if (index >=0 && index < (int) numChannels)
+ {
+ return _stream->channel(index)->name();
+ }
+ else
+ {
+ return QString("");
+ }
+}
diff --git a/src/barscaledraw.h b/src/barscaledraw.h
new file mode 100644
--- /dev/null
+++ b/src/barscaledraw.h
@@ -0,0 +1,39 @@
+/*
+ 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 .
+*/
+
+#ifndef BARSCALEDRAW_H
+#define BARSCALEDRAW_H
+
+#include
+#include
+#include
+
+#include "stream.h"
+
+class BarScaleDraw : public QwtScaleDraw
+{
+public:
+ explicit BarScaleDraw(const Stream* stream);
+ QwtText label(double value) const;
+
+private:
+ const Stream* _stream;
+};
+
+#endif // BARSCALEDRAW_H
diff --git a/src/binarystreamreader.cpp b/src/binarystreamreader.cpp
--- a/src/binarystreamreader.cpp
+++ b/src/binarystreamreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,19 +23,16 @@
#include "binarystreamreader.h"
#include "floatswap.h"
-BinaryStreamReader::BinaryStreamReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject* parent) :
- AbstractReader(device, channelMan, recorder, parent)
+BinaryStreamReader::BinaryStreamReader(QIODevice* device, QObject* parent) :
+ AbstractReader(device, parent)
{
paused = false;
skipByteRequested = false;
skipSampleRequested = false;
- _numOfChannels = _settingsWidget.numOfChannels();
+ _numChannels = _settingsWidget.numOfChannels();
connect(&_settingsWidget, &BinaryStreamReaderSettings::numOfChannelsChanged,
- this, &BinaryStreamReader::numOfChannelsChanged);
- connect(&_settingsWidget, &BinaryStreamReaderSettings::numOfChannelsChanged,
- this, &BinaryStreamReader::onNumOfChannelsChanged);
+ this, &BinaryStreamReader::onNumOfChannelsChanged);
// initial number format selection
onNumberFormatChanged(_settingsWidget.numberFormat());
@@ -60,27 +57,9 @@ QWidget* BinaryStreamReader::settingsWid
return &_settingsWidget;
}
-unsigned BinaryStreamReader::numOfChannels()
-{
- return _numOfChannels;
-}
-
-void BinaryStreamReader::enable(bool enabled)
+unsigned BinaryStreamReader::numChannels() const
{
- if (enabled)
- {
- QObject::connect(_device, &QIODevice::readyRead,
- this, &BinaryStreamReader::onDataReady);
- }
- else
- {
- QObject::disconnect(_device, 0, this, 0);
- }
-}
-
-void BinaryStreamReader::pause(bool enabled)
-{
- paused = enabled;
+ return _numChannels;
}
void BinaryStreamReader::onNumberFormatChanged(NumberFormat numberFormat)
@@ -123,17 +102,19 @@ void BinaryStreamReader::onNumberFormatC
void BinaryStreamReader::onNumOfChannelsChanged(unsigned value)
{
- _numOfChannels = value;
+ _numChannels = value;
+ updateNumChannels();
+ emit numOfChannelsChanged(value);
}
void BinaryStreamReader::onDataReady()
{
// a package is a set of channel data like {CHAN0_SAMPLE, CHAN1_SAMPLE...}
- int packageSize = sampleSize * _numOfChannels;
+ int packageSize = sampleSize * _numChannels;
int bytesAvailable = _device->bytesAvailable();
// skip 1 byte if requested
- if (bytesAvailable > 0 && skipByteRequested)
+ if (skipByteRequested && bytesAvailable > 0)
{
_device->read(1);
skipByteRequested = false;
@@ -141,11 +122,11 @@ void BinaryStreamReader::onDataReady()
}
// skip 1 sample (channel) if requested
- if (bytesAvailable >= (int) sampleSize && skipSampleRequested)
+ if (skipSampleRequested && bytesAvailable >= (int) sampleSize)
{
_device->read(sampleSize);
skipSampleRequested = false;
- bytesAvailable--;
+ bytesAvailable -= sampleSize;
}
if (bytesAvailable < packageSize) return;
@@ -160,20 +141,16 @@ void BinaryStreamReader::onDataReady()
return;
}
- double* channelSamples = new double[numOfPackagesToRead*_numOfChannels];
-
+ // actual reading
+ SamplePack samples(numOfPackagesToRead, _numChannels);
for (int i = 0; i < numOfPackagesToRead; i++)
{
- for (unsigned int ci = 0; ci < _numOfChannels; ci++)
+ for (unsigned int ci = 0; ci < _numChannels; ci++)
{
- // channelSamples[ci].replace(i, (this->*readSample)());
- channelSamples[ci*numOfPackagesToRead+i] = (this->*readSample)();
+ samples.data(ci)[i] = (this->*readSample)();
}
}
-
- addData(channelSamples, numOfPackagesToRead*_numOfChannels);
-
- delete[] channelSamples;
+ feedOut(samples);
}
template double BinaryStreamReader::readSampleAs()
diff --git a/src/binarystreamreader.h b/src/binarystreamreader.h
--- a/src/binarystreamreader.h
+++ b/src/binarystreamreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -34,24 +34,18 @@ class BinaryStreamReader : public Abstra
{
Q_OBJECT
public:
- explicit BinaryStreamReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject *parent = 0);
+ explicit BinaryStreamReader(QIODevice* device, QObject *parent = 0);
QWidget* settingsWidget();
- unsigned numOfChannels();
- void enable(bool enabled = true);
+ unsigned numChannels() const;
/// Stores settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads settings from a `QSettings`.
void loadSettings(QSettings* settings);
-public slots:
- void pause(bool);
-
private:
BinaryStreamReaderSettings _settingsWidget;
- unsigned _numOfChannels;
+ unsigned _numChannels;
unsigned sampleSize;
- bool paused;
bool skipByteRequested;
bool skipSampleRequested;
@@ -69,7 +63,7 @@ private:
private slots:
void onNumberFormatChanged(NumberFormat numberFormat);
void onNumOfChannelsChanged(unsigned value);
- void onDataReady();
+ void onDataReady() override;
};
#endif // BINARYSTREAMREADER_H
diff --git a/src/channelinfomodel.cpp b/src/channelinfomodel.cpp
--- a/src/channelinfomodel.cpp
+++ b/src/channelinfomodel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -68,6 +68,7 @@ ChannelInfoModel::ChannelInfoModel(unsig
ChannelInfoModel::ChannelInfoModel(const ChannelInfoModel& other) :
ChannelInfoModel(other.rowCount(), other.parent())
{
+ // TODO: why not set (copy) info list directly instead?
for (int i = 0; i < other.rowCount(); i++)
{
setData(index(i, COLUMN_NAME),
@@ -76,9 +77,24 @@ ChannelInfoModel::ChannelInfoModel(const
setData(index(i, COLUMN_NAME),
other.data(other.index(i, COLUMN_NAME), Qt::ForegroundRole),
Qt::ForegroundRole);
+
setData(index(i, COLUMN_VISIBILITY),
other.data(other.index(i, COLUMN_VISIBILITY), Qt::CheckStateRole),
Qt::CheckStateRole);
+
+ setData(index(i, COLUMN_GAIN),
+ other.data(other.index(i, COLUMN_GAIN), Qt::CheckStateRole),
+ Qt::CheckStateRole);
+ setData(index(i, COLUMN_GAIN),
+ other.data(other.index(i, COLUMN_GAIN), Qt::EditRole),
+ Qt::EditRole);
+
+ setData(index(i, COLUMN_OFFSET),
+ other.data(other.index(i, COLUMN_OFFSET), Qt::CheckStateRole),
+ Qt::CheckStateRole);
+ setData(index(i, COLUMN_OFFSET),
+ other.data(other.index(i, COLUMN_OFFSET), Qt::EditRole),
+ Qt::EditRole);
}
}
@@ -96,6 +112,10 @@ ChannelInfoModel::ChannelInfo::ChannelIn
name = tr("Channel %1").arg(index + 1);
visibility = true;
color = colors[index % NUMOF_COLORS];
+ gain = 1.0;
+ offset = 0.0;
+ gainEn = false;
+ offsetEn = false;
}
QString ChannelInfoModel::name(unsigned i) const
@@ -113,6 +133,26 @@ bool ChannelInfoModel::isVisible(unsigne
return infos[i].visibility;
}
+bool ChannelInfoModel::gainEn (unsigned i) const
+{
+ return infos[i].gainEn;
+}
+
+double ChannelInfoModel::gain (unsigned i) const
+{
+ return infos[i].gain;
+}
+
+bool ChannelInfoModel::offsetEn (unsigned i) const
+{
+ return infos[i].offsetEn;
+}
+
+double ChannelInfoModel::offset (unsigned i) const
+{
+ return infos[i].offset;
+}
+
QStringList ChannelInfoModel::channelNames() const
{
QStringList r;
@@ -143,6 +183,10 @@ Qt::ItemFlags ChannelInfoModel::flags(co
{
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
}
+ else if (index.column() == COLUMN_GAIN || index.column() == COLUMN_OFFSET)
+ {
+ return Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
+ }
return Qt::NoItemFlags;
}
@@ -155,27 +199,51 @@ QVariant ChannelInfoModel::data(const QM
return QVariant();
}
+ auto &info = infos[index.row()];
+
// get color
if (role == Qt::ForegroundRole)
{
- return infos[index.row()].color;
+ return info.color;
}
- // get name
+ // name
if (index.column() == COLUMN_NAME)
{
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
- return QVariant(infos[index.row()].name);
+ return QVariant(info.name);
}
- } // get visibility
+ } // visibility
else if (index.column() == COLUMN_VISIBILITY)
{
if (role == Qt::CheckStateRole)
{
- bool visible = infos[index.row()].visibility;
+ bool visible = info.visibility;
return visible ? Qt::Checked : Qt::Unchecked;
}
+ } // gain
+ else if (index.column() == COLUMN_GAIN)
+ {
+ if (role == Qt::CheckStateRole)
+ {
+ return info.gainEn ? Qt::Checked : Qt::Unchecked;
+ }
+ else if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ return QVariant(info.gain);
+ }
+ } // offset
+ else if (index.column() == COLUMN_OFFSET)
+ {
+ if (role == Qt::CheckStateRole)
+ {
+ return info.offsetEn ? Qt::Checked : Qt::Unchecked;
+ }
+ else if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ return QVariant(info.offset);
+ }
}
return QVariant();
@@ -195,6 +263,14 @@ QVariant ChannelInfoModel::headerData(in
{
return tr("Visible");
}
+ else if (section == COLUMN_GAIN)
+ {
+ return tr("Gain");
+ }
+ else if (section == COLUMN_OFFSET)
+ {
+ return tr("Offset");
+ }
}
}
else // vertical
@@ -216,22 +292,24 @@ bool ChannelInfoModel::setData(const QMo
return false;
}
+ auto &info = infos[index.row()];
+
// set color
if (role == Qt::ForegroundRole)
{
- infos[index.row()].color = value.value();
+ info.color = value.value();
emit dataChanged(index, index, QVector({Qt::ForegroundRole}));
return true;
}
// set name
+ bool r = false;
if (index.column() == COLUMN_NAME)
{
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
- infos[index.row()].name = value.toString();
- emit dataChanged(index, index, QVector({role}));
- return true;
+ info.name = value.toString();
+ r = true;
}
} // set visibility
else if (index.column() == COLUMN_VISIBILITY)
@@ -239,14 +317,47 @@ bool ChannelInfoModel::setData(const QMo
if (role == Qt::CheckStateRole)
{
bool checked = value.toInt() == Qt::Checked;
- infos[index.row()].visibility = checked;
- emit dataChanged(index, index, QVector({role}));
- return true;
+ info.visibility = checked;
+ r = true;
+ }
+ }
+ else if (index.column() == COLUMN_GAIN)
+ {
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ info.gain = value.toDouble();
+ r = true;
+ }
+ else if (role == Qt::CheckStateRole)
+ {
+ bool checked = value.toInt() == Qt::Checked;
+ info.gainEn = checked;
+ if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
+ r = true;
+ }
+ }
+ else if (index.column() == COLUMN_OFFSET)
+ {
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ info.offset = value.toDouble();
+ r = true;
+ }
+ else if (role == Qt::CheckStateRole)
+ {
+ bool checked = value.toInt() == Qt::Checked;
+ info.offsetEn = checked;
+ if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
+ r = true;
}
}
- // invalid index/role
- return false;
+ if (r)
+ {
+ emit dataChanged(index, index, QVector({role}));
+ }
+
+ return r;
}
void ChannelInfoModel::setNumOfChannels(unsigned number)
@@ -285,6 +396,7 @@ void ChannelInfoModel::setNumOfChannels(
}
_numOfChannels = number;
+ updateGainOrOffsetEn();
if (isInserting)
{
@@ -306,11 +418,13 @@ void ChannelInfoModel::resetInfos()
endResetModel();
}
+// TODO: fix repetitive code, ChannelInfoModel::reset* functions
void ChannelInfoModel::resetNames()
{
beginResetModel();
for (unsigned ci = 0; (int) ci < infos.length(); ci++)
{
+ // TODO: do not create a full object every time (applies to other reset methods as well)
infos[ci].name = ChannelInfo(ci).name;
}
endResetModel();
@@ -326,17 +440,56 @@ void ChannelInfoModel::resetColors()
endResetModel();
}
-void ChannelInfoModel::resetVisibility()
+void ChannelInfoModel::resetVisibility(bool visible)
+{
+ beginResetModel();
+ for (unsigned ci = 0; (int) ci < infos.length(); ci++)
+ {
+ infos[ci].visibility = visible;
+ }
+ endResetModel();
+}
+
+void ChannelInfoModel::resetGains()
{
beginResetModel();
for (unsigned ci = 0; (int) ci < infos.length(); ci++)
{
- infos[ci].visibility = true;
+ infos[ci].gain = ChannelInfo(ci).gain;
+ infos[ci].gainEn = ChannelInfo(ci).gainEn;
}
+ updateGainOrOffsetEn();
endResetModel();
}
-void ChannelInfoModel::saveSettings(QSettings* settings)
+void ChannelInfoModel::resetOffsets()
+{
+ beginResetModel();
+ for (unsigned ci = 0; (int) ci < infos.length(); ci++)
+ {
+ infos[ci].offset = ChannelInfo(ci).offset;
+ infos[ci].offsetEn = ChannelInfo(ci).offsetEn;
+ }
+ updateGainOrOffsetEn();
+ endResetModel();
+}
+
+bool ChannelInfoModel::gainOrOffsetEn() const
+{
+ return _gainOrOffsetEn;
+}
+
+void ChannelInfoModel::updateGainOrOffsetEn()
+{
+ _gainOrOffsetEn = false;
+ for (unsigned ci = 0; ci < _numOfChannels; ci++)
+ {
+ auto& info = infos[ci];
+ _gainOrOffsetEn |= (info.gainEn || info.offsetEn);
+ }
+}
+
+void ChannelInfoModel::saveSettings(QSettings* settings) const
{
settings->beginGroup(SettingGroup_Channels);
settings->beginWriteArray(SG_Channels_Channel);
@@ -345,9 +498,14 @@ void ChannelInfoModel::saveSettings(QSet
for (unsigned ci = 0; (int) ci < infos.length(); ci++)
{
settings->setArrayIndex(ci);
- settings->setValue(SG_Channels_Name, infos[ci].name);
- settings->setValue(SG_Channels_Color, infos[ci].color);
- settings->setValue(SG_Channels_Visible, infos[ci].visibility);
+ auto& info = infos[ci];
+ settings->setValue(SG_Channels_Name, info.name);
+ settings->setValue(SG_Channels_Color, info.color);
+ settings->setValue(SG_Channels_Visible, info.visibility);
+ settings->setValue(SG_Channels_Gain, info.gain);
+ settings->setValue(SG_Channels_GainEn, info.gainEn);
+ settings->setValue(SG_Channels_Offset, info.offset);
+ settings->setValue(SG_Channels_OffsetEn, info.offsetEn);
}
settings->endArray();
@@ -364,9 +522,13 @@ void ChannelInfoModel::loadSettings(QSet
settings->setArrayIndex(ci);
ChannelInfo chanInfo(ci);
- chanInfo.name = settings->value(SG_Channels_Name, chanInfo.name).toString();
- chanInfo.color = settings->value(SG_Channels_Color, chanInfo.color).value();
- chanInfo.visibility = settings->value(SG_Channels_Visible, true).toBool();
+ chanInfo.name = settings->value(SG_Channels_Name , chanInfo.name).toString();
+ chanInfo.color = settings->value(SG_Channels_Color , chanInfo.color).value();
+ chanInfo.visibility = settings->value(SG_Channels_Visible , chanInfo.visibility).toBool();
+ chanInfo.gain = settings->value(SG_Channels_Gain , chanInfo.gain).toDouble();
+ chanInfo.gainEn = settings->value(SG_Channels_GainEn , chanInfo.gainEn).toBool();
+ chanInfo.offset = settings->value(SG_Channels_Offset , chanInfo.offset).toDouble();
+ chanInfo.offsetEn = settings->value(SG_Channels_OffsetEn , chanInfo.offsetEn).toBool();
if ((int) ci < infos.size())
{
@@ -385,6 +547,8 @@ void ChannelInfoModel::loadSettings(QSet
}
}
+ updateGainOrOffsetEn();
+
settings->endArray();
settings->endGroup();
}
diff --git a/src/channelinfomodel.h b/src/channelinfomodel.h
--- a/src/channelinfomodel.h
+++ b/src/channelinfomodel.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -34,7 +34,9 @@ public:
{
COLUMN_NAME = 0,
COLUMN_VISIBILITY,
- COLUMN_COUNT
+ COLUMN_GAIN,
+ COLUMN_OFFSET,
+ COLUMN_COUNT // MUST be last
};
explicit ChannelInfoModel(unsigned numberOfChannels, QObject *parent = 0);
@@ -44,6 +46,12 @@ public:
QString name (unsigned i) const;
QColor color (unsigned i) const;
bool isVisible(unsigned i) const;
+ bool gainEn (unsigned i) const;
+ double gain (unsigned i) const;
+ bool offsetEn (unsigned i) const;
+ double offset (unsigned i) const;
+ /// Returns true if any of the channels have gain or offset enabled
+ bool gainOrOffsetEn() const;
/// Returns a list of channel names
QStringList channelNames() const;
@@ -57,7 +65,7 @@ public:
void setNumOfChannels(unsigned number);
/// Stores all channel info into a `QSettings`
- void saveSettings(QSettings* settings);
+ void saveSettings(QSettings* settings) const;
/// Loads all channel info from a `QSettings`.
void loadSettings(QSettings* settings);
@@ -68,8 +76,12 @@ public slots:
void resetNames();
/// reset all channel colors
void resetColors();
+ /// reset all channel gain values and disables gains
+ void resetGains();
+ /// reset all channel offset values and disables offsets
+ void resetOffsets();
/// reset visibility
- void resetVisibility();
+ void resetVisibility(bool visible);
private:
struct ChannelInfo
@@ -79,6 +91,8 @@ private:
QString name;
bool visibility;
QColor color;
+ double gain, offset;
+ bool gainEn, offsetEn;
};
unsigned _numOfChannels; ///< @note this is not necessarily the length of `infos`
@@ -88,6 +102,16 @@ private:
* remember user entered info (names, colors etc.).
*/
QList infos;
+
+ /**
+ * Cache for gain and offset enabled variables of channels. If gain and/or
+ * offset is not enabled for *any* of the channels this is false otherwise
+ * true.
+ */
+ bool _gainOrOffsetEn;
+
+ /// Updates `_gainOrOffsetEn` by scanning all channel infos.
+ void updateGainOrOffsetEn();
};
#endif // CHANNELINFOMODEL_H
diff --git a/src/channelmanager.cpp b/src/channelmanager.cpp
deleted file mode 100644
--- a/src/channelmanager.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- Copyright © 2017 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 .
-*/
-
-#include
-
-#include
-
-#include "channelmanager.h"
-#include "setting_defines.h"
-
-ChannelManager::ChannelManager(unsigned numberOfChannels, unsigned numberOfSamples, QObject *parent) :
- QObject(parent),
- _infoModel(numberOfChannels)
-{
- _numOfChannels = numberOfChannels;
- _numOfSamples = numberOfSamples;
- _paused = false;
-
- for (unsigned int i = 0; i < numberOfChannels; i++)
- {
- channelBuffers.append(new FrameBuffer(numberOfSamples));
- }
-
- connect(&_infoModel, &ChannelInfoModel::dataChanged,
- this, &ChannelManager::onChannelInfoChanged);
-}
-
-ChannelManager::~ChannelManager()
-{
- for (auto buffer : channelBuffers)
- {
- delete buffer;
- }
-}
-
-unsigned ChannelManager::numOfChannels()
-{
- return channelBuffers.size();
-}
-
-unsigned ChannelManager::numOfSamples()
-{
- return _numOfSamples;
-}
-
-void ChannelManager::setNumOfChannels(unsigned number)
-{
- unsigned int oldNum = channelBuffers.size();
-
- if (number > oldNum)
- {
- // add new channels
- for (unsigned int i = 0; i < number - oldNum; i++)
- {
- channelBuffers.append(new FrameBuffer(_numOfSamples));
- }
- }
- else if(number < oldNum)
- {
- // remove channels
- for (unsigned int i = oldNum-1; i > number-1; i--)
- {
- delete channelBuffers.takeLast();
- }
- }
-
- _numOfChannels = number;
- _infoModel.setNumOfChannels(number);
-
- emit numOfChannelsChanged(number);
-}
-
-void ChannelManager::setNumOfSamples(unsigned number)
-{
- _numOfSamples = number;
-
- for (int ci = 0; ci < channelBuffers.size(); ci++)
- {
- channelBuffers[ci]->resize(_numOfSamples);
- }
-
- emit numOfSamplesChanged(number);
-}
-
-void ChannelManager::pause(bool paused)
-{
- _paused = paused;
-}
-
-FrameBuffer* ChannelManager::channelBuffer(unsigned channel)
-{
- return channelBuffers[channel];
-}
-
-ChannelInfoModel* ChannelManager::infoModel()
-{
- return &_infoModel;
-}
-
-QString ChannelManager::channelName(unsigned channel)
-{
- return _infoModel.data(_infoModel.index(channel, ChannelInfoModel::COLUMN_NAME),
- Qt::DisplayRole).toString();
-}
-
-QStringList ChannelManager::channelNames()
-{
- QStringList list;
- for (unsigned ci = 0; ci < _numOfChannels; ci++)
- {
- list << channelName(ci);
- }
- return list;
-}
-
-void ChannelManager::onChannelInfoChanged(const QModelIndex & topLeft,
- const QModelIndex & bottomRight,
- const QVector & roles)
-{
- int start = topLeft.row();
- int end = bottomRight.row();
- int col = topLeft.column();
-
- for (int ci = start; ci <= end; ci++)
- {
- for (auto role : roles)
- {
- switch (role)
- {
- case Qt::EditRole:
- if (col == ChannelInfoModel::COLUMN_NAME)
- {
- emit channelNameChanged(ci, channelName(ci));
- }
- break;
- case Qt::ForegroundRole:
- if (col == ChannelInfoModel::COLUMN_NAME)
- {
- // TODO: emit channel color changed
- }
- break;
- case Qt::CheckStateRole:
- if (col == ChannelInfoModel::COLUMN_VISIBILITY)
- {
- // TODO: emit visibility
- }
- break;
- }
- }
- // emit channelNameChanged(i, channelName(i));
- }
-}
-
-void ChannelManager::addData(double* data, unsigned size)
-{
- Q_ASSERT(size % _numOfChannels == 0);
-
- if (_paused) return;
-
- int n = size / _numOfChannels;
- for (unsigned ci = 0; ci < _numOfChannels; ci++)
- {
- channelBuffers[ci]->addSamples(&data[ci*n], n);
- }
-
- emit dataAdded();
-}
-
-void ChannelManager::saveSettings(QSettings* settings)
-{
- _infoModel.saveSettings(settings);
-}
-
-void ChannelManager::loadSettings(QSettings* settings)
-{
- _infoModel.loadSettings(settings);
-}
diff --git a/src/channelmanager.h b/src/channelmanager.h
deleted file mode 100644
--- a/src/channelmanager.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- Copyright © 2017 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 .
-*/
-
-#ifndef CHANNELMANAGER_H
-#define CHANNELMANAGER_H
-
-#include
-#include
-#include
-#include
-#include
-
-#include "framebuffer.h"
-#include "channelinfomodel.h"
-
-class ChannelManager : public QObject
-{
- Q_OBJECT
-public:
- explicit ChannelManager(unsigned numberOfChannels, unsigned numberOfSamples, QObject *parent = 0);
- ~ChannelManager();
-
- unsigned numOfChannels();
- unsigned numOfSamples();
- FrameBuffer* channelBuffer(unsigned channel);
- QString channelName(unsigned channel);
- /// Stores channel names into a `QSettings`
- void saveSettings(QSettings* settings);
- /// Loads channel names from a `QSettings`.
- void loadSettings(QSettings* settings);
- /// Returns a model that manages channel information (name, color etc)
- ChannelInfoModel* infoModel();
- /// Returns a list of channel names
- QStringList channelNames();
-
-signals:
- void numOfChannelsChanged(unsigned value);
- void numOfSamplesChanged(unsigned value);
- void channelNameChanged(unsigned channel, QString name);
- void dataAdded(); ///< emitted when data added to channel man.
-
-public slots:
- void setNumOfChannels(unsigned number);
- void setNumOfSamples(unsigned number);
- /**
- * Add data for all channels.
- *
- * All channels data is provided in a single array which contains equal
- * number of samples for all channels. Structure is as shown below:
- *
- * [CH0_SMP0, CH0_SMP1 ... CH0_SMPN, CH1_SMP0, CH1_SMP1, ... , CHN_SMPN]
- *
- * @param data samples for all channels
- * @param size size of `data`, must be multiple of `numOfChannels`
- */
- void addData(double* data, unsigned size);
-
- /// When paused `addData` does nothing.
- void pause(bool paused);
-
-private:
- unsigned _numOfChannels;
- unsigned _numOfSamples;
- bool _paused;
- QList channelBuffers;
- // QStringListModel _channelNames;
- ChannelInfoModel _infoModel;
-
- void addChannelName(QString name); ///< appends a new channel name at the end of list
-
-private slots:
- void onChannelInfoChanged(const QModelIndex & topLeft,
- const QModelIndex & bottomRight,
- const QVector & roles = QVector ());
-};
-
-#endif // CHANNELMANAGER_H
diff --git a/src/commandedit.cpp b/src/commandedit.cpp
--- a/src/commandedit.cpp
+++ b/src/commandedit.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -62,7 +62,7 @@ CommandEdit::CommandEdit(QWidget *parent
QLineEdit(parent)
{
hexValidator = new HexCommandValidator(this);
- asciiValidator = new QRegExpValidator(QRegExp("[\\x0000-\\x007F]+"));
+ asciiValidator = new QRegExpValidator(QRegExp("[\\x0000-\\x007F]+"), this);
ascii_mode = true;
setValidator(asciiValidator);
}
diff --git a/src/commandwidget.cpp b/src/commandwidget.cpp
--- a/src/commandwidget.cpp
+++ b/src/commandwidget.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -103,7 +103,14 @@ bool CommandWidget::isASCIIMode()
void CommandWidget::setASCIIMode(bool enabled)
{
- ui->pbASCII->setChecked(enabled);
+ if (enabled)
+ {
+ ui->pbASCII->setChecked(true);
+ }
+ else
+ {
+ ui->pbHEX->setChecked(true);
+ }
}
void CommandWidget::setName(QString name)
diff --git a/src/dataformatpanel.cpp b/src/dataformatpanel.cpp
--- a/src/dataformatpanel.cpp
+++ b/src/dataformatpanel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,41 +21,30 @@
#include "ui_dataformatpanel.h"
#include
-#include
-#include
#include
#include "utils.h"
#include "setting_defines.h"
-#include "floatswap.h"
-DataFormatPanel::DataFormatPanel(QSerialPort* port,
- ChannelManager* channelMan,
- DataRecorder* recorder,
- QWidget *parent) :
+DataFormatPanel::DataFormatPanel(QSerialPort* port, QWidget *parent) :
QWidget(parent),
ui(new Ui::DataFormatPanel),
- bsReader(port, channelMan, recorder, this),
- asciiReader(port, channelMan, recorder, this),
- framedReader(port, channelMan, recorder, this),
- demoReader(port, channelMan, recorder, this)
+ bsReader(port, this),
+ asciiReader(port, this),
+ framedReader(port, this),
+ demoReader(port, this)
{
ui->setupUi(this);
serialPort = port;
- _channelMan = channelMan;
paused = false;
- demoEnabled = false;
+ readerBeforeDemo = nullptr;
// initalize default reader
currentReader = &bsReader;
bsReader.enable();
ui->rbBinary->setChecked(true);
ui->horizontalLayout->addWidget(bsReader.settingsWidget(), 1);
- connect(&bsReader, SIGNAL(numOfChannelsChanged(unsigned)),
- this, SIGNAL(numOfChannelsChanged(unsigned)));
- connect(&bsReader, SIGNAL(samplesPerSecondChanged(unsigned)),
- this, SIGNAL(samplesPerSecondChanged(unsigned)));
// initalize reader selection buttons
connect(ui->rbBinary, &QRadioButton::toggled, [this](bool checked)
@@ -72,10 +61,6 @@ DataFormatPanel::DataFormatPanel(QSerial
{
if (checked) selectReader(&framedReader);
});
-
- // re-purpose numofchannels settings from actual reader settings to demo reader
- connect(this, &DataFormatPanel::numOfChannelsChanged,
- &demoReader, &DemoReader::setNumOfChannels);
}
DataFormatPanel::~DataFormatPanel()
@@ -83,9 +68,14 @@ DataFormatPanel::~DataFormatPanel()
delete ui;
}
-unsigned DataFormatPanel::numOfChannels()
+unsigned DataFormatPanel::numChannels() const
{
- return currentReader->numOfChannels();
+ return currentReader->numChannels();
+}
+
+Source* DataFormatPanel::activeSource()
+{
+ return currentReader;
}
void DataFormatPanel::pause(bool enabled)
@@ -95,33 +85,29 @@ void DataFormatPanel::pause(bool enabled
demoReader.pause(enabled);
}
-void DataFormatPanel::enableDemo(bool enabled)
+void DataFormatPanel::enableDemo(bool demoEnabled)
{
- if (enabled)
+ if (demoEnabled)
{
- demoReader.enable();
- demoReader.recording = currentReader->recording;
- connect(&demoReader, &DemoReader::samplesPerSecondChanged,
- this, &DataFormatPanel::samplesPerSecondChanged);
+ readerBeforeDemo = currentReader;
+ demoReader.setNumChannels(readerBeforeDemo->numChannels());
+ selectReader(&demoReader);
}
else
{
- demoReader.enable(false);
- disconnect(&demoReader, 0, this, 0);
+ Q_ASSERT(readerBeforeDemo != nullptr);
+ selectReader(readerBeforeDemo);
}
- demoEnabled = enabled;
+
+ // disable/enable reader selection buttons during/after demo
+ ui->rbAscii->setDisabled(demoEnabled);
+ ui->rbBinary->setDisabled(demoEnabled);
+ ui->rbFramed->setDisabled(demoEnabled);
}
-void DataFormatPanel::startRecording()
+bool DataFormatPanel::isDemoEnabled() const
{
- currentReader->recording = true;
- if (demoEnabled) demoReader.recording = true;
-}
-
-void DataFormatPanel::stopRecording()
-{
- currentReader->recording = false;
- if (demoEnabled) demoReader.recording = false;
+ return currentReader == &demoReader;
}
void DataFormatPanel::selectReader(AbstractReader* reader)
@@ -131,10 +117,6 @@ void DataFormatPanel::selectReader(Abstr
// re-connect signals
disconnect(currentReader, 0, this, 0);
- connect(reader, SIGNAL(numOfChannelsChanged(unsigned)),
- this, SIGNAL(numOfChannelsChanged(unsigned)));
- connect(reader, SIGNAL(samplesPerSecondChanged(unsigned)),
- this, SIGNAL(samplesPerSecondChanged(unsigned)));
// switch the settings widget
ui->horizontalLayout->removeWidget(currentReader->settingsWidget());
@@ -142,29 +124,24 @@ void DataFormatPanel::selectReader(Abstr
ui->horizontalLayout->addWidget(reader->settingsWidget(), 1);
reader->settingsWidget()->show();
- // notify if number of channels is different
- if (currentReader->numOfChannels() != reader->numOfChannels())
- {
- emit numOfChannelsChanged(reader->numOfChannels());
- }
-
reader->pause(paused);
- reader->recording = currentReader->recording;
currentReader = reader;
+ emit sourceChanged(currentReader);
}
void DataFormatPanel::saveSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_DataFormat);
- // save selected format
+ // save selected data format (current reader)
QString format;
- if (currentReader == &bsReader)
+ AbstractReader* selectedReader = isDemoEnabled() ? readerBeforeDemo : currentReader;
+ if (selectedReader == &bsReader)
{
format = "binary";
}
- else if (currentReader == &asciiReader)
+ else if (selectedReader == &asciiReader)
{
format = "ascii";
}
diff --git a/src/dataformatpanel.h b/src/dataformatpanel.h
--- a/src/dataformatpanel.h
+++ b/src/dataformatpanel.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -28,8 +28,6 @@
#include
#include
-#include "framebuffer.h"
-#include "channelmanager.h"
#include "binarystreamreader.h"
#include "asciireader.h"
#include "demoreader.h"
@@ -45,14 +43,13 @@ class DataFormatPanel : public QWidget
Q_OBJECT
public:
- explicit DataFormatPanel(QSerialPort* port,
- ChannelManager* channelMan,
- DataRecorder* recorder,
- QWidget* parent = 0);
+ explicit DataFormatPanel(QSerialPort* port, QWidget* parent = 0);
~DataFormatPanel();
/// Returns currently selected number of channels
- unsigned numOfChannels();
+ unsigned numChannels() const;
+ /// Returns active source (reader)
+ Source* activeSource();
/// Stores data format panel settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads data format panel settings from a `QSettings`.
@@ -62,25 +59,14 @@ public slots:
void pause(bool);
void enableDemo(bool); // demo shouldn't be enabled when port is open
- /**
- * @brief Starts sending data to recorder.
- *
- * @note recorder must have been started!
- */
- void startRecording();
-
- /// Stops recording.
- void stopRecording();
-
signals:
- void numOfChannelsChanged(unsigned);
- void samplesPerSecondChanged(unsigned);
+ /// Active (selected) reader has changed.
+ void sourceChanged(Source* source);
private:
Ui::DataFormatPanel *ui;
QSerialPort* serialPort;
- ChannelManager* _channelMan;
BinaryStreamReader bsReader;
AsciiReader asciiReader;
@@ -92,8 +78,10 @@ private:
bool paused;
- bool demoEnabled;
DemoReader demoReader;
+ AbstractReader* readerBeforeDemo;
+
+ bool isDemoEnabled() const;
};
#endif // DATAFORMATPANEL_H
diff --git a/src/datarecorder.cpp b/src/datarecorder.cpp
--- a/src/datarecorder.cpp
+++ b/src/datarecorder.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -19,6 +19,9 @@
#include "datarecorder.h"
+#include
+#include
+#include
#include
DataRecorder::DataRecorder(QObject *parent) :
@@ -28,12 +31,25 @@ DataRecorder::DataRecorder(QObject *pare
lastNumChannels = 0;
disableBuffering = false;
windowsLE = false;
+ timestampEn = false;
}
-bool DataRecorder::startRecording(QString fileName, QString separator, QStringList channelNames)
+bool DataRecorder::startRecording(QString fileName, QString separator,
+ QStringList channelNames, bool insertTime)
{
Q_ASSERT(!file.isOpen());
_sep = separator;
+ timestampEn = insertTime;
+
+ // create directory if it doesn't exist
+ {
+ QFileInfo fi(fileName);
+ if (!fi.dir().mkpath("."))
+ {
+ qCritical() << "Failed to create directory for: " << fileName;
+ return false;
+ }
+ }
// open file
file.setFileName(fileName);
@@ -47,6 +63,10 @@ bool DataRecorder::startRecording(QStrin
// write header line
if (!channelNames.isEmpty())
{
+ if (timestampEn)
+ {
+ fileStream << tr("timestamp") << _sep;
+ }
fileStream << channelNames.join(_sep);
fileStream << le();
lastNumChannels = channelNames.length();
@@ -54,26 +74,35 @@ bool DataRecorder::startRecording(QStrin
return true;
}
-void DataRecorder::addData(double* data, unsigned length, unsigned numOfChannels)
+void DataRecorder::feedIn(const SamplePack& data)
{
- Q_ASSERT(length > 0);
- Q_ASSERT(length % numOfChannels == 0);
+ Q_ASSERT(file.isOpen()); // recorder should be disconnected before stopping recording
+ Q_ASSERT(!data.hasX()); // NYI
- if (lastNumChannels != 0 && numOfChannels != lastNumChannels)
+ // check if number of channels has changed during recording and warn
+ unsigned numChannels = data.numChannels();
+ if (lastNumChannels != 0 && numChannels != lastNumChannels)
{
qWarning() << "Number of channels changed from " << lastNumChannels
- << " to " << numOfChannels <<
+ << " to " << numChannels <<
" during recording, CSV file is corrupted but no data will be lost.";
}
- lastNumChannels = numOfChannels;
+ lastNumChannels = numChannels;
- unsigned numOfSamples = length / numOfChannels; // per channel
- for (unsigned int i = 0; i < numOfSamples; i++)
+ // write data
+ qint64 timestamp;
+ if (timestampEn) timestamp = QDateTime::currentMSecsSinceEpoch();
+ unsigned numSamples = data.numSamples();
+ for (unsigned int i = 0; i < numSamples; i++)
{
- for (unsigned ci = 0; ci < numOfChannels; ci++)
+ if (timestampEn)
{
- fileStream << data[ci * numOfSamples + i];
- if (ci != numOfChannels-1) fileStream << _sep;
+ fileStream << timestamp << _sep;
+ }
+ for (unsigned ci = 0; ci < numChannels; ci++)
+ {
+ fileStream << data.data(ci)[i];
+ if (ci != numChannels-1) fileStream << _sep;
}
fileStream << le();
}
diff --git a/src/datarecorder.h b/src/datarecorder.h
--- a/src/datarecorder.h
+++ b/src/datarecorder.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -24,7 +24,15 @@
#include
#include
-class DataRecorder : public QObject
+#include "sink.h"
+
+/**
+ * Implemented as a `Sink` that writes incoming data to a file. Before
+ * connecting a `Source` recording must be started with the `startRecording`
+ * method. Also before calling `stopRecording`, recorder should be disconnected
+ * from source.
+ */
+class DataRecorder : public QObject, public Sink
{
Q_OBJECT
public:
@@ -44,14 +52,17 @@ public:
/**
* @brief Starts recording data to a file in CSV format.
*
- * File is opened and header line (names of channels) is written.
+ * File is opened and header line (names of channels) is written. After
+ * calling this function recorder should be connected to a `Source`.
*
* @param fileName name of the recording file
* @param separator column separator
* @param channelNames names of the channels for header line, if empty no header line is written
+ * @param insertTime enable inserting timestamp
* @return false if file operation fails (read only etc.)
*/
- bool startRecording(QString fileName, QString separator, QStringList channelNames);
+ bool startRecording(QString fileName, QString separator,
+ QStringList channelNames, bool insertTime);
/**
* @brief Adds data to a channel.
@@ -75,11 +86,15 @@ public:
/// Stops recording, closes file.
void stopRecording();
+protected:
+ virtual void feedIn(const SamplePack& data);
+
private:
unsigned lastNumChannels; ///< used for error message only
QFile file;
QTextStream fileStream;
QString _sep;
+ bool timestampEn;
/// Returns the selected line ending.
const char* le() const;
diff --git a/src/demoreader.cpp b/src/demoreader.cpp
--- a/src/demoreader.cpp
+++ b/src/demoreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -25,21 +25,23 @@
#define M_PI 3.14159265358979323846
#endif
-DemoReader::DemoReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject* parent) :
- AbstractReader(device, channelMan, recorder, parent)
+DemoReader::DemoReader(QIODevice* device, QObject* parent) :
+ AbstractReader(device, parent)
{
paused = false;
- _numOfChannels = 1;
+ _numChannels = _settingsWidget.numChannels();
+ connect(&_settingsWidget, &DemoReaderSettings::numChannelsChanged,
+ this, &DemoReader::onNumChannelsChanged);
+
count = 0;
timer.setInterval(100);
- QObject::connect(&timer, &QTimer::timeout,
- this, &DemoReader::demoTimerTimeout);
+ connect(&timer, &QTimer::timeout,
+ this, &DemoReader::demoTimerTimeout);
}
QWidget* DemoReader::settingsWidget()
{
- return NULL;
+ return &_settingsWidget;
}
void DemoReader::enable(bool enabled)
@@ -51,22 +53,18 @@ void DemoReader::enable(bool enabled)
else
{
timer.stop();
+ disconnectSinks();
}
}
-unsigned DemoReader::numOfChannels()
+unsigned DemoReader::numChannels() const
{
- return _numOfChannels;
+ return _numChannels;
}
-void DemoReader::setNumOfChannels(unsigned value)
+void DemoReader::setNumChannels(unsigned value)
{
- _numOfChannels = value;
-}
-
-void DemoReader::pause(bool enabled)
-{
- paused = enabled;
+ _settingsWidget.setNumChannels(value);
}
void DemoReader::demoTimerTimeout()
@@ -77,13 +75,23 @@ void DemoReader::demoTimerTimeout()
if (!paused)
{
- double* samples = new double[_numOfChannels];
- for (unsigned ci = 0; ci < _numOfChannels; ci++)
+ SamplePack samples(1, _numChannels);
+ for (unsigned ci = 0; ci < _numChannels; ci++)
{
// we are calculating the fourier components of square wave
- samples[ci] = 4*sin(2*M_PI*double((ci+1)*count)/period)/((2*(ci+1))*M_PI);
+ samples.data(ci)[0] = 4*sin(2*M_PI*double((ci+1)*count)/period)/((2*(ci+1))*M_PI);
}
- addData(samples, _numOfChannels);
- delete[] samples;
+ feedOut(samples);
}
}
+
+void DemoReader::onNumChannelsChanged(unsigned value)
+{
+ _numChannels = value;
+ updateNumChannels();
+}
+
+void DemoReader::onDataReady()
+{
+ // intentionally empty, required by AbstractReader
+}
diff --git a/src/demoreader.h b/src/demoreader.h
--- a/src/demoreader.h
+++ b/src/demoreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,6 +23,7 @@
#include
#include "abstractreader.h"
+#include "demoreadersettings.h"
/**
* This is a special case of reader implementation and should be used
@@ -38,30 +39,26 @@ class DemoReader : public AbstractReader
Q_OBJECT
public:
- explicit DemoReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject* parent = 0);
+ explicit DemoReader(QIODevice* device, QObject* parent = 0);
- /// Demo reader is an exception so this function returns NULL
QWidget* settingsWidget();
-
- unsigned numOfChannels();
-
- void enable(bool enabled = true);
+ unsigned numChannels() const;
+ void enable(bool enabled = true) override;
public slots:
- void pause(bool);
-
- /// Sets the number of channels, this doesn't trigger a `numOfChannelsChanged` signal.
- void setNumOfChannels(unsigned value);
+ void setNumChannels(unsigned value);
private:
- bool paused;
- unsigned _numOfChannels;
+ DemoReaderSettings _settingsWidget;
+
+ unsigned _numChannels;
QTimer timer;
int count;
private slots:
void demoTimerTimeout();
+ void onNumChannelsChanged(unsigned value);
+ void onDataReady() override;
};
#endif // DEMOREADER_H
diff --git a/src/demoreadersettings.cpp b/src/demoreadersettings.cpp
new file mode 100644
--- /dev/null
+++ b/src/demoreadersettings.cpp
@@ -0,0 +1,51 @@
+/*
+ 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 .
+*/
+
+#include "demoreadersettings.h"
+#include "ui_demoreadersettings.h"
+
+#include "utils.h"
+
+DemoReaderSettings::DemoReaderSettings(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::DemoReaderSettings)
+{
+ ui->setupUi(this);
+
+ connect(ui->spNumChannels, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged),
+ [this](int value)
+ {
+ emit numChannelsChanged(value);
+ });
+}
+
+DemoReaderSettings::~DemoReaderSettings()
+{
+ delete ui;
+}
+
+unsigned DemoReaderSettings::numChannels() const
+{
+ return ui->spNumChannels->value();
+}
+
+void DemoReaderSettings::setNumChannels(unsigned value)
+{
+ ui->spNumChannels->setValue(value);
+}
diff --git a/src/demoreadersettings.h b/src/demoreadersettings.h
new file mode 100644
--- /dev/null
+++ b/src/demoreadersettings.h
@@ -0,0 +1,48 @@
+/*
+ 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 .
+*/
+
+#ifndef DEMOREADERSETTINGS_H
+#define DEMOREADERSETTINGS_H
+
+#include
+
+namespace Ui {
+class DemoReaderSettings;
+}
+
+class DemoReaderSettings : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit DemoReaderSettings(QWidget *parent = 0);
+ ~DemoReaderSettings();
+
+ unsigned numChannels() const;
+ /// Doesn't signal `numChannelsChanged`.
+ void setNumChannels(unsigned value);
+
+private:
+ Ui::DemoReaderSettings *ui;
+
+signals:
+ void numChannelsChanged(unsigned);
+};
+
+#endif // DEMOREADERSETTINGS_H
diff --git a/src/demoreadersettings.ui b/src/demoreadersettings.ui
new file mode 100644
--- /dev/null
+++ b/src/demoreadersettings.ui
@@ -0,0 +1,83 @@
+
+
+ DemoReaderSettings
+
+
+
+ 0
+ 0
+ 444
+ 141
+
+
+
+ Form
+
+
+ -
+
+
+ QFormLayout::ExpandingFieldsGrow
+
+
-
+
+
+ Number Of Channels:
+
+
+
+ -
+
+
+
+ 60
+ 0
+
+
+
+ Select number of channels or set to 0 for Auto (determined from incoming data)
+
+
+
+
+
+ false
+
+
+ 1
+
+
+ 32
+
+
+ 1
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Demo is enabled, exit demo for reader settings.
+
+
+
+
+
+
+
+
diff --git a/src/framebuffer.cpp b/src/framebuffer.cpp
deleted file mode 100644
--- a/src/framebuffer.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- Copyright © 2015 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 .
-*/
-
-#include "framebuffer.h"
-
-FrameBuffer::FrameBuffer(size_t size)
-{
- _size = size;
- data = new double[_size]();
- headIndex = 0;
-
- _boundingRect.setCoords(0, 0, size, 0);
-}
-
-FrameBuffer::~FrameBuffer()
-{
- delete[] data;
-}
-
-void FrameBuffer::resize(size_t size)
-{
- int offset = size - _size;
- if (offset == 0) return;
-
- double* newData = new double[size];
-
- // move data to new array
- int fill_start = offset > 0 ? offset : 0;
-
- for (int i = fill_start; i < int(size); i++)
- {
- newData[i] = sample(i - offset);
- }
-
- // fill the beginning of the new data
- if (fill_start > 0)
- {
- for (int i = 0; i < fill_start; i++)
- {
- newData[i] = 0;
- }
- }
-
- // data is ready, clean and re-point
- delete data;
- data = newData;
- headIndex = 0;
- _size = size;
-
- // update the bounding rectangle
- _boundingRect.setRight(_size);
-}
-
-void FrameBuffer::addSamples(double* samples, size_t size)
-{
- unsigned shift = size;
- if (shift < _size)
- {
- unsigned x = _size - headIndex; // distance of `head` to end
-
- if (shift <= x) // there is enough room at the end of array
- {
- for (size_t i = 0; i < shift; i++)
- {
- data[i+headIndex] = samples[i];
- }
-
- if (shift == x) // we used all the room at the end
- {
- headIndex = 0;
- }
- else
- {
- headIndex += shift;
- }
- }
- else // there isn't enough room
- {
- for (size_t i = 0; i < x; i++) // fill the end part
- {
- data[i+headIndex] = samples[i];
- }
- for (size_t i = 0; i < (shift-x); i++) // continue from the beginning
- {
- data[i] = samples[i+x];
- }
- headIndex = shift-x;
- }
- }
- else // number of new samples equal or bigger than current size
- {
- int x = shift - _size;
- for (size_t i = 0; i < _size; i++)
- {
- data[i] = samples[i+x];
- }
- headIndex = 0;
- }
-
- // update bounding rectangle
- double minValue = data[0];
- double maxValue = data[0];
- for (size_t i = 0; i < _size; i++)
- {
- if (data[i] > maxValue)
- {
- maxValue = data[i];
- }
- else if (data[i] < minValue)
- {
- minValue = data[i];
- }
- }
- _boundingRect.setTop(minValue);
- _boundingRect.setBottom(maxValue);
-}
-
-void FrameBuffer::clear()
-{
- for (size_t i=0; i < _size; i++) data[i] = 0.;
-}
-
-size_t FrameBuffer::size() const
-{
- return _size;
-}
-
-QRectF FrameBuffer::boundingRect() const
-{
- return _boundingRect;
-}
-
-double FrameBuffer::sample(size_t i) const
-{
- size_t index = headIndex + i;
- if (index >= _size) index -= _size;
- return data[index];
-}
diff --git a/src/framebuffer.h b/src/framebuffer.h
--- a/src/framebuffer.h
+++ b/src/framebuffer.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2015 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,33 +17,48 @@
along with serialplot. If not, see .
*/
+// IMPORTANT NOTE: this file will be renamed to "framebuffer.h" when
+// stream work is complete
+
#ifndef FRAMEBUFFER_H
#define FRAMEBUFFER_H
-#include
-#include
+struct Range
+{
+ double start, end;
+};
+/// Abstract base class for all frame buffers.
class FrameBuffer
{
public:
- FrameBuffer(size_t size);
- ~FrameBuffer();
-
- void resize(size_t size);
- void addSamples(double* samples, size_t size);
- void clear(); // fill 0
+ /// Placeholder virtual destructor
+ virtual ~FrameBuffer() {};
+ /// Returns size of the buffer.
+ virtual unsigned size() const = 0;
+ /// Returns a sample from given index.
+ virtual double sample(unsigned i) const = 0;
+ /// Returns minimum and maximum of the buffer values.
+ virtual Range limits() const = 0;
+};
- // QwtSeriesData related implementations
- size_t size() const;
- QRectF boundingRect() const;
- double sample(size_t i) const;
+/// Common base class for index and writable frame buffers
+class ResizableBuffer : public FrameBuffer
+{
+public:
+ /// Resize the buffer.
+ ///
+ /// @important Resizing to same value is an error.
+ virtual void resize(unsigned n) = 0;
+};
-private:
- size_t _size; // size of `data`
- double* data;
- size_t headIndex; // indicates the actual `0` index of the ring buffer
-
- QRectF _boundingRect;
+/// 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;
};
#endif // FRAMEBUFFER_H
diff --git a/src/framebufferseries.cpp b/src/framebufferseries.cpp
--- a/src/framebufferseries.cpp
+++ b/src/framebufferseries.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,14 +17,17 @@
along with serialplot. If not, see .
*/
+#include
#include "framebufferseries.h"
-FrameBufferSeries::FrameBufferSeries(FrameBuffer* buffer)
+FrameBufferSeries::FrameBufferSeries(const FrameBuffer* buffer)
{
xAsIndex = true;
_xmin = 0;
_xmax = 1;
_buffer = buffer;
+ int_index_start = 0;
+ int_index_end = _buffer->size();
}
void FrameBufferSeries::setXAxis(bool asIndex, double xmin, double xmax)
@@ -36,32 +39,56 @@ void FrameBufferSeries::setXAxis(bool as
size_t FrameBufferSeries::size() const
{
- return _buffer->size();
+ return int_index_end - int_index_start;
}
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) / size() + _xmin, _buffer->sample(i));
+ return QPointF(i * (_xmax - _xmin) / _buffer->size() + _xmin, _buffer->sample(i));
}
}
QRectF FrameBufferSeries::boundingRect() const
{
+ QRectF rect;
+ auto yLim = _buffer->limits();
+ rect.setBottom(yLim.start);
+ rect.setTop(yLim.end);
if (xAsIndex)
{
- return _buffer->boundingRect();
+ rect.setLeft(0);
+ rect.setRight(size());
}
else
{
- auto rect = _buffer->boundingRect();
rect.setLeft(_xmin);
rect.setRight(_xmax);
- return rect;
+ }
+ return rect.normalized();
+}
+
+void FrameBufferSeries::setRectOfInterest(const QRectF& rect)
+{
+ if (xAsIndex)
+ {
+ int_index_start = floor(rect.left())-1;
+ int_index_end = ceil(rect.right())+1;
}
+ else
+ {
+ 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 = std::max(int_index_start, 0);
+ int_index_end = std::min((int) _buffer->size(), int_index_end);
}
diff --git a/src/framebufferseries.h b/src/framebufferseries.h
--- a/src/framebufferseries.h
+++ b/src/framebufferseries.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -35,7 +35,7 @@
class FrameBufferSeries : public QwtSeriesData
{
public:
- FrameBufferSeries(FrameBuffer* buffer);
+ FrameBufferSeries(const FrameBuffer* buffer);
/// Behavior of X axis
void setXAxis(bool asIndex, double xmin, double xmax);
@@ -44,12 +44,16 @@ public:
size_t size() const;
QPointF sample(size_t i) const;
QRectF boundingRect() const;
+ void setRectOfInterest(const QRectF& rect);
private:
- FrameBuffer* _buffer;
+ const FrameBuffer* _buffer;
bool xAsIndex;
double _xmin;
double _xmax;
+
+ int int_index_start; ///< starting index of "rectangle of interest"
+ int int_index_end; ///< ending index of "rectangle of interest"
};
#endif // FRAMEBUFFERSERIES_H
diff --git a/src/framedreader.cpp b/src/framedreader.cpp
--- a/src/framedreader.cpp
+++ b/src/framedreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,15 +23,14 @@
#include "framedreader.h"
-FramedReader::FramedReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject* parent) :
- AbstractReader(device, channelMan, recorder, parent)
+FramedReader::FramedReader(QIODevice* device, QObject* parent) :
+ AbstractReader(device, parent)
{
paused = false;
// initial settings
settingsInvalid = 0;
- _numOfChannels = _settingsWidget.numOfChannels();
+ _numChannels = _settingsWidget.numOfChannels();
hasSizeByte = _settingsWidget.frameSize() == 0;
frameSize = _settingsWidget.frameSize();
syncWord = _settingsWidget.syncWord();
@@ -63,32 +62,14 @@ FramedReader::FramedReader(QIODevice* de
reset();
}
-void FramedReader::enable(bool enabled)
-{
- if (enabled)
- {
- connect(_device, &QIODevice::readyRead,
- this, &FramedReader::onDataReady);
- }
- else
- {
- QObject::disconnect(_device, 0, this, 0);
- }
-}
-
QWidget* FramedReader::settingsWidget()
{
return &_settingsWidget;
}
-unsigned FramedReader::numOfChannels()
+unsigned FramedReader::numChannels() const
{
- return _numOfChannels;
-}
-
-void FramedReader::pause(bool enabled)
-{
- paused = enabled;
+ return _numChannels;
}
void FramedReader::onNumberFormatChanged(NumberFormat numberFormat)
@@ -145,7 +126,7 @@ void FramedReader::checkSettings()
}
// check if fixed frame size is multiple of a sample set size
- if (!hasSizeByte && frameSize % (_numOfChannels * sampleSize) != 0)
+ if (!hasSizeByte && frameSize % (_numChannels * sampleSize) != 0)
{
settingsInvalid |= FRAMESIZE_INVALID;
}
@@ -163,7 +144,7 @@ void FramedReader::checkSettings()
{
QString errorMessage =
QString("Frame size must be multiple of %1 (#channels * sample size)!")\
- .arg(_numOfChannels * sampleSize);
+ .arg(_numChannels * sampleSize);
_settingsWidget.showMessage(errorMessage, true);
}
@@ -175,7 +156,7 @@ void FramedReader::checkSettings()
void FramedReader::onNumOfChannelsChanged(unsigned value)
{
- _numOfChannels = value;
+ _numChannels = value;
checkSettings();
reset();
emit numOfChannelsChanged(value);
@@ -238,11 +219,11 @@ void FramedReader::onDataReady()
qCritical() << "Frame size is 0!";
reset();
}
- else if (frameSize % (_numOfChannels * sampleSize) != 0)
+ else if (frameSize % (_numChannels * sampleSize) != 0)
{
qCritical() <<
QString("Frame size is not multiple of %1 (#channels * sample size)!") \
- .arg(_numOfChannels * sampleSize);
+ .arg(_numChannels * sampleSize);
reset();
}
else
@@ -287,14 +268,13 @@ void FramedReader::readFrameDataAndCheck
}
// a package is 1 set of samples for all channels
- unsigned numOfPackagesToRead = frameSize / (_numOfChannels * sampleSize);
- double* channelSamples = new double[numOfPackagesToRead * _numOfChannels];
-
+ unsigned numOfPackagesToRead = frameSize / (_numChannels * sampleSize);
+ SamplePack samples(numOfPackagesToRead, _numChannels);
for (unsigned i = 0; i < numOfPackagesToRead; i++)
{
- for (unsigned int ci = 0; ci < _numOfChannels; ci++)
+ for (unsigned int ci = 0; ci < _numChannels; ci++)
{
- channelSamples[ci*numOfPackagesToRead+i] = (this->*readSample)();
+ samples.data(ci)[i] = (this->*readSample)();
}
}
@@ -311,14 +291,12 @@ void FramedReader::readFrameDataAndCheck
if (!checksumEnabled || checksumPassed)
{
// commit data
- addData(channelSamples, numOfPackagesToRead*_numOfChannels);
+ feedOut(samples);
}
else
{
qCritical() << "Checksum failed! Received:" << rChecksum << "Calculated:" << calcChecksum;
}
-
- delete[] channelSamples;
}
template double FramedReader::readSampleAs()
diff --git a/src/framedreader.h b/src/framedreader.h
--- a/src/framedreader.h
+++ b/src/framedreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -33,19 +33,14 @@ class FramedReader : public AbstractRead
Q_OBJECT
public:
- explicit FramedReader(QIODevice* device, ChannelManager* channelMan,
- DataRecorder* recorder, QObject *parent = 0);
+ explicit FramedReader(QIODevice* device, QObject *parent = 0);
QWidget* settingsWidget();
- unsigned numOfChannels();
- void enable(bool enabled = true);
+ unsigned numChannels() const;
/// Stores settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads settings from a `QSettings`.
void loadSettings(QSettings* settings);
-public slots:
- void pause(bool);
-
private:
/// bit wise fields for `settingsValid` member
enum SettingInvalidFlag
@@ -56,9 +51,8 @@ private:
// settings related members
FramedReaderSettings _settingsWidget;
- unsigned _numOfChannels;
+ unsigned _numChannels;
unsigned sampleSize;
- bool paused;
unsigned settingsInvalid; /// settings are all valid if this is 0, if not no reading is done
QByteArray syncWord;
bool checksumEnabled;
@@ -86,7 +80,7 @@ private:
void readFrameDataAndCheck();
private slots:
- void onDataReady();
+ void onDataReady() override;
void onNumberFormatChanged(NumberFormat numberFormat);
void onNumOfChannelsChanged(unsigned value);
diff --git a/src/indexbuffer.cpp b/src/indexbuffer.cpp
new file mode 100644
--- /dev/null
+++ b/src/indexbuffer.cpp
@@ -0,0 +1,49 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#include
+
+#include "indexbuffer.h"
+
+IndexBuffer::IndexBuffer(unsigned n)
+{
+ _size = n;
+}
+
+unsigned IndexBuffer::size() const
+{
+ return _size;
+}
+
+void IndexBuffer::resize(unsigned n)
+{
+ _size = n;
+}
+
+double IndexBuffer::sample(unsigned i) const
+{
+ Q_ASSERT(i < _size);
+
+ return i;
+}
+
+Range IndexBuffer::limits() const
+{
+ return Range{0, _size-1.};
+}
diff --git a/src/indexbuffer.h b/src/indexbuffer.h
new file mode 100644
--- /dev/null
+++ b/src/indexbuffer.h
@@ -0,0 +1,43 @@
+/*
+ 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 .
+*/
+
+#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
+{
+public:
+ IndexBuffer(unsigned n);
+
+ unsigned size() const;
+ double sample(unsigned i) const;
+ Range limits() const;
+ void resize(unsigned n);
+
+private:
+ unsigned _size;
+};
+
+#endif
diff --git a/src/linindexbuffer.cpp b/src/linindexbuffer.cpp
new file mode 100644
--- /dev/null
+++ b/src/linindexbuffer.cpp
@@ -0,0 +1,57 @@
+ /*
+ Copyright © 2017 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 .
+*/
+
+#include
+
+#include "linindexbuffer.h"
+
+LinIndexBuffer::LinIndexBuffer(unsigned n, Range lim)
+{
+ Q_ASSERT(n > 0);
+
+ _size = n;
+ setLimits(lim);
+}
+
+unsigned LinIndexBuffer::size() const
+{
+ return _size;
+}
+
+double LinIndexBuffer::sample(unsigned i) const
+{
+ return _limits.start + i * _step;
+}
+
+Range LinIndexBuffer::limits() const
+{
+ return _limits;
+}
+
+void LinIndexBuffer::resize(unsigned n)
+{
+ _size = n;
+ setLimits(_limits); // called to update `_step`
+}
+
+void LinIndexBuffer::setLimits(Range lim)
+{
+ _limits = lim;
+ _step = (lim.end - lim.start) / (_size-1);
+}
diff --git a/src/linindexbuffer.h b/src/linindexbuffer.h
new file mode 100644
--- /dev/null
+++ b/src/linindexbuffer.h
@@ -0,0 +1,49 @@
+ /*
+ 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 .
+*/
+
+#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
+{
+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);
+ /// Sets minimum and maximum sample values of the buffer.
+ void setLimits(Range lim);
+
+private:
+ unsigned _size;
+ Range _limits;
+ double _step;
+};
+
+#endif
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -35,6 +35,7 @@
#include
#include
+#include
#include "framebufferseries.h"
#include "utils.h"
@@ -47,11 +48,14 @@
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#endif
+// TODO: depends on tab insertion order, a better solution would be to use object names
const QMap panelSettingMap({
{0, "Port"},
{1, "DataFormat"},
{2, "Plot"},
- {3, "Commands"}
+ {3, "Commands"},
+ {4, "Record"},
+ {5, "Log"}
});
MainWindow::MainWindow(QWidget *parent) :
@@ -59,15 +63,16 @@ MainWindow::MainWindow(QWidget *parent)
ui(new Ui::MainWindow),
aboutDialog(this),
portControl(&serialPort),
- channelMan(1, 1, this),
- snapshotMan(this, &channelMan),
+ secondaryPlot(NULL),
+ snapshotMan(this, &stream),
commandPanel(&serialPort),
- dataFormatPanel(&serialPort, &channelMan, &recorder),
- recordPanel(&recorder, &channelMan)
+ dataFormatPanel(&serialPort),
+ recordPanel(&stream),
+ updateCheckDialog(this)
{
ui->setupUi(this);
- plotMan = new PlotManager(ui->plotArea, channelMan.infoModel());
+ plotMan = new PlotManager(ui->plotArea, &plotMenu, &stream);
ui->tabWidget->insertTab(0, &portControl, "Port");
ui->tabWidget->insertTab(1, &dataFormatPanel, "Data Format");
@@ -80,8 +85,8 @@ MainWindow::MainWindow(QWidget *parent)
addToolBar(recordPanel.toolbar());
ui->plotToolBar->addAction(snapshotMan.takeSnapshotAction());
- ui->menuBar->insertMenu(ui->menuHelp->menuAction(), snapshotMan.menu());
- ui->menuBar->insertMenu(ui->menuHelp->menuAction(), commandPanel.menu());
+ menuBar()->insertMenu(ui->menuHelp->menuAction(), snapshotMan.menu());
+ menuBar()->insertMenu(ui->menuHelp->menuAction(), commandPanel.menu());
connect(&commandPanel, &CommandPanel::focusRequested, [this]()
{
@@ -95,23 +100,42 @@ MainWindow::MainWindow(QWidget *parent)
setupAboutDialog();
// init view menu
- for (auto a : plotMan->menuActions())
- {
- ui->menuView->addAction(a);
- }
-
- ui->menuView->addSeparator();
-
- QMenu* tbMenu = ui->menuView->addMenu("Toolbars");
+ ui->menuBar->insertMenu(ui->menuSecondary->menuAction(), &plotMenu);
+ plotMenu.addSeparator();
+ QMenu* tbMenu = plotMenu.addMenu("Toolbars");
tbMenu->addAction(ui->plotToolBar->toggleViewAction());
tbMenu->addAction(portControl.toolBar()->toggleViewAction());
+ // init secondary plot menu
+ auto group = new QActionGroup(this);
+ group->addAction(ui->actionVertical);
+ group->addAction(ui->actionHorizontal);
+
// init UI signals
+ // Secondary plot menu signals
+ connect(ui->actionBarPlot, &QAction::triggered,
+ this, &MainWindow::showBarPlot);
+
+ connect(ui->actionVertical, &QAction::triggered,
+ [this](bool checked)
+ {
+ if (checked) ui->splitter->setOrientation(Qt::Vertical);
+ });
+
+ connect(ui->actionHorizontal, &QAction::triggered,
+ [this](bool checked)
+ {
+ if (checked) ui->splitter->setOrientation(Qt::Horizontal);
+ });
+
// Help menu signals
QObject::connect(ui->actionHelpAbout, &QAction::triggered,
&aboutDialog, &QWidget::show);
+ QObject::connect(ui->actionCheckUpdate, &QAction::triggered,
+ &updateCheckDialog, &QWidget::show);
+
QObject::connect(ui->actionReportBug, &QAction::triggered,
[](){QDesktopServices::openUrl(QUrl(BUG_REPORT_URL));});
@@ -147,28 +171,18 @@ MainWindow::MainWindow(QWidget *parent)
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);
- // init port signals
- QObject::connect(&(this->serialPort), SIGNAL(error(QSerialPort::SerialPortError)),
- this, SLOT(onPortError(QSerialPort::SerialPortError)));
-
- // init data format and reader
- QObject::connect(&channelMan, &ChannelManager::dataAdded,
- plotMan, &PlotManager::replot);
-
QObject::connect(ui->actionPause, &QAction::triggered,
- &channelMan, &ChannelManager::pause);
-
- QObject::connect(&recordPanel, &RecordPanel::recordStarted,
- &dataFormatPanel, &DataFormatPanel::startRecording);
-
- QObject::connect(&recordPanel, &RecordPanel::recordStopped,
- &dataFormatPanel, &DataFormatPanel::stopRecording);
+ &stream, &Stream::pause);
QObject::connect(ui->actionPause, &QAction::triggered,
[this](bool enabled)
@@ -195,26 +209,10 @@ MainWindow::MainWindow(QWidget *parent)
connect(&serialPort, &QIODevice::aboutToClose,
&recordPanel, &RecordPanel::onPortClose);
- // init data arrays and plot
+ // init plot
numOfSamples = plotControlPanel.numOfSamples();
- unsigned numOfChannels = dataFormatPanel.numOfChannels();
-
- channelMan.setNumOfSamples(numOfSamples);
- channelMan.setNumOfChannels(dataFormatPanel.numOfChannels());
-
- connect(&dataFormatPanel, &DataFormatPanel::numOfChannelsChanged,
- &channelMan, &ChannelManager::setNumOfChannels);
-
- connect(&channelMan, &ChannelManager::numOfChannelsChanged,
- this, &MainWindow::onNumOfChannelsChanged);
-
- plotControlPanel.setChannelInfoModel(channelMan.infoModel());
-
- // init curve list
- for (unsigned int i = 0; i < numOfChannels; i++)
- {
- plotMan->addCurve(channelMan.channelName(i), channelMan.channelBuffer(i));
- }
+ stream.setNumSamples(numOfSamples);
+ plotControlPanel.setChannelInfoModel(stream.infoModel());
// init scales
plotMan->setYAxis(plotControlPanel.autoScale(),
@@ -222,14 +220,14 @@ MainWindow::MainWindow(QWidget *parent)
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);
- QObject::connect(&dataFormatPanel,
- &DataFormatPanel::samplesPerSecondChanged,
- this, &MainWindow::onSpsChanged);
+ connect(&sampleCounter, &SampleCounter::spsChanged,
+ this, &MainWindow::onSpsChanged);
// init demo
QObject::connect(ui->actionDemoMode, &QAction::toggled,
@@ -238,6 +236,11 @@ MainWindow::MainWindow(QWidget *parent)
QObject::connect(ui->actionDemoMode, &QAction::toggled,
plotMan, &PlotManager::showDemoIndicator);
+ // init stream connections
+ connect(&dataFormatPanel, &DataFormatPanel::sourceChanged,
+ this, &MainWindow::onSourceChanged);
+ onSourceChanged(dataFormatPanel.activeSource());
+
// load default settings
QSettings settings("serialplot", "serialplot");
loadAllSettings(&settings);
@@ -279,8 +282,7 @@ void MainWindow::closeEvent(QCloseEvent
auto clickedButton = QMessageBox::warning(
this, "Closing SerialPlot",
"There are un-saved snapshots. If you close you will loose the data.",
- QMessageBox::Discard | QMessageBox::Discard,
- QMessageBox::Cancel);
+ QMessageBox::Discard, QMessageBox::Cancel);
if (clickedButton == QMessageBox::Cancel)
{
event->ignore();
@@ -346,104 +348,29 @@ void MainWindow::onPortToggled(bool open
ui->actionDemoMode->setEnabled(!open);
}
-void MainWindow::onPortError(QSerialPort::SerialPortError error)
+void MainWindow::onSourceChanged(Source* source)
{
- switch(error)
- {
- case QSerialPort::NoError :
- break;
- case QSerialPort::ResourceError :
- qWarning() << "Port error: resource unavaliable; most likely device removed.";
- if (serialPort.isOpen())
- {
- qWarning() << "Closing port on resource error: " << serialPort.portName();
- portControl.togglePort();
- }
- portControl.loadPortList();
- break;
- case QSerialPort::DeviceNotFoundError:
- qCritical() << "Device doesn't exists: " << serialPort.portName();
- break;
- case QSerialPort::PermissionError:
- qCritical() << "Permission denied. Either you don't have \
-required privileges or device is already opened by another process.";
- break;
- case QSerialPort::OpenError:
- qWarning() << "Device is already opened!";
- break;
- case QSerialPort::NotOpenError:
- qCritical() << "Device is not open!";
- break;
- case QSerialPort::ParityError:
- qCritical() << "Parity error detected.";
- break;
- case QSerialPort::FramingError:
- qCritical() << "Framing error detected.";
- break;
- case QSerialPort::BreakConditionError:
- qCritical() << "Break condition is detected.";
- break;
- case QSerialPort::WriteError:
- qCritical() << "An error occurred while writing data.";
- break;
- case QSerialPort::ReadError:
- qCritical() << "An error occurred while reading data.";
- break;
- case QSerialPort::UnsupportedOperationError:
- qCritical() << "Operation is not supported.";
- break;
- case QSerialPort::TimeoutError:
- qCritical() << "A timeout error occurred.";
- break;
- case QSerialPort::UnknownError:
- qCritical() << "Unknown error! Error: " << serialPort.errorString();
- break;
- default:
- qCritical() << "Unhandled port error: " << error;
- break;
- }
+ source->connectSink(&stream);
+ source->connectSink(&sampleCounter);
}
void MainWindow::clearPlot()
{
- for (unsigned ci = 0; ci < channelMan.numOfChannels(); ci++)
- {
- channelMan.channelBuffer(ci)->clear();
- }
+ stream.clear();
plotMan->replot();
}
void MainWindow::onNumOfSamplesChanged(int value)
{
numOfSamples = value;
- channelMan.setNumOfSamples(value);
+ stream.setNumSamples(value);
plotMan->replot();
}
-void MainWindow::onNumOfChannelsChanged(unsigned value)
+void MainWindow::onSpsChanged(float sps)
{
- unsigned int oldNum = plotMan->numOfCurves();
- unsigned numOfChannels = value;
-
- if (numOfChannels > oldNum)
- {
- // add new channels
- for (unsigned int i = oldNum; i < numOfChannels; i++)
- {
- plotMan->addCurve(channelMan.channelName(i), channelMan.channelBuffer(i));
- }
- }
- else if(numOfChannels < oldNum)
- {
- plotMan->removeCurves(oldNum - numOfChannels);
- }
-
- plotMan->replot();
-}
-
-void MainWindow::onSpsChanged(unsigned sps)
-{
- spsLabel.setText(QString::number(sps) + "sps");
+ int precision = sps < 1. ? 3 : 0;
+ spsLabel.setText(QString::number(sps, 'f', precision) + "sps");
}
bool MainWindow::isDemoRunning()
@@ -471,6 +398,48 @@ void MainWindow::enableDemo(bool enabled
}
}
+void MainWindow::showSecondary(QWidget* wid)
+{
+ if (secondaryPlot != NULL)
+ {
+ secondaryPlot->deleteLater();
+ }
+
+ secondaryPlot = wid;
+ ui->splitter->addWidget(wid);
+ ui->splitter->setStretchFactor(0, 1);
+ ui->splitter->setStretchFactor(1, 0);
+}
+
+void MainWindow::hideSecondary()
+{
+ if (secondaryPlot == NULL)
+ {
+ qFatal("Secondary plot doesn't exist!");
+ }
+
+ secondaryPlot->deleteLater();
+ secondaryPlot = NULL;
+}
+
+void MainWindow::showBarPlot(bool show)
+{
+ if (show)
+ {
+ auto plot = new BarPlot(&stream, &plotMenu);
+ plot->setYAxis(plotControlPanel.autoScale(),
+ plotControlPanel.yMin(),
+ plotControlPanel.yMax());
+ connect(&plotControlPanel, &PlotControlPanel::yScaleChanged,
+ plot, &BarPlot::setYAxis);
+ showSecondary(plot);
+ }
+ else
+ {
+ hideSecondary();
+ }
+}
+
void MainWindow::onExportCsv()
{
bool wasPaused = ui->actionPause->isChecked();
@@ -492,7 +461,7 @@ void MainWindow::onExportCsv()
PlotViewSettings MainWindow::viewSettings() const
{
- return plotMan->viewSettings();
+ return plotMenu.viewSettings();
}
void MainWindow::messageHandler(QtMsgType type,
@@ -541,11 +510,12 @@ void MainWindow::saveAllSettings(QSettin
saveMWSettings(settings);
portControl.saveSettings(settings);
dataFormatPanel.saveSettings(settings);
- channelMan.saveSettings(settings);
+ stream.saveSettings(settings);
plotControlPanel.saveSettings(settings);
- plotMan->saveSettings(settings);
+ plotMenu.saveSettings(settings);
commandPanel.saveSettings(settings);
recordPanel.saveSettings(settings);
+ updateCheckDialog.saveSettings(settings);
}
void MainWindow::loadAllSettings(QSettings* settings)
@@ -553,11 +523,12 @@ void MainWindow::loadAllSettings(QSettin
loadMWSettings(settings);
portControl.loadSettings(settings);
dataFormatPanel.loadSettings(settings);
- channelMan.loadSettings(settings);
+ stream.loadSettings(settings);
plotControlPanel.loadSettings(settings);
- plotMan->loadSettings(settings);
+ plotMenu.loadSettings(settings);
commandPanel.loadSettings(settings);
recordPanel.loadSettings(settings);
+ updateCheckDialog.loadSettings(settings);
}
void MainWindow::saveMWSettings(QSettings* settings)
diff --git a/src/mainwindow.h b/src/mainwindow.h
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -40,11 +40,12 @@
#include "plotcontrolpanel.h"
#include "recordpanel.h"
#include "ui_about_dialog.h"
-#include "framebuffer.h"
-#include "channelmanager.h"
+#include "stream.h"
#include "snapshotmanager.h"
#include "plotmanager.h"
-#include "datarecorder.h"
+#include "plotmenu.h"
+#include "updatecheckdialog.h"
+#include "samplecounter.h"
namespace Ui {
class MainWindow;
@@ -75,18 +76,28 @@ private:
unsigned int numOfSamples;
QList curves;
- ChannelManager channelMan;
+ // ChannelManager channelMan;
+ Stream stream;
PlotManager* plotMan;
+ QWidget* secondaryPlot;
SnapshotManager snapshotMan;
- DataRecorder recorder; // operated by `recordPanel`
+ SampleCounter sampleCounter;
QLabel spsLabel;
CommandPanel commandPanel;
DataFormatPanel dataFormatPanel;
RecordPanel recordPanel;
PlotControlPanel plotControlPanel;
+ PlotMenu plotMenu;
+ UpdateCheckDialog updateCheckDialog;
+ /// Returns true if demo is running
bool isDemoRunning();
+ /// Display a secondary plot in the splitter, removing and
+ /// deleting previous one if it exists
+ void showSecondary(QWidget* wid);
+ /// Hide secondary plot
+ void hideSecondary();
/// Stores settings for all modules
void saveAllSettings(QSettings* settings);
/// Load settings for all modules
@@ -101,14 +112,14 @@ private:
private slots:
void onPortToggled(bool open);
- void onPortError(QSerialPort::SerialPortError error);
-
+ void onSourceChanged(Source* source);
void onNumOfSamplesChanged(int value);
- void onNumOfChannelsChanged(unsigned value);
void clearPlot();
- void onSpsChanged(unsigned sps);
+ void onSpsChanged(float sps);
void enableDemo(bool enabled);
+ void showBarPlot(bool show);
+
void onExportCsv();
void onSaveSettings();
void onLoadSettings();
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -14,15 +14,26 @@
SerialPlot
-
+
-
-
+
-
+
0
0
+
+ Qt::Horizontal
+
+
+
+
+ 0
+ 0
+
+
+
-
@@ -90,7 +101,7 @@
0
0
653
- 27
+ 24
-
@@ -142,7 +158,8 @@
false
-
+
+ ..
Pause
@@ -156,7 +173,8 @@
-
+
+ ..
Clear
@@ -221,6 +239,38 @@
Load Settings from a File
+
+
+ &Check Update
+
+
+
+
+ true
+
+
+ Bar Plot
+
+
+
+
+ true
+
+
+ Vertical
+
+
+
+
+ true
+
+
+ true
+
+
+ Horizontal
+
+
diff --git a/src/plot.cpp b/src/plot.cpp
--- a/src/plot.cpp
+++ b/src/plot.cpp
@@ -39,6 +39,7 @@ Plot::Plot(QWidget* parent) :
isAutoScaled = true;
symbolSize = 0;
numOfSamples = 1;
+ plotWidth = 1;
showSymbols = Plot::ShowSymbolsAuto;
QObject::connect(&zoomer, &Zoomer::unzoomed, this, &Plot::unzoomed);
@@ -73,6 +74,16 @@ Plot::Plot(QWidget* parent) :
demoIndicator.setText(demoText);
demoIndicator.hide();
demoIndicator.attach(this);
+
+ // init no channels are visible indicator
+ QwtText noChannelText(" No Visible Channels ");
+ noChannelText.setColor(QColor("white"));
+ noChannelText.setBackgroundBrush(Qt::darkBlue);
+ noChannelText.setBorderRadius(4);
+ noChannelText.setRenderFlags(Qt::AlignHCenter | Qt::AlignVCenter);
+ noChannelIndicator.setText(noChannelText);
+ noChannelIndicator.hide();
+ noChannelIndicator.attach(this);
}
Plot::~Plot()
@@ -99,17 +110,18 @@ void Plot::setXAxis(double xMin, double
_xMin = xMin;
_xMax = xMax;
+ zoomer.setXLimits(xMin, xMax);
zoomer.zoom(0); // unzoom
// set axis
- setAxisScale(QwtPlot::xBottom, xMin, xMax);
+ // setAxisScale(QwtPlot::xBottom, xMin, xMax);
replot(); // Note: if we don't replot here scale at startup isn't set correctly
// reset zoom base
- auto base = zoomer.zoomBase();
- base.setLeft(xMin);
- base.setRight(xMax);
- zoomer.setZoomBase(base);
+ // auto base = zoomer.zoomBase();
+ // base.setLeft(xMin);
+ // base.setRight(xMax);
+ // zoomer.setZoomBase(base);
onXScaleChanged();
}
@@ -163,6 +175,12 @@ void Plot::showDemoIndicator(bool show)
replot();
}
+void Plot::showNoChannel(bool show)
+{
+ noChannelIndicator.setVisible(show);
+ replot();
+}
+
void Plot::unzoom()
{
zoomer.zoom(0);
@@ -195,36 +213,6 @@ void Plot::darkBackground(bool enabled)
replot();
}
-/*
- Below crude drawing demostrates how color selection occurs for
- given channel index
-
- 0° <--Hue Value--> 360°
- |* . o . + . o . * . o . + . o . * . o . + . o . * . o . + . o . |
-
- * -> 0-3
- + -> 4-7
- o -> 8-15
- . -> 16-31
-
- */
-QColor Plot::makeColor(unsigned int channelIndex)
-{
- auto i = channelIndex;
-
- if (i < 4)
- {
- return QColor::fromHsv(360*i/4, 255, 230);
- }
- else
- {
- double p = floor(log2(i));
- double n = pow(2, p);
- i = i - n;
- return QColor::fromHsv(360*i/n + 360/pow(2,p+1), 255, 230);
- }
-}
-
void Plot::flashSnapshotOverlay(bool light)
{
if (snapshotOverlay != NULL) delete snapshotOverlay;
@@ -285,7 +273,8 @@ void Plot::calcSymbolSize()
auto scaleDist = sw->scaleDraw()->scaleMap().sDist();
auto fullScaleDist = zoomer.zoomBase().width();
auto zoomRate = fullScaleDist / scaleDist;
- float samplesInView = numOfSamples / zoomRate;
+ float plotWidthNumSamp = abs(numOfSamples * plotWidth / (_xMax - _xMin));
+ float samplesInView = plotWidthNumSamp / zoomRate;
int symDisPx = round(paintDist / samplesInView);
if (symDisPx < SYMBOL_SHOW_AT_WIDTH)
@@ -331,3 +320,9 @@ void Plot::setNumOfSamples(unsigned valu
numOfSamples = value;
onXScaleChanged();
}
+
+void Plot::setPlotWidth(double width)
+{
+ plotWidth = width;
+ zoomer.setHViewSize(width);
+}
diff --git a/src/plot.h b/src/plot.h
--- a/src/plot.h
+++ b/src/plot.h
@@ -50,13 +50,12 @@ public:
Plot(QWidget* parent = 0);
~Plot();
- static QColor makeColor(unsigned int channelIndex);
-
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);
@@ -72,6 +71,8 @@ public slots:
void setNumOfSamples(unsigned value);
+ void setPlotWidth(double width);
+
protected:
/// update the display of symbols depending on `symbolSize`
void updateSymbols();
@@ -81,6 +82,7 @@ private:
double yMin, yMax;
double _xMin, _xMax;
unsigned numOfSamples;
+ double plotWidth;
int symbolSize;
Zoomer zoomer;
ScaleZoomer sZoomer;
@@ -88,6 +90,7 @@ private:
PlotSnapshotOverlay* snapshotOverlay;
QwtPlotLegendItem legend;
QwtPlotTextLabel demoIndicator;
+ QwtPlotTextLabel noChannelIndicator;
ShowSymbols showSymbols;
void resetAxes();
diff --git a/src/plotcontrolpanel.cpp b/src/plotcontrolpanel.cpp
--- a/src/plotcontrolpanel.cpp
+++ b/src/plotcontrolpanel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
@@ -29,7 +30,9 @@
#include "setting_defines.h"
/// Confirm if #samples is being set to a value greater than this
-const int NUMSAMPLES_CONFIRM_AT = 10000;
+const int NUMSAMPLES_CONFIRM_AT = 1000000;
+/// Precision used for channel info table numbers
+const int DOUBLESP_PRECISION = 6;
/// Used for scale range selection combobox
struct Range
@@ -40,6 +43,25 @@ struct Range
Q_DECLARE_METATYPE(Range);
+/// Used for customizing double precision in tables
+class SpinBoxDelegate : public QStyledItemDelegate
+{
+public:
+ QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const Q_DECL_OVERRIDE
+ {
+ auto w = QStyledItemDelegate::createEditor(
+ parent, option, index);
+
+ auto sp = qobject_cast(w);
+ if (sp)
+ {
+ sp->setDecimals(DOUBLESP_PRECISION);
+ }
+ return w;
+ }
+};
+
PlotControlPanel::PlotControlPanel(QWidget *parent) :
QWidget(parent),
ui(new Ui::PlotControlPanel),
@@ -47,10 +69,16 @@ PlotControlPanel::PlotControlPanel(QWidg
resetNamesAct(tr("Reset Names"), this),
resetColorsAct(tr("Reset Colors"), this),
showAllAct(tr("Show All"), this),
+ hideAllAct(tr("Hide All"), this),
+ resetGainsAct(tr("Reset All Gain"), this),
+ resetOffsetsAct(tr("Reset All Offset"), this),
resetMenu(tr("Reset Menu"), this)
{
ui->setupUi(this);
+ delegate = new SpinBoxDelegate();
+ ui->tvChannelInfo->setItemDelegate(delegate);
+
warnNumOfSamples = true; // TODO: load from settings
_numOfSamples = ui->spNumOfSamples->value();
@@ -86,9 +114,28 @@ PlotControlPanel::PlotControlPanel(QWidg
connect(ui->spXmax, SIGNAL(valueChanged(double)),
this, SLOT(onXScaleChanged()));
+ connect(ui->spXmax, static_cast(&QDoubleSpinBox::valueChanged),
+ [this](double v)
+ {
+ // set limit just a little below
+ double step = pow(10, -1 * ui->spXmin->decimals());
+ ui->spXmin->setMaximum(v - step);
+ });
+
connect(ui->spXmin, SIGNAL(valueChanged(double)),
this, SLOT(onXScaleChanged()));
+ connect(ui->spXmin, static_cast(&QDoubleSpinBox::valueChanged),
+ [this](double v)
+ {
+ // set limit just a little above
+ double step = pow(10, -1 * ui->spXmax->decimals());
+ ui->spXmax->setMinimum(v + step);
+ });
+
+ connect(ui->spPlotWidth, SIGNAL(valueChanged(int)),
+ this, SLOT(onPlotWidthChanged()));
+
// init scale range preset list
for (int nbits = 8; nbits <= 24; nbits++) // signed binary formats
{
@@ -119,12 +166,19 @@ PlotControlPanel::PlotControlPanel(QWidg
ui->colorSelector->setDisplayMode(color_widgets::ColorPreview::AllAlpha);
ui->colorSelector->setDisabled(true);
- // reset button
+ // reset buttons
+ resetAct.setToolTip(tr("Reset channel names and colors"));
resetMenu.addAction(&resetNamesAct);
resetMenu.addAction(&resetColorsAct);
- resetMenu.addAction(&showAllAct);
+ resetMenu.addAction(&resetGainsAct);
+ resetMenu.addAction(&resetOffsetsAct);
resetAct.setMenu(&resetMenu);
ui->tbReset->setDefaultAction(&resetAct);
+
+ showAllAct.setToolTip(tr("Show all channels"));
+ hideAllAct.setToolTip(tr("Hide all channels"));
+ ui->tbShowAll->setDefaultAction(&showAllAct);
+ ui->tbHideAll->setDefaultAction(&hideAllAct);
}
PlotControlPanel::~PlotControlPanel()
@@ -275,6 +329,7 @@ void PlotControlPanel::onIndexChecked(bo
emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
}
+ emit plotWidthChanged(plotWidth());
}
void PlotControlPanel::onXScaleChanged()
@@ -282,9 +337,29 @@ void PlotControlPanel::onXScaleChanged()
if (!xAxisAsIndex())
{
emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
+ emit plotWidthChanged(plotWidth());
}
}
+double PlotControlPanel::plotWidth() const
+{
+ double value = ui->spPlotWidth->value();
+ if (!xAxisAsIndex())
+ {
+ // scale by xmin and xmax
+ auto xmax = ui->spXmax->value();
+ auto xmin = ui->spXmin->value();
+ double scale = (xmax - xmin) / _numOfSamples;
+ value *= scale;
+ }
+ return value;
+}
+
+void PlotControlPanel::onPlotWidthChanged()
+{
+ emit plotWidthChanged(plotWidth());
+}
+
void PlotControlPanel::setChannelInfoModel(ChannelInfoModel* model)
{
ui->tvChannelInfo->setModel(model);
@@ -355,13 +430,17 @@ void PlotControlPanel::setChannelInfoMod
connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos);
connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames);
connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors);
- connect(&showAllAct, &QAction::triggered, model, &ChannelInfoModel::resetVisibility);
+ connect(&resetGainsAct, &QAction::triggered, model, &ChannelInfoModel::resetGains);
+ connect(&resetOffsetsAct, &QAction::triggered, model, &ChannelInfoModel::resetOffsets);
+ connect(&showAllAct, &QAction::triggered, [model]{model->resetVisibility(true);});
+ connect(&hideAllAct, &QAction::triggered, [model]{model->resetVisibility(false);});
}
void PlotControlPanel::saveSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_Plot);
settings->setValue(SG_Plot_NumOfSamples, numOfSamples());
+ settings->setValue(SG_Plot_PlotWidth, ui->spPlotWidth->value());
settings->setValue(SG_Plot_IndexAsX, xAxisAsIndex());
settings->setValue(SG_Plot_XMax, xMax());
settings->setValue(SG_Plot_XMin, xMin());
@@ -376,6 +455,8 @@ void PlotControlPanel::loadSettings(QSet
settings->beginGroup(SettingGroup_Plot);
ui->spNumOfSamples->setValue(
settings->value(SG_Plot_NumOfSamples, numOfSamples()).toInt());
+ ui->spPlotWidth->setValue(
+ settings->value(SG_Plot_PlotWidth, ui->spPlotWidth->value()).toInt());
ui->cbIndex->setChecked(
settings->value(SG_Plot_IndexAsX, xAxisAsIndex()).toBool());
ui->spXmax->setValue(settings->value(SG_Plot_XMax, xMax()).toDouble());
diff --git a/src/plotcontrolpanel.h b/src/plotcontrolpanel.h
--- a/src/plotcontrolpanel.h
+++ b/src/plotcontrolpanel.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include "channelinfomodel.h"
@@ -46,6 +47,8 @@ public:
bool xAxisAsIndex() const;
double xMax() const;
double xMin() const;
+ /// Returns the plot width adjusted for x axis scaling.
+ double plotWidth() const;
void setChannelInfoModel(ChannelInfoModel* model);
@@ -58,6 +61,7 @@ signals:
void numOfSamplesChanged(int value);
void yScaleChanged(bool autoScaled, double yMin = 0, double yMax = 1);
void xScaleChanged(bool asIndex, double xMin = 0, double xMax = 1);
+ void plotWidthChanged(double width);
private:
Ui::PlotControlPanel *ui;
@@ -67,8 +71,10 @@ private:
/// User can disable this setting in the checkbox
bool warnNumOfSamples;
- QAction resetAct, resetNamesAct, resetColorsAct, showAllAct;
+ QAction resetAct, resetNamesAct, resetColorsAct, showAllAct,
+ hideAllAct, resetGainsAct, resetOffsetsAct;
QMenu resetMenu;
+ QStyledItemDelegate* delegate;
/// Show a confirmation dialog before setting #samples to a big value
bool askNSConfirmation(int value);
@@ -80,6 +86,7 @@ private slots:
void onRangeSelected();
void onIndexChecked(bool checked);
void onXScaleChanged();
+ void onPlotWidthChanged();
};
#endif // PLOTCONTROLPANEL_H
diff --git a/src/plotcontrolpanel.ui b/src/plotcontrolpanel.ui
--- a/src/plotcontrolpanel.ui
+++ b/src/plotcontrolpanel.ui
@@ -6,8 +6,8 @@
0
0
- 706
- 187
+ 704
+ 195
@@ -17,7 +17,7 @@
-
-
+
0
0
@@ -41,14 +41,14 @@
-
-
+
0
0
- 300
+ 30000
170
@@ -62,6 +62,9 @@
-
+
+ 3
+
QLayout::SetMaximumSize
@@ -95,12 +98,32 @@
1
- 20
+ 1
-
+
+
+ Show all channels
+
+
+ Show All
+
+
+
+ -
+
+
+ Hide all channels
+
+
+ Hide All
+
+
+
+ -
Reset
@@ -132,15 +155,30 @@
-
+
+
+
- Number Of Samples:
+ Buffer Size:
-
+
+
+ 100
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
- length of X axis
+ Length of acquisition as number of samples
false
@@ -149,14 +187,14 @@
2
- 1000000
+ 10000000
1000
- -
+
-
Index as X AXis
@@ -166,7 +204,7 @@
- -
+
-
-
@@ -237,7 +275,7 @@
- -
+
-
Auto Scale Y Axis
@@ -247,7 +285,7 @@
- -
+
-
-
@@ -318,34 +356,59 @@
- -
+
-
Select Range Preset:
- -
+
-
+ -
+
+
+
+
+
+ Plot Width:
+
+
+
+ -
+
+
+
+ 100
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
+
+ Width of X axis as maximum number of samples that are shown in plot
+
+
+ false
+
+
+ 2
+
+
+ 100000
+
+
+ 1000
+
+
+
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 1
- 20
-
-
-
-
diff --git a/src/plotmanager.cpp b/src/plotmanager.cpp
--- a/src/plotmanager.cpp
+++ b/src/plotmanager.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,7 +17,7 @@
along with serialplot. If not, see .
*/
-#include
+#include
#include
#include
#include "qwt_symbol.h"
@@ -27,25 +27,69 @@
#include "utils.h"
#include "setting_defines.h"
-PlotManager::PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel, QObject *parent) :
- QObject(parent),
- _plotArea(plotArea),
- showGridAction("&Grid", this),
- showMinorGridAction("&Minor Grid", this),
- unzoomAction("&Unzoom", this),
- darkBackgroundAction("&Dark Background", this),
- showLegendAction("&Legend", this),
- showMultiAction("Multi &Plot", this),
- setSymbolsAction("Symbols", this)
+PlotManager::PlotManager(QWidget* plotArea, PlotMenu* menu,
+ const Stream* stream, QObject* parent) :
+ QObject(parent)
{
+ construct(plotArea, menu);
+ _stream = stream;
+ if (_stream == NULL) 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());
+ }
+
+}
+
+PlotManager::PlotManager(QWidget* plotArea, PlotMenu* menu,
+ Snapshot* snapshot, QObject *parent) :
+ QObject(parent)
+{
+ 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]);
+ }
+
+ 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;
- _infoModel = infoModel;
_numOfSamples = 1;
+ _plotWidth = 1;
showSymbols = Plot::ShowSymbolsAuto;
+ emptyPlot = NULL;
// initalize layout and single widget
isMulti = false;
@@ -53,93 +97,28 @@ PlotManager::PlotManager(QWidget* plotAr
setupLayout(isMulti);
addPlotWidget();
- // initialize menu actions
- showGridAction.setToolTip("Show Grid");
- showMinorGridAction.setToolTip("Show Minor Grid");
- unzoomAction.setToolTip("Unzoom the Plot");
- darkBackgroundAction.setToolTip("Enable Dark Plot Background");
- showLegendAction.setToolTip("Display the Legend on Plot");
- showMultiAction.setToolTip("Display All Channels Separately");
- setSymbolsAction.setToolTip("Show/Hide symbols");
-
- showGridAction.setShortcut(QKeySequence("G"));
- showMinorGridAction.setShortcut(QKeySequence("M"));
-
- showGridAction.setCheckable(true);
- showMinorGridAction.setCheckable(true);
- darkBackgroundAction.setCheckable(true);
- showLegendAction.setCheckable(true);
- showMultiAction.setCheckable(true);
-
- showGridAction.setChecked(false);
- showMinorGridAction.setChecked(false);
- darkBackgroundAction.setChecked(false);
- showLegendAction.setChecked(true);
- showMultiAction.setChecked(false);
-
- showMinorGridAction.setEnabled(false);
+ // connect to menu
+ connect(menu, &PlotMenu::symbolShowChanged, this, &PlotManager:: setSymbols);
- // setup symbols menu
- setSymbolsAutoAct = setSymbolsMenu.addAction("Show When Zoomed");
- setSymbolsAutoAct->setCheckable(true);
- setSymbolsAutoAct->setChecked(true);
- connect(setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::triggered),
- [this](bool checked)
- {
- if (checked) setSymbols(Plot::ShowSymbolsAuto);
- });
- setSymbolsShowAct = setSymbolsMenu.addAction("Always Show");
- setSymbolsShowAct->setCheckable(true);
- connect(setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::triggered),
- [this](bool checked)
- {
- if (checked) setSymbols(Plot::ShowSymbolsShow);
- });
- setSymbolsHideAct = setSymbolsMenu.addAction("Always Hide");
- setSymbolsHideAct->setCheckable(true);
- connect(setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::triggered),
- [this](bool checked)
- {
- if (checked) setSymbols(Plot::ShowSymbolsHide);
- });
- setSymbolsAction.setMenu(&setSymbolsMenu);
-
- // add symbol actions to same group so that they appear as radio buttons
- auto group = new QActionGroup(this);
- group->addAction(setSymbolsAutoAct);
- group->addAction(setSymbolsShowAct);
- group->addAction(setSymbolsHideAct);
+ connect(&menu->showGridAction, SELECT::OVERLOAD_OF(&QAction::toggled),
+ this, &PlotManager::showGrid);
+ connect(&menu->showMinorGridAction, SELECT::OVERLOAD_OF(&QAction::toggled),
+ this, &PlotManager::showMinorGrid);
+ connect(&menu->darkBackgroundAction, SELECT::OVERLOAD_OF(&QAction::toggled),
+ this, &PlotManager::darkBackground);
+ connect(&menu->showLegendAction, SELECT::OVERLOAD_OF(&QAction::toggled),
+ this, &PlotManager::showLegend);
+ connect(&menu->showMultiAction, SELECT::OVERLOAD_OF(&QAction::toggled),
+ this, &PlotManager::setMulti);
+ connect(&menu->unzoomAction, &QAction::triggered,
+ this, &PlotManager::unzoom);
- connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
- this, &PlotManager::showGrid);
- connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
- &showMinorGridAction, &QAction::setEnabled);
- connect(&showMinorGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
- this, &PlotManager::showMinorGrid);
- connect(&unzoomAction, &QAction::triggered, this, &PlotManager::unzoom);
- connect(&darkBackgroundAction, SELECT::OVERLOAD_OF(&QAction::triggered),
- this, &PlotManager::darkBackground);
- connect(&showLegendAction, SELECT::OVERLOAD_OF(&QAction::triggered),
- this, &PlotManager::showLegend);
- connect(&showLegendAction, SELECT::OVERLOAD_OF(&QAction::triggered),
- this, &PlotManager::showLegend);
- connect(&showMultiAction, SELECT::OVERLOAD_OF(&QAction::triggered),
- this, &PlotManager::setMulti);
-
- // connect to channel info model
- if (_infoModel != NULL) // TODO: remove when snapshots have 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
- });
- }
+ // initial settings from menu actions
+ showGrid(menu->showGridAction.isChecked());
+ showMinorGrid(menu->showMinorGridAction.isChecked());
+ darkBackground(menu->darkBackgroundAction.isChecked());
+ showLegend(menu->showLegendAction.isChecked());
+ setMulti(menu->showMultiAction.isChecked());
}
PlotManager::~PlotManager()
@@ -156,6 +135,28 @@ PlotManager::~PlotManager()
}
if (scrollArea != NULL) delete scrollArea;
+ if (emptyPlot != NULL) delete emptyPlot;
+}
+
+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());
+ }
+ }
+ else if(numOfChannels < oldNum)
+ {
+ removeCurves(oldNum - numOfChannels);
+ }
+
+ replot();
}
void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
@@ -189,6 +190,8 @@ void PlotManager::onChannelInfoChanged(c
}
}
+ checkNoVisChannels();
+
// replot single widget
if (!isMulti)
{
@@ -198,6 +201,20 @@ void PlotManager::onChannelInfoChanged(c
}
}
+void PlotManager::checkNoVisChannels()
+{
+ // if all channels are hidden show indicator
+ bool allhidden = std::none_of(curves.cbegin(), curves.cend(),
+ [](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;
@@ -224,7 +241,9 @@ void PlotManager::setMulti(bool enabled)
// add new widgets and attach
for (auto curve : curves)
{
- curve->attach(addPlotWidget());
+ auto plot = addPlotWidget();
+ plot->setVisible(curve->isVisible());
+ curve->attach(plot);
}
}
else
@@ -238,6 +257,12 @@ void PlotManager::setMulti(bool enabled)
curve->attach(plot);
}
}
+
+ // will skip if no plot widgets exist (can happen during constructor)
+ if (plotWidgets.length())
+ {
+ checkNoVisChannels();
+ }
}
void PlotManager::setupLayout(bool multiPlot)
@@ -284,15 +309,17 @@ Plot* PlotManager::addPlotWidget()
plotWidgets.append(plot);
layout->addWidget(plot);
- plot->darkBackground(darkBackgroundAction.isChecked());
- plot->showGrid(showGridAction.isChecked());
- plot->showMinorGrid(showMinorGridAction.isChecked());
- plot->showLegend(showLegendAction.isChecked());
+ plot->darkBackground(_menu->darkBackgroundAction.isChecked());
+ plot->showGrid(_menu->showGridAction.isChecked());
+ plot->showMinorGrid(_menu->showMinorGridAction.isChecked());
+ plot->showLegend(_menu->showLegendAction.isChecked());
+ plot->setSymbols(_menu->showSymbols());
+
plot->showDemoIndicator(isDemoShown);
plot->setYAxis(_autoScaled, _yMin, _yMax);
plot->setNumOfSamples(_numOfSamples);
- plot->setSymbols(showSymbols);
+ plot->setPlotWidth(_plotWidth);
if (_xAxisAsIndex)
{
plot->setXAxis(0, _numOfSamples);
@@ -305,7 +332,7 @@ Plot* PlotManager::addPlotWidget()
return plot;
}
-void PlotManager::addCurve(QString title, FrameBuffer* buffer)
+void PlotManager::addCurve(QString title, const FrameBuffer* buffer)
{
auto curve = new QwtPlotCurve(title);
auto series = new FrameBufferSeries(buffer);
@@ -314,20 +341,13 @@ void PlotManager::addCurve(QString title
_addCurve(curve);
}
-void PlotManager::addCurve(QString title, QVector data)
-{
- auto curve = new QwtPlotCurve(title);
- curve->setSamples(data);
- _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);
+ auto color = infoModel->color(index);
curve->setPen(color);
// create the plot for the curve if we are on multi display
@@ -367,13 +387,6 @@ unsigned PlotManager::numOfCurves()
return curves.size();
}
-void PlotManager::setTitle(unsigned index, QString title)
-{
- curves[index]->setTitle(title);
-
- plotWidget(index)->replot();
-}
-
Plot* PlotManager::plotWidget(unsigned curveIndex)
{
if (isMulti)
@@ -394,19 +407,6 @@ void PlotManager::replot()
}
}
-QList PlotManager::menuActions()
-{
- QList actions;
- actions << &showGridAction;
- actions << &showMinorGridAction;
- actions << &unzoomAction;
- actions << &darkBackgroundAction;
- actions << &showLegendAction;
- actions << &showMultiAction;
- actions << &setSymbolsAction;
- return actions;
-}
-
void PlotManager::showGrid(bool show)
{
for (auto plot : plotWidgets)
@@ -483,7 +483,6 @@ void PlotManager::setXAxis(bool asIndex,
_xMax = xMax;
for (auto curve : curves)
{
- // TODO: what happens when addCurve(QVector) is used?
FrameBufferSeries* series = static_cast(curve->data());
series->setXAxis(asIndex, xMin, xMax);
}
@@ -505,7 +504,7 @@ void PlotManager::flashSnapshotOverlay()
{
for (auto plot : plotWidgets)
{
- plot->flashSnapshotOverlay(darkBackgroundAction.isChecked());
+ plot->flashSnapshotOverlay(_menu->darkBackgroundAction.isChecked());
}
}
@@ -519,114 +518,11 @@ void PlotManager::setNumOfSamples(unsign
}
}
-PlotViewSettings PlotManager::viewSettings() const
-{
- return PlotViewSettings(
- {
- showGridAction.isChecked(),
- showMinorGridAction.isChecked(),
- darkBackgroundAction.isChecked(),
- showLegendAction.isChecked(),
- showMultiAction.isChecked(),
- showSymbols
- });
-}
-
-void PlotManager::setViewSettings(const PlotViewSettings& settings)
+void PlotManager::setPlotWidth(double width)
{
- showGridAction.setChecked(settings.showGrid);
- showGrid(settings.showGrid);
- showMinorGridAction.setChecked(settings.showMinorGrid);
- showMinorGrid(settings.showMinorGrid);
- darkBackgroundAction.setChecked(settings.darkBackground);
- darkBackground(settings.darkBackground);
- showLegendAction.setChecked(settings.showLegend);
- showLegend(settings.showLegend);
- showMultiAction.setChecked(settings.showMulti);
- setMulti(settings.showMulti);
-
- setSymbols(settings.showSymbols);
- if (showSymbols == Plot::ShowSymbolsAuto)
+ _plotWidth = width;
+ for (auto plot : plotWidgets)
{
- setSymbolsAutoAct->setChecked(true);
- }
- else if (showSymbols == Plot::ShowSymbolsShow)
- {
- setSymbolsShowAct->setChecked(true);
- }
- else
- {
- setSymbolsHideAct->setChecked(true);
+ plot->setPlotWidth(width);
}
}
-
-void PlotManager::saveSettings(QSettings* settings)
-{
- settings->beginGroup(SettingGroup_Plot);
- settings->setValue(SG_Plot_DarkBackground, darkBackgroundAction.isChecked());
- settings->setValue(SG_Plot_Grid, showGridAction.isChecked());
- settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked());
- settings->setValue(SG_Plot_Legend, showLegendAction.isChecked());
- settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked());
-
- QString showSymbolsStr;
- if (showSymbols == Plot::ShowSymbolsAuto)
- {
- showSymbolsStr = "auto";
- }
- else if (showSymbols == Plot::ShowSymbolsShow)
- {
- showSymbolsStr = "show";
- }
- else
- {
- showSymbolsStr = "hide";
- }
- settings->setValue(SG_Plot_Symbols, showSymbolsStr);
-
- settings->endGroup();
-}
-
-void PlotManager::loadSettings(QSettings* settings)
-{
- settings->beginGroup(SettingGroup_Plot);
- darkBackgroundAction.setChecked(
- settings->value(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()).toBool());
- darkBackground(darkBackgroundAction.isChecked());
- showGridAction.setChecked(
- settings->value(SG_Plot_Grid, showGridAction.isChecked()).toBool());
- showGrid(showGridAction.isChecked());
- showMinorGridAction.setChecked(
- settings->value(SG_Plot_MinorGrid, showMinorGridAction.isChecked()).toBool());
- showMinorGridAction.setEnabled(showGridAction.isChecked());
- showMinorGrid(showMinorGridAction.isChecked());
- showLegendAction.setChecked(
- settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool());
- showLegend(showLegendAction.isChecked());
- showMultiAction.setChecked(
- settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
- setMulti(showMultiAction.isChecked());
-
- QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString();
- if (showSymbolsStr == "auto")
- {
- setSymbols(Plot::ShowSymbolsAuto);
- setSymbolsAutoAct->setChecked(true);
- }
- else if (showSymbolsStr == "show")
- {
- setSymbols(Plot::ShowSymbolsShow);
- setSymbolsShowAct->setChecked(true);
- }
- else if (showSymbolsStr == "hide")
- {
- setSymbols(Plot::ShowSymbolsHide);
- setSymbolsHideAct->setChecked(true);
- }
- else
- {
- qCritical() << "Invalid symbol setting:" << showSymbolsStr;
- }
-
- settings->endGroup();
-}
diff --git a/src/plotmanager.h b/src/plotmanager.h
--- a/src/plotmanager.h
+++ b/src/plotmanager.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -26,52 +26,34 @@
#include
#include
#include
-#include
#include
#include
#include "plot.h"
#include "framebufferseries.h"
-#include "channelinfomodel.h"
-
-struct PlotViewSettings
-{
- bool showGrid;
- bool showMinorGrid;
- bool darkBackground;
- bool showLegend;
- bool showMulti;
- Plot::ShowSymbols showSymbols;
-};
+#include "stream.h"
+#include "snapshot.h"
+#include "plotmenu.h"
class PlotManager : public QObject
{
Q_OBJECT
public:
- explicit PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel = NULL, QObject *parent = 0);
+ explicit PlotManager(QWidget* plotArea, PlotMenu* menu,
+ const Stream* stream = NULL,
+ 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, FrameBuffer* buffer);
- /// Alternative of `addCurve` for static curve data (snapshots).
- void addCurve(QString title, QVector data);
- /// Set the displayed title for a curve
- void setTitle(unsigned index, QString title);
+ void addCurve(QString title, const FrameBuffer* buffer);
/// Removes curves from the end
void removeCurves(unsigned number);
/// Returns current number of curves known by plot manager
unsigned numOfCurves();
- /// Returns the list of actions to be inserted into the `View` menu
- QList menuActions();
- /// Returns current status of menu actions
- PlotViewSettings viewSettings() const;
- /// Set the current state of view
- void setViewSettings(const PlotViewSettings& settings);
- /// Stores plot settings into a `QSettings`.
- void saveSettings(QSettings* settings);
- /// Loads plot settings from a `QSettings`.
- void loadSettings(QSettings* settings);
public slots:
/// Enable/Disable multiple plot display
@@ -88,15 +70,20 @@ public slots:
void flashSnapshotOverlay();
/// Should be called to update zoom base
void setNumOfSamples(unsigned value);
+ /// 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 curves;
QList plotWidgets;
- ChannelInfoModel* _infoModel;
+ Plot* emptyPlot; ///< for displaying when all channels are hidden
+ const Stream* _stream; ///< attached stream, can be `NULL`
+ const ChannelInfoModel* infoModel;
bool isDemoShown;
bool _autoScaled;
double _yMin;
@@ -105,21 +92,12 @@ private:
double _xMin;
double _xMax;
unsigned _numOfSamples;
+ double _plotWidth;
Plot::ShowSymbols showSymbols;
- // menu actions
- QAction showGridAction;
- QAction showMinorGridAction;
- QAction unzoomAction;
- QAction darkBackgroundAction;
- QAction showLegendAction;
- QAction showMultiAction;
- QAction setSymbolsAction;
- QMenu setSymbolsMenu;
- QAction* setSymbolsAutoAct;
- QAction* setSymbolsShowAct;
- QAction* setSymbolsHideAct;
-
+ /// Common constructor
+ void construct(QWidget* plotArea, PlotMenu* menu);
+ /// Setups the layout for multi or single plot
void setupLayout(bool multiPlot);
/// Inserts a new plot widget to the current layout.
Plot* addPlotWidget();
@@ -127,7 +105,8 @@ private:
Plot* plotWidget(unsigned curveIndex);
/// Common part of overloaded `addCurve` functions
void _addCurve(QwtPlotCurve* curve);
- void setSymbols(Plot::ShowSymbols shown);
+ /// Check and make sure "no visible channels" text is shown
+ void checkNoVisChannels();
private slots:
void showGrid(bool show = true);
@@ -135,7 +114,9 @@ private slots:
void showLegend(bool show = true);
void unzoom();
void darkBackground(bool enabled = true);
+ void setSymbols(Plot::ShowSymbols shown);
+ void onNumChannelsChanged(unsigned value);
void onChannelInfoChanged(const QModelIndex & topLeft,
const QModelIndex & bottomRight,
const QVector & roles = QVector ());
diff --git a/src/plotmenu.cpp b/src/plotmenu.cpp
new file mode 100644
--- /dev/null
+++ b/src/plotmenu.cpp
@@ -0,0 +1,224 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#include "plotmenu.h"
+#include "setting_defines.h"
+#include "utils.h"
+
+PlotMenu::PlotMenu(QWidget* parent) :
+ QMenu(tr("&View"), parent),
+ showGridAction("&Grid", this),
+ showMinorGridAction("&Minor Grid", this),
+ unzoomAction("&Unzoom", this),
+ darkBackgroundAction("&Dark Background", this),
+ showLegendAction("&Legend", this),
+ showMultiAction("Multi &Plot", this),
+ setSymbolsAction("&Symbols", this),
+ setSymbolsAutoAct("Show When &Zoomed", this),
+ setSymbolsShowAct("Always &Show", this),
+ setSymbolsHideAct("Always &Hide", this)
+{
+ showGridAction.setToolTip("Show Grid");
+ showMinorGridAction.setToolTip("Show Minor Grid");
+ unzoomAction.setToolTip("Unzoom the Plot");
+ darkBackgroundAction.setToolTip("Enable Dark Plot Background");
+ showLegendAction.setToolTip("Display the Legend on Plot");
+ showMultiAction.setToolTip("Display All Channels Separately");
+ setSymbolsAction.setToolTip("Show/Hide symbols");
+
+ showGridAction.setShortcut(QKeySequence("G"));
+ showMinorGridAction.setShortcut(QKeySequence("M"));
+
+ showGridAction.setCheckable(true);
+ showMinorGridAction.setCheckable(true);
+ darkBackgroundAction.setCheckable(true);
+ showLegendAction.setCheckable(true);
+ showMultiAction.setCheckable(true);
+
+ showGridAction.setChecked(false);
+ showMinorGridAction.setChecked(false);
+ darkBackgroundAction.setChecked(false);
+ showLegendAction.setChecked(true);
+ showMultiAction.setChecked(false);
+
+ // minor grid is only enabled when _major_ grid is enabled
+ showMinorGridAction.setEnabled(false);
+ connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered),
+ &showMinorGridAction, &QAction::setEnabled);
+
+ // setup set symbols menu
+ setSymbolsMenu.addAction(&setSymbolsAutoAct);
+ setSymbolsAutoAct.setCheckable(true);
+ setSymbolsAutoAct.setChecked(true);
+ connect(&setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ [this](bool checked)
+ {
+ if (checked) emit symbolShowChanged(Plot::ShowSymbolsAuto);
+ });
+
+ setSymbolsMenu.addAction(&setSymbolsShowAct);
+ setSymbolsShowAct.setCheckable(true);
+ connect(&setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ [this](bool checked)
+ {
+ if (checked) symbolShowChanged(Plot::ShowSymbolsShow);
+ });
+
+ setSymbolsMenu.addAction(&setSymbolsHideAct);
+ setSymbolsHideAct.setCheckable(true);
+ connect(&setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ [this](bool checked)
+ {
+ if (checked) symbolShowChanged(Plot::ShowSymbolsHide);
+ });
+
+ // add symbol actions to same group so that they appear as radio buttons
+ auto group = new QActionGroup(this);
+ group->addAction(&setSymbolsAutoAct);
+ group->addAction(&setSymbolsShowAct);
+ group->addAction(&setSymbolsHideAct);
+
+ setSymbolsAction.setMenu(&setSymbolsMenu);
+
+ // add all actions to create this menu
+ addAction(&showGridAction);
+ addAction(&showMinorGridAction);
+ addAction(&unzoomAction);
+ addAction(&darkBackgroundAction);
+ addAction(&showLegendAction);
+ addAction(&showMultiAction);
+ addAction(&setSymbolsAction);
+}
+
+PlotMenu::PlotMenu(PlotViewSettings s, QWidget* parent) :
+ PlotMenu(parent)
+{
+ showGridAction.setChecked(s.showGrid);
+ showMinorGridAction.setChecked(s.showMinorGrid);
+ darkBackgroundAction.setChecked(s.darkBackground);
+ showLegendAction.setChecked(s.showLegend);
+ showMultiAction.setChecked(s.showMulti);
+ switch (s.showSymbols)
+ {
+ case Plot::ShowSymbolsAuto:
+ setSymbolsAutoAct.setChecked(true);
+ break;
+ case Plot::ShowSymbolsShow:
+ setSymbolsShowAct.setChecked(true);
+ break;
+ case Plot::ShowSymbolsHide:
+ setSymbolsHideAct.setChecked(true);
+ break;
+ }
+}
+
+PlotViewSettings PlotMenu::viewSettings() const
+{
+ return PlotViewSettings(
+ {
+ showGridAction.isChecked(),
+ showMinorGridAction.isChecked(),
+ darkBackgroundAction.isChecked(),
+ showLegendAction.isChecked(),
+ showMultiAction.isChecked(),
+ showSymbols()
+ });
+}
+
+Plot::ShowSymbols PlotMenu::showSymbols() const
+{
+ if (setSymbolsAutoAct.isChecked())
+ {
+ return Plot::ShowSymbolsAuto;
+ }
+ else if (setSymbolsShowAct.isChecked())
+ {
+ return Plot::ShowSymbolsShow;
+ }
+ else // setSymbolsHideAct.isChecked()
+ {
+ return Plot::ShowSymbolsHide;
+ }
+}
+
+void PlotMenu::saveSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Plot);
+ settings->setValue(SG_Plot_DarkBackground, darkBackgroundAction.isChecked());
+ settings->setValue(SG_Plot_Grid, showGridAction.isChecked());
+ settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked());
+ settings->setValue(SG_Plot_Legend, showLegendAction.isChecked());
+ settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked());
+
+ QString showSymbolsStr;
+ if (showSymbols() == Plot::ShowSymbolsAuto)
+ {
+ showSymbolsStr = "auto";
+ }
+ else if (showSymbols() == Plot::ShowSymbolsShow)
+ {
+ showSymbolsStr = "show";
+ }
+ else
+ {
+ showSymbolsStr = "hide";
+ }
+ settings->setValue(SG_Plot_Symbols, showSymbolsStr);
+
+ settings->endGroup();
+}
+
+void PlotMenu::loadSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Plot);
+ darkBackgroundAction.setChecked(
+ settings->value(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()).toBool());
+ showGridAction.setChecked(
+ settings->value(SG_Plot_Grid, showGridAction.isChecked()).toBool());
+ showMinorGridAction.setChecked(
+ settings->value(SG_Plot_MinorGrid, showMinorGridAction.isChecked()).toBool());
+ showMinorGridAction.setEnabled(showGridAction.isChecked());
+ showLegendAction.setChecked(
+ settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool());
+ showMultiAction.setChecked(
+ settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
+
+ QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString();
+ if (showSymbolsStr == "auto")
+ {
+ // setSymbols(Plot::ShowSymbolsAuto);
+ setSymbolsAutoAct.setChecked(true);
+ }
+ else if (showSymbolsStr == "show")
+ {
+ // setSymbols(Plot::ShowSymbolsShow);
+ setSymbolsShowAct.setChecked(true);
+ }
+ else if (showSymbolsStr == "hide")
+ {
+ // setSymbols(Plot::ShowSymbolsHide);
+ setSymbolsHideAct.setChecked(true);
+ }
+ else
+ {
+ qCritical() << "Invalid symbol setting:" << showSymbolsStr;
+ }
+
+ settings->endGroup();
+}
diff --git a/src/plotmenu.h b/src/plotmenu.h
new file mode 100644
--- /dev/null
+++ b/src/plotmenu.h
@@ -0,0 +1,73 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#ifndef PLOTMENU_H
+#define PLOTMENU_H
+
+#include
+#include
+#include
+
+#include "plot.h"
+
+/// Used to quickly transfer view options between different menus
+struct PlotViewSettings
+{
+ bool showGrid;
+ bool showMinorGrid;
+ bool darkBackground;
+ bool showLegend;
+ bool showMulti;
+ Plot::ShowSymbols showSymbols;
+};
+
+class PlotMenu : public QMenu
+{
+ Q_OBJECT
+
+public:
+ PlotMenu(QWidget* parent = 0);
+ PlotMenu(PlotViewSettings s, QWidget* parent = 0);
+
+ QAction showGridAction;
+ QAction showMinorGridAction;
+ QAction unzoomAction;
+ QAction darkBackgroundAction;
+ QAction showLegendAction;
+ QAction showMultiAction;
+ QAction setSymbolsAction;
+ QMenu setSymbolsMenu;
+ QAction setSymbolsAutoAct;
+ QAction setSymbolsShowAct;
+ QAction setSymbolsHideAct;
+
+ /// Returns a bundle of current view settings (menu selections)
+ PlotViewSettings viewSettings() const;
+ /// Selected "show symbol" option
+ Plot::ShowSymbols showSymbols() const;
+ /// Stores plot settings into a `QSettings`.
+ void saveSettings(QSettings* settings);
+ /// Loads plot settings from a `QSettings`.
+ void loadSettings(QSettings* settings);
+
+signals:
+ void symbolShowChanged(Plot::ShowSymbols shown);
+};
+
+#endif // PLOTMENU_H
diff --git a/src/portcontrol.cpp b/src/portcontrol.cpp
--- a/src/portcontrol.cpp
+++ b/src/portcontrol.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -48,6 +48,8 @@ PortControl::PortControl(QSerialPort* po
ui->setupUi(this);
serialPort = port;
+ connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)),
+ this, SLOT(onPortError(QSerialPort::SerialPortError)));
// setup actions
openAction.setCheckable(true);
@@ -130,6 +132,40 @@ PortControl::PortControl(QSerialPort* po
SELECT::OVERLOAD_OF(&QButtonGroup::buttonClicked),
this, &PortControl::selectFlowControl);
+ // initialize signal leds
+ ui->ledDTR->setOn(true);
+ ui->ledRTS->setOn(true);
+
+ // connect output signals
+ connect(ui->pbDTR, &QPushButton::clicked, [this]()
+ {
+ // toggle DTR
+ ui->ledDTR->toggle();
+ if (serialPort->isOpen())
+ {
+ serialPort->setDataTerminalReady(ui->ledDTR->isOn());
+ }
+ });
+
+ connect(ui->pbRTS, &QPushButton::clicked, [this]()
+ {
+ // toggle RTS
+ ui->ledRTS->toggle();
+ if (serialPort->isOpen())
+ {
+ serialPort->setRequestToSend(ui->ledRTS->isOn());
+ }
+ });
+
+ // setup pin update leds
+ ui->ledDCD->setColor(Qt::yellow);
+ ui->ledDSR->setColor(Qt::yellow);
+ ui->ledRI->setColor(Qt::yellow);
+ ui->ledCTS->setColor(Qt::yellow);
+
+ pinUpdateTimer.setInterval(1000); // ms
+ connect(&pinUpdateTimer, &QTimer::timeout, this, &PortControl::updatePinLeds);
+
loadPortList();
loadBaudRateList();
ui->cbBaudRate->setCurrentIndex(ui->cbBaudRate->findText("9600"));
@@ -221,6 +257,7 @@ void PortControl::togglePort()
{
if (serialPort->isOpen())
{
+ pinUpdateTimer.stop();
serialPort->close();
qDebug() << "Closed port:" << serialPort->portName();
emit portToggled(false);
@@ -258,6 +295,14 @@ void PortControl::togglePort()
selectStopBits((QSerialPort::StopBits) stopBitsButtons.checkedId());
selectFlowControl((QSerialPort::FlowControl) flowControlButtons.checkedId());
+ // set output signals
+ serialPort->setDataTerminalReady(ui->ledDTR->isOn());
+ serialPort->setRequestToSend(ui->ledRTS->isOn());
+
+ // update pin signals
+ updatePinLeds();
+ pinUpdateTimer.start();
+
qDebug() << "Opened port:" << serialPort->portName();
emit portToggled(true);
}
@@ -319,6 +364,73 @@ void PortControl::onTbPortListActivated(
ui->cbPortList->setCurrentIndex(index);
}
+void PortControl::onPortError(QSerialPort::SerialPortError error)
+{
+ switch(error)
+ {
+ case QSerialPort::NoError :
+ break;
+ case QSerialPort::ResourceError :
+ qWarning() << "Port error: resource unavaliable; most likely device removed.";
+ if (serialPort->isOpen())
+ {
+ qWarning() << "Closing port on resource error: " << serialPort->portName();
+ togglePort();
+ }
+ loadPortList();
+ break;
+ case QSerialPort::DeviceNotFoundError:
+ qCritical() << "Device doesn't exists: " << serialPort->portName();
+ break;
+ case QSerialPort::PermissionError:
+ qCritical() << "Permission denied. Either you don't have \
+required privileges or device is already opened by another process.";
+ break;
+ case QSerialPort::OpenError:
+ qWarning() << "Device is already opened!";
+ break;
+ case QSerialPort::NotOpenError:
+ qCritical() << "Device is not open!";
+ break;
+ case QSerialPort::ParityError:
+ qCritical() << "Parity error detected.";
+ break;
+ case QSerialPort::FramingError:
+ qCritical() << "Framing error detected.";
+ break;
+ case QSerialPort::BreakConditionError:
+ qCritical() << "Break condition is detected.";
+ break;
+ case QSerialPort::WriteError:
+ qCritical() << "An error occurred while writing data.";
+ break;
+ case QSerialPort::ReadError:
+ qCritical() << "An error occurred while reading data.";
+ break;
+ case QSerialPort::UnsupportedOperationError:
+ qCritical() << "Operation is not supported.";
+ break;
+ case QSerialPort::TimeoutError:
+ qCritical() << "A timeout error occurred.";
+ break;
+ case QSerialPort::UnknownError:
+ qCritical() << "Unknown error! Error: " << serialPort->errorString();
+ break;
+ default:
+ qCritical() << "Unhandled port error: " << error;
+ break;
+ }
+}
+
+void PortControl::updatePinLeds(void)
+{
+ auto pins = serialPort->pinoutSignals();
+ ui->ledDCD->setOn(pins & QSerialPort::DataCarrierDetectSignal);
+ ui->ledDSR->setOn(pins & QSerialPort::DataSetReadySignal);
+ ui->ledRI->setOn(pins & QSerialPort::RingIndicatorSignal);
+ ui->ledCTS->setOn(pins & QSerialPort::ClearToSendSignal);
+}
+
QString PortControl::currentParityText()
{
return paritySettingMap.value(
diff --git a/src/portcontrol.h b/src/portcontrol.h
--- a/src/portcontrol.h
+++ b/src/portcontrol.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include "portlist.h"
@@ -65,6 +66,9 @@ private:
QComboBox tbPortList;
PortList portList;
+ /// Used to refresh pinout signal leds periodically
+ QTimer pinUpdateTimer;
+
/// Returns the currently selected (entered) "portName" in the UI
QString selectedPortName();
/// Returns currently selected parity as text to be saved in settings
@@ -86,9 +90,10 @@ public slots:
private slots:
void openActionTriggered(bool checked);
-
void onCbPortListActivated(int index);
void onTbPortListActivated(int index);
+ void onPortError(QSerialPort::SerialPortError error);
+ void updatePinLeds(void);
signals:
void portToggled(bool open);
diff --git a/src/portcontrol.ui b/src/portcontrol.ui
--- a/src/portcontrol.ui
+++ b/src/portcontrol.ui
@@ -33,6 +33,9 @@
0
+
+ You can enter a port name even if it's not listed, such as pseudo terminals.
+
true
@@ -40,6 +43,9 @@
-
+
+ You can enter a custom baud rate if it's supported by your OS/adapter.
+
Qt::ImhPreferNumbers
@@ -70,7 +76,7 @@
-
+ Reload the list of ports
↺
@@ -301,6 +307,224 @@
-
+
+
+ 2
+
+
-
+
+
+
+ 15
+ 15
+
+
+
+
+ 15
+ 15
+
+
+
+ Request To Send
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 40
+ 20
+
+
+
+ Data Terminal Ready
+
+
+ DTR
+
+
+
+ -
+
+
+
+ 15
+ 15
+
+
+
+
+ 15
+ 15
+
+
+
+ Data Terminal Ready
+
+
+
+ -
+
+
+
+ 15
+ 15
+
+
+
+
+ 15
+ 15
+
+
+
+ Data Set Ready
+
+
+
+ -
+
+
+ Data Set Ready
+
+
+ DSR
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 40
+ 20
+
+
+
+ Request To Send
+
+
+ RTS
+
+
+
+ -
+
+
+ Data Carrier Detect
+
+
+ DCD
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 15
+ 15
+
+
+
+
+ 15
+ 15
+
+
+
+ Data Carrier Detect
+
+
+
+ -
+
+
+ Ring Indicator
+
+
+ RI
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 15
+ 15
+
+
+
+
+ 15
+ 15
+
+
+
+ Ring Indicator
+
+
+
+ -
+
+
+
+ 15
+ 15
+
+
+
+
+ 15
+ 15
+
+
+
+ Clear To Send
+
+
+
+ -
+
+
+ Clear To Send
+
+
+ CTS
+
+
+ Qt::AlignCenter
+
+
+
+
+
+ -
Qt::Vertical
@@ -308,7 +532,7 @@
20
- 40
+ 1
@@ -317,6 +541,14 @@
+
+
+ LedWidget
+ QWidget
+
+ 1
+
+
cbPortList
pbReloadPorts
diff --git a/src/portlist.cpp b/src/portlist.cpp
--- a/src/portlist.cpp
+++ b/src/portlist.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2017 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -50,7 +50,8 @@ void PortListItem::construct(QString nam
{
text += QString(" ") + description;
}
- if (vid && pid)
+ // Note: in some cases internal ports or RS232 ports may have VID&PID
+ if (vid && pid && !name.contains("tty"))
{
text += QString("[%1:").arg(vid, 4, 16, QChar('0'));
text += QString("%1]").arg(pid, 4, 16, QChar('0'));
diff --git a/src/readonlybuffer.cpp b/src/readonlybuffer.cpp
new file mode 100644
--- /dev/null
+++ b/src/readonlybuffer.cpp
@@ -0,0 +1,103 @@
+/*
+ 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 .
+*/
+
+#include
+#include
+
+#include "readonlybuffer.h"
+
+ReadOnlyBuffer::ReadOnlyBuffer(const FrameBuffer* source) :
+ ReadOnlyBuffer(source, 0, source->size())
+{
+ // intentionally empty, see ↑
+}
+
+ReadOnlyBuffer::ReadOnlyBuffer(const FrameBuffer* source, unsigned start, unsigned n)
+{
+ Q_ASSERT(source->size() > 0);
+ Q_ASSERT(start + n <= source->size());
+
+ _size = n;
+ data = new double[_size];
+
+ for (unsigned i = 0; i < n; i++)
+ {
+ data[i] = source->sample(start + i);
+ }
+
+ /// if not exact copy of source re-calculate limits
+ if (start == 0 && n == source->size())
+ {
+ _limits = source->limits();
+ }
+ else
+ {
+ updateLimits();
+ }
+}
+
+ReadOnlyBuffer::ReadOnlyBuffer(const double* source, unsigned ssize)
+{
+ Q_ASSERT(source != nullptr && ssize);
+
+ _size = ssize;
+ data = new double[_size];
+ memcpy(data, source, sizeof(double) * ssize);
+ updateLimits();
+}
+
+ReadOnlyBuffer::~ReadOnlyBuffer()
+{
+ delete[] data;
+}
+
+unsigned ReadOnlyBuffer::size() const
+{
+ return _size;
+}
+
+double ReadOnlyBuffer::sample(unsigned i) const
+{
+ return data[i];
+}
+
+Range ReadOnlyBuffer::limits() const
+{
+ return _limits;
+}
+
+void ReadOnlyBuffer::updateLimits()
+{
+ Q_ASSERT(_size);
+
+ _limits.start = data[0];
+ _limits.end = data[0];
+
+ for (unsigned i = 0; i < _size; i++)
+ {
+ if (data[i] > _limits.end)
+ {
+ _limits.end = data[i];
+ }
+ else if (data[i] < _limits.start)
+ {
+ _limits.start = data[i];
+ }
+ }
+}
diff --git a/src/readonlybuffer.h b/src/readonlybuffer.h
new file mode 100644
--- /dev/null
+++ b/src/readonlybuffer.h
@@ -0,0 +1,62 @@
+/*
+ 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 .
+*/
+
+#ifndef READONLYBUFFER_H
+#define READONLYBUFFER_H
+
+#include "framebuffer.h"
+
+/// A read only frame buffer used for storing snapshot data. Main advantage of
+/// this compared to `RingBuffer` is that reading data should be somewhat
+/// faster.
+class ReadOnlyBuffer : public FrameBuffer
+{
+public:
+ /// Creates a buffer with data copied from `source`. Source buffer cannot be
+ /// empty.
+ ReadOnlyBuffer(const FrameBuffer* source);
+
+ /// Creates a buffer from a slice of the `source`.
+ ///
+ /// @param start start of the slice
+ /// @param n number of samples
+ ///
+ /// @important (start + n) should be smaller or equal than `source->size()`,
+ /// otherwise it's an error.
+ ReadOnlyBuffer(const FrameBuffer* source, unsigned start, unsigned n);
+
+ /// Creates a buffer with data copied from an array
+ ReadOnlyBuffer(const double* source, unsigned ssize);
+
+ ~ReadOnlyBuffer();
+
+ virtual unsigned size() const;
+ virtual double sample(unsigned i) const;
+ virtual Range limits() const;
+
+private:
+ double* data; ///< data storage
+ unsigned _size; ///< data size
+ Range _limits; ///< limits cache
+
+ // TODO: duplicate with `RingBuffer`
+ void updateLimits(); ///< Updates limits cache
+};
+
+#endif // READONLYBUFFER_H
diff --git a/src/recordpanel.cpp b/src/recordpanel.cpp
--- a/src/recordpanel.cpp
+++ b/src/recordpanel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,21 +23,25 @@
#include
#include
#include
+#include
+#include
+#include
#include
+#include
#include "recordpanel.h"
#include "ui_recordpanel.h"
#include "setting_defines.h"
-RecordPanel::RecordPanel(DataRecorder* recorder, ChannelManager* channelMan, QWidget *parent) :
+RecordPanel::RecordPanel(Stream* stream, QWidget *parent) :
QWidget(parent),
ui(new Ui::RecordPanel),
recordToolBar(tr("Record Toolbar")),
- recordAction(QIcon::fromTheme("media-record"), tr("Record"), this)
+ recordAction(QIcon::fromTheme("media-record"), tr("Record"), this),
+ recorder(this)
{
overwriteSelected = false;
- _recorder = recorder;
- _channelMan = channelMan;
+ _stream = stream;
ui->setupUi(this);
@@ -58,16 +62,25 @@ RecordPanel::RecordPanel(DataRecorder* r
connect(ui->cbDisableBuffering, &QCheckBox::toggled,
[this](bool enabled)
{
- _recorder->disableBuffering = enabled;
+ recorder.disableBuffering = enabled;
});
connect(ui->cbWindowsLE, &QCheckBox::toggled,
[this](bool enabled)
{
- _recorder->windowsLE = enabled;
+ recorder.windowsLE = enabled;
});
connect(&recordAction, &QAction::toggled, ui->cbWindowsLE, &QWidget::setDisabled);
+ connect(&recordAction, &QAction::toggled, ui->cbTimestamp, &QWidget::setDisabled);
+ connect(&recordAction, &QAction::toggled, ui->leSeparator, &QWidget::setDisabled);
+ connect(&recordAction, &QAction::toggled, ui->pbBrowse, &QWidget::setDisabled);
+
+ QCompleter *completer = new QCompleter(this);
+ // TODO: QDirModel is deprecated, use QFileSystemModel (but it doesn't work)
+ completer->setModel(new QDirModel(completer));
+ completer->setCaseSensitivity(Qt::CaseInsensitive);
+ ui->leFileName->setCompleter(completer);
}
RecordPanel::~RecordPanel()
@@ -80,11 +93,6 @@ QToolBar* RecordPanel::toolbar()
return &recordToolBar;
}
-bool RecordPanel::isRecording()
-{
- return recordAction.isChecked();
-}
-
bool RecordPanel::recordPaused()
{
return ui->cbRecordPaused->isChecked();
@@ -101,13 +109,75 @@ bool RecordPanel::selectFile()
}
else
{
- selectedFile = fileName;
- ui->lbFileName->setText(selectedFile);
+ setSelectedFile(fileName);
overwriteSelected = QFile::exists(fileName);
return true;
}
}
+QString RecordPanel::selectedFile() const
+{
+ return ui->leFileName->text();
+}
+
+void RecordPanel::setSelectedFile(QString f)
+{
+ ui->leFileName->setText(f);
+}
+
+QString RecordPanel::getSelectedFile()
+{
+ if (selectedFile().isEmpty())
+ {
+ if (!selectFile()) return QString();
+ }
+
+ // assume that file name contains a time format specifier
+ if (selectedFile().contains("%"))
+ {
+ auto ts = formatTimeStamp(selectedFile());
+ if (!QFile::exists(ts) || // file doesn't exists
+ confirmOverwrite(ts)) // exists but user accepted overwrite
+ {
+ return ts;
+ }
+ return QString();
+ }
+
+ // if no timestamp and file exists try autoincrement option
+ if (!overwriteSelected && QFile::exists(selectedFile()))
+ {
+ if (ui->cbAutoIncrement->isChecked())
+ {
+ if (!incrementFileName()) return QString();
+ }
+ else
+ {
+ if (!confirmOverwrite(selectedFile()))
+ return QString();
+ }
+ }
+
+ return selectedFile();
+}
+
+QString RecordPanel::formatTimeStamp(QString t) const
+{
+ auto maxSize = t.size() + 1024;
+ auto r = new char[maxSize];
+
+ time_t rawtime;
+ struct tm * timeinfo;
+
+ time(&rawtime);
+ timeinfo = localtime (&rawtime);
+ strftime(r, maxSize, t.toLatin1().data(), timeinfo);
+
+ auto rs = QString(r);
+ delete r;
+ return rs;
+}
+
void RecordPanel::onRecord(bool start)
{
if (!start)
@@ -126,22 +196,11 @@ void RecordPanel::onRecord(bool start)
}
// check file name
- if (!canceled && selectedFile.isEmpty() && !selectFile())
- {
- canceled = true;
- }
-
- if (!canceled && !overwriteSelected && QFile::exists(selectedFile))
+ QString fn;
+ if (!canceled)
{
- if (ui->cbAutoIncrement->isChecked())
- {
- // TODO: should we increment even if user selected to replace?
- canceled = !incrementFileName();
- }
- else
- {
- canceled = !confirmOverwrite(selectedFile);
- }
+ fn = getSelectedFile();
+ canceled = fn.isEmpty();
}
if (canceled)
@@ -151,13 +210,15 @@ void RecordPanel::onRecord(bool start)
else
{
overwriteSelected = false;
- startRecording();
+ // TODO: show more visible error message when recording fails
+ if (!startRecording(fn))
+ recordAction.setChecked(false);
}
}
bool RecordPanel::incrementFileName(void)
{
- QFileInfo fileInfo(selectedFile);
+ QFileInfo fileInfo(selectedFile());
QString base = fileInfo.completeBaseName();
QRegularExpression regex("(.*?)(\\d+)(?!.*\\d)(.*)");
@@ -192,10 +253,9 @@ bool RecordPanel::incrementFileName(void
}
else
{
- selectedFile = autoFileName;
+ setSelectedFile(autoFileName);
}
- ui->lbFileName->setText(selectedFile);
return true;
}
@@ -222,7 +282,7 @@ bool RecordPanel::confirmOverwrite(QStri
}
else if (mb.clickedButton() == bOverwrite)
{
- selectedFile = fileName;
+ setSelectedFile(fileName);
return true;
}
else // select button
@@ -231,21 +291,29 @@ bool RecordPanel::confirmOverwrite(QStri
}
}
-void RecordPanel::startRecording(void)
+bool RecordPanel::startRecording(QString fileName)
{
QStringList channelNames;
if (ui->cbHeader->isChecked())
{
- channelNames = _channelMan->infoModel()->channelNames();
+ channelNames = _stream->infoModel()->channelNames();
}
- _recorder->startRecording(selectedFile, getSeparator(), channelNames);
- emit recordStarted();
+ if (recorder.startRecording(fileName, getSeparator(),
+ channelNames, ui->cbTimestamp->isChecked()))
+ {
+ _stream->connectFollower(&recorder);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
}
void RecordPanel::stopRecording(void)
{
- emit recordStopped();
- _recorder->stopRecording();
+ recorder.stopRecording();
+ _stream->disconnectFollower(&recorder);
}
void RecordPanel::onPortClose()
@@ -272,6 +340,7 @@ void RecordPanel::saveSettings(QSettings
settings->setValue(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked());
settings->setValue(SG_Record_Header, ui->cbHeader->isChecked());
settings->setValue(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked());
+ settings->setValue(SG_Record_Timestamp, ui->cbTimestamp->isChecked());
settings->setValue(SG_Record_Separator, ui->leSeparator->text());
settings->endGroup();
}
@@ -289,6 +358,8 @@ void RecordPanel::loadSettings(QSettings
settings->value(SG_Record_Header, ui->cbHeader->isChecked()).toBool());
ui->cbDisableBuffering->setChecked(
settings->value(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked()).toBool());
+ ui->cbTimestamp->setChecked(
+ settings->value(SG_Record_Timestamp, ui->cbTimestamp->isChecked()).toBool());
ui->leSeparator->setText(settings->value(SG_Record_Separator, ui->leSeparator->text()).toString());
settings->endGroup();
}
diff --git a/src/recordpanel.h b/src/recordpanel.h
--- a/src/recordpanel.h
+++ b/src/recordpanel.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -26,7 +26,7 @@
#include
#include "datarecorder.h"
-#include "channelmanager.h"
+#include "stream.h"
namespace Ui {
class RecordPanel;
@@ -37,13 +37,11 @@ class RecordPanel : public QWidget
Q_OBJECT
public:
- explicit RecordPanel(DataRecorder* recorder, ChannelManager* channelMan,
- QWidget* parent = 0);
+ explicit RecordPanel(Stream* stream, QWidget* parent = 0);
~RecordPanel();
QToolBar* toolbar();
- bool isRecording();
bool recordPaused();
/// Stores settings into a `QSettings`
@@ -64,10 +62,9 @@ private:
Ui::RecordPanel *ui;
QToolBar recordToolBar;
QAction recordAction;
- QString selectedFile;
bool overwriteSelected;
- DataRecorder* _recorder;
- ChannelManager* _channelMan;
+ DataRecorder recorder;
+ Stream* _stream;
/**
* @brief Increments the file name.
@@ -92,7 +89,26 @@ private:
*/
bool confirmOverwrite(QString fileName);
- void startRecording(void);
+ /// Returns filename in edit box. May be invalid!
+ QString selectedFile() const;
+ /// Sets the filename in edit box.
+ void setSelectedFile(QString f);
+
+ /**
+ * Tries to get a valid file name by handling user interactions and
+ * automatic naming (increment, timestamp etc).
+ *
+ * Returned file name can be used immediately. File name box should also be
+ * set to selected file name.
+ *
+ * @return empty if failure otherwise valid filename
+ */
+ QString getSelectedFile();
+
+ /// Formats timestamp in given text
+ QString formatTimeStamp(QString t) const;
+
+ bool startRecording(QString fileName);
void stopRecording(void);
/// Returns separator text from ui. "\t" is converted to TAB
diff --git a/src/recordpanel.ui b/src/recordpanel.ui
--- a/src/recordpanel.ui
+++ b/src/recordpanel.ui
@@ -7,7 +7,7 @@
0
0
627
- 261
+ 207
@@ -19,6 +19,22 @@
-
-
+
+
+
+ 0
+ 0
+
+
+
+ You can use C `strftime` function format specifiers for timestamps in your file name.
+
+
+ Enter file name or browse
+
+
+
+ -
Select record file
@@ -28,23 +44,56 @@
- -
-
-
-
- 0
- 0
-
-
-
- Select file...
-
-
-
-
+
-
+
+
+ Stop recording when port closed
+
+
+ true
+
+
+
+ -
+
+
+ Increments file name automatically everytime a new recording starts
+
+
+ Auto increment file name
+
+
+ true
+
+
+
+ -
+
+
+ Do not buffer when writing to file. Check this if you are using other software to open the file during recording.
+
+
+ Disable buffering
+
+
+
+ -
+
+
+ Use CR+LF as line endings. Some windows software may not show lines correctly otherwise. Can't be changed during recording.
+
+
+ Windows Style Line Endings
+
+
+ false
+
+
+
-
@@ -71,52 +120,6 @@
- -
-
-
- Do not buffer when writing to file. Check this if you are using other software to open the file during recording.
-
-
- Disable buffering
-
-
-
- -
-
-
- Stop recording when port closed
-
-
- true
-
-
-
- -
-
-
- Increments file name automatically everytime a new recording starts
-
-
- Auto increment file name
-
-
- true
-
-
-
- -
-
-
- Use CR+LF as line endings. Some windows software may not show lines correctly otherwise. Can't be changed during recording.
-
-
- Windows Style Line Endings
-
-
- false
-
-
-
-
@@ -130,6 +133,16 @@
+ -
+
+
+ Insert timestamp (milliseconds from epoch) as first column
+
+
+ Insert timestamp
+
+
+
-
diff --git a/src/ringbuffer.cpp b/src/ringbuffer.cpp
new file mode 100644
--- /dev/null
+++ b/src/ringbuffer.cpp
@@ -0,0 +1,172 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#include
+
+#include "ringbuffer.h"
+
+RingBuffer::RingBuffer(unsigned n)
+{
+ _size = n;
+ data = new double[_size]();
+ headIndex = 0;
+
+ limInvalid = false;
+ limCache = {0, 0};
+}
+
+RingBuffer::~RingBuffer()
+{
+ delete[] data;
+}
+
+unsigned RingBuffer::size() const
+{
+ return _size;
+}
+
+double RingBuffer::sample(unsigned i) const
+{
+ unsigned index = headIndex + i;
+ if (index >= _size) index -= _size;
+ return data[index];
+}
+
+Range RingBuffer::limits() const
+{
+ if (limInvalid) updateLimits();
+ return limCache;
+}
+
+void RingBuffer::resize(unsigned n)
+{
+ Q_ASSERT(n != _size);
+
+ int offset = (int) n - (int) _size;
+ if (offset == 0) return;
+
+ double* newData = new double[n];
+
+ // move data to new array
+ int fill_start = offset > 0 ? offset : 0;
+
+ for (int i = fill_start; i < int(n); i++)
+ {
+ newData[i] = sample(i - offset);
+ }
+
+ // fill the beginning of the new data
+ if (fill_start > 0)
+ {
+ for (int i = 0; i < fill_start; i++)
+ {
+ newData[i] = 0;
+ }
+ }
+
+ // data is ready, clean up and re-point
+ delete data;
+ data = newData;
+ headIndex = 0;
+ _size = n;
+
+ // invalidate bounding rectangle
+ limInvalid = true;
+}
+
+void RingBuffer::addSamples(double* samples, unsigned n)
+{
+ unsigned shift = n;
+ if (shift < _size)
+ {
+ unsigned x = _size - headIndex; // distance of `head` to end
+
+ if (shift <= x) // there is enough room at the end of array
+ {
+ for (unsigned i = 0; i < shift; i++)
+ {
+ data[i+headIndex] = samples[i];
+ }
+
+ if (shift == x) // we used all the room at the end
+ {
+ headIndex = 0;
+ }
+ else
+ {
+ headIndex += shift;
+ }
+ }
+ else // there isn't enough room
+ {
+ for (unsigned i = 0; i < x; i++) // fill the end part
+ {
+ data[i+headIndex] = samples[i];
+ }
+ for (unsigned i = 0; i < (shift-x); i++) // continue from the beginning
+ {
+ data[i] = samples[i+x];
+ }
+ headIndex = shift-x;
+ }
+ }
+ else // number of new samples equal or bigger than current size (doesn't fit)
+ {
+ int x = shift - _size;
+ for (unsigned i = 0; i < _size; i++)
+ {
+ data[i] = samples[i+x];
+ }
+ headIndex = 0;
+ }
+
+ // invalidate cache
+ limInvalid = true;
+}
+
+void RingBuffer::clear()
+{
+ for (unsigned i=0; i < _size; i++)
+ {
+ data[i] = 0.;
+ }
+
+ limCache = {0, 0};
+ limInvalid = false;
+}
+
+void RingBuffer::updateLimits() const
+{
+ limCache.start = data[0];
+ limCache.end = data[0];
+
+ for (unsigned i = 0; i < _size; i++)
+ {
+ if (data[i] > limCache.end)
+ {
+ limCache.end = data[i];
+ }
+ else if (data[i] < limCache.start)
+ {
+ limCache.start = data[i];
+ }
+ }
+
+ limInvalid = false;
+}
diff --git a/src/ringbuffer.h b/src/ringbuffer.h
new file mode 100644
--- /dev/null
+++ b/src/ringbuffer.h
@@ -0,0 +1,49 @@
+/*
+ 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 .
+*/
+
+#ifndef RINGBUFFER_H
+#define RINGBUFFER_H
+
+#include "framebuffer.h"
+
+/// A fast buffer implementation for storing data.
+class RingBuffer : public WFrameBuffer
+{
+public:
+ RingBuffer(unsigned n);
+ ~RingBuffer();
+
+ virtual unsigned size() const;
+ virtual double sample(unsigned i) const;
+ virtual Range limits() const;
+ virtual void resize(unsigned n);
+ virtual void addSamples(double* samples, unsigned n);
+ virtual void clear();
+
+private:
+ unsigned _size; ///< size of `data`
+ double* data; ///< storage
+ unsigned headIndex; ///< indicates the actual `0` index of the ring buffer
+
+ mutable bool limInvalid; ///< Indicates that limits needs to be re-calculated
+ mutable Range limCache; ///< Cache for limits()
+ void updateLimits() const; ///< Updates limits cache
+};
+
+#endif
diff --git a/src/samplecounter.cpp b/src/samplecounter.cpp
new file mode 100644
--- /dev/null
+++ b/src/samplecounter.cpp
@@ -0,0 +1,44 @@
+/*
+ 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 .
+*/
+
+#include
+#include "samplecounter.h"
+
+SampleCounter::SampleCounter()
+{
+ prevTimeMs = QDateTime::currentMSecsSinceEpoch();
+ count = 0;
+}
+
+#include
+
+void SampleCounter::feedIn(const SamplePack& data)
+{
+ count += data.numSamples();
+
+ qint64 current = QDateTime::currentMSecsSinceEpoch();
+ auto diff = current - prevTimeMs;
+ if (diff > 1000) // 1sec
+ {
+ emit spsChanged(1000 * float(count) / diff);
+
+ prevTimeMs = current;
+ count = 0;
+ }
+}
diff --git a/src/samplecounter.h b/src/samplecounter.h
new file mode 100644
--- /dev/null
+++ b/src/samplecounter.h
@@ -0,0 +1,47 @@
+/*
+ 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 .
+*/
+
+#ifndef SAMPLECOUNTER_H
+#define SAMPLECOUNTER_H
+
+#include
+#include "sink.h"
+
+/// A `Sink` class for counting and reporting number of samples per second.
+class SampleCounter : public QObject, public Sink
+{
+ Q_OBJECT
+
+public:
+ SampleCounter();
+
+protected:
+ // implementations for `Sink`
+ virtual void feedIn(const SamplePack& data);
+
+signals:
+ /// Emitted per second if SPS value has changed.
+ void spsChanged(float value);
+
+private:
+ qint64 prevTimeMs;
+ unsigned count;
+};
+
+#endif // SAMPLECOUNTER_H
diff --git a/src/samplepack.cpp b/src/samplepack.cpp
new file mode 100644
--- /dev/null
+++ b/src/samplepack.cpp
@@ -0,0 +1,98 @@
+/*
+ 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 .
+*/
+
+#include
+#include
+
+#include "samplepack.h"
+
+SamplePack::SamplePack(unsigned ns, unsigned nc, bool x)
+{
+ Q_ASSERT(ns > 0 && nc > 0);
+
+ _numSamples = ns;
+ _numChannels = nc;
+
+ _yData = new double[_numSamples * _numChannels]();
+ if (x)
+ {
+ _xData = new double[_numSamples]();
+ }
+ else
+ {
+ _xData = nullptr;
+ }
+}
+
+SamplePack::SamplePack(const SamplePack& other) :
+ SamplePack(other.numSamples(), other.numChannels(), other.hasX())
+{
+ size_t dataSize = sizeof(double) * numSamples();
+ if (hasX())
+ memcpy(xData(), other.xData(), dataSize);
+ memcpy(_yData, other._yData, dataSize * numChannels());
+}
+
+SamplePack::~SamplePack()
+{
+ delete[] _yData;
+ if (_xData != nullptr)
+ {
+ delete[] _xData;
+ }
+}
+
+bool SamplePack::hasX() const
+{
+ return _xData != nullptr;
+}
+
+unsigned SamplePack::numChannels() const
+{
+ return _numChannels;
+}
+
+unsigned SamplePack::numSamples() const
+{
+ return _numSamples;
+}
+
+double* SamplePack::xData() const
+{
+ Q_ASSERT(_xData != nullptr);
+
+ return _xData;
+}
+
+double* SamplePack::data(unsigned channel) const
+{
+ Q_ASSERT(channel < _numChannels);
+
+ return &_yData[channel * _numSamples];
+}
+
+double* SamplePack::xData()
+{
+ return const_cast(static_cast(*this).xData());
+}
+
+double* SamplePack::data(unsigned channel)
+{
+ return const_cast(static_cast(*this).data(channel));
+}
diff --git a/src/samplepack.h b/src/samplepack.h
new file mode 100644
--- /dev/null
+++ b/src/samplepack.h
@@ -0,0 +1,50 @@
+/*
+ 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 .
+*/
+
+#ifndef SAMPLEPACK_H
+#define SAMPLEPACK_H
+
+class SamplePack
+{
+public:
+ /**
+ * @param ns number of samples
+ * @param nc number of channels
+ * @param x has X channel
+ */
+ SamplePack(unsigned ns, unsigned nc, bool x = false);
+ SamplePack(const SamplePack& other);
+ ~SamplePack();
+
+ bool hasX() const;
+ unsigned numChannels() const;
+ unsigned numSamples() const;
+ double* xData() const;
+ double* data(unsigned channel) const;
+
+ double* xData();
+ double* data(unsigned channel);
+
+private:
+ unsigned _numSamples, _numChannels;
+ double* _xData;
+ double* _yData;
+};
+
+#endif // SAMPLEPACK_H
diff --git a/src/scrollzoomer.cpp b/src/scrollzoomer.cpp
--- a/src/scrollzoomer.cpp
+++ b/src/scrollzoomer.cpp
@@ -42,6 +42,10 @@ ScrollZoomer::ScrollZoomer( QWidget *can
d_vScrollData( NULL ),
d_inZoom( false )
{
+ xMin = 0.;
+ xMax = 10000.;
+ hViewSize = 10000;
+
for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
d_alignCanvasToScales[ axis ] = false;
@@ -50,6 +54,8 @@ ScrollZoomer::ScrollZoomer( QWidget *can
d_hScrollData = new ScrollData;
d_vScrollData = new ScrollData;
+ hscrollmove = false;
+ vscrollmove = false;
}
ScrollZoomer::~ScrollZoomer()
@@ -59,6 +65,39 @@ ScrollZoomer::~ScrollZoomer()
delete d_hScrollData;
}
+void ScrollZoomer::setXLimits(double min, double max)
+{
+ xMin = min;
+ xMax = max;
+ setZoomBase();
+}
+
+void ScrollZoomer::setHViewSize(double size)
+{
+ hscrollmove = true;
+ hViewSize = size;
+ setZoomBase();
+ hscrollmove = false;
+}
+
+void ScrollZoomer::setZoomBase(bool doReplot)
+{
+ QwtPlotZoomer::setZoomBase(doReplot);
+ auto zb = zoomBase();
+ auto zs = zoomStack();
+ zb.setRight(xMax);
+ if ((xMax - xMin) < hViewSize)
+ {
+ zb.setLeft(xMin);
+ }
+ else
+ {
+ zb.setLeft(xMax-hViewSize);
+ }
+ zs[0] = zb;
+ setZoomStack(zs);
+}
+
void ScrollZoomer::rescale()
{
QwtScaleWidget *xScale = plot()->axisWidget( xAxis() );
@@ -112,7 +151,45 @@ void ScrollZoomer::rescale()
}
}
- QwtPlotZoomer::rescale();
+ // NOTE: Below snippet is copied from QwtPlotZoomer::rescale() just so that
+ // we can refrain from updating y axis when moving horizontal scrollbar, so
+ // that auto-scale isn't disrupted. Also we don't want to jump around in
+ // x-axis when moving vertical scroll.
+ {
+ QwtPlot *plt = plot();
+ if ( !plt )
+ return;
+
+ const QRectF &rect = zoomStack()[zoomRectIndex()];
+ if ( rect != scaleRect() )
+ {
+ const bool doReplot = plt->autoReplot();
+ plt->setAutoReplot( false );
+
+ if (!vscrollmove)
+ {
+ double x1 = rect.left();
+ double x2 = rect.right();
+ if ( !plt->axisScaleDiv( xAxis() ).isIncreasing() )
+ qSwap( x1, x2 );
+
+ plt->setAxisScale( xAxis(), x1, x2 );
+ }
+
+ if (!hscrollmove)
+ {
+ double y1 = rect.top();
+ double y2 = rect.bottom();
+ if ( !plt->axisScaleDiv( yAxis() ).isIncreasing() )
+ qSwap( y1, y2 );
+
+ plt->setAxisScale( yAxis(), y1, y2 );
+
+ plt->setAutoReplot( doReplot );
+ }
+ plt->replot();
+ }
+ }
updateScrollBars();
}
@@ -237,6 +314,11 @@ bool ScrollZoomer::eventFilter( QObject
layoutScrollBars( rect );
break;
}
+ case QEvent::Show:
+ {
+ layoutScrollBars( canvas()->contentsRect() );
+ break;
+ }
case QEvent::ChildRemoved:
{
const QObject *child =
@@ -265,8 +347,8 @@ bool ScrollZoomer::needScrollBar( Qt::Or
if ( orientation == Qt::Horizontal )
{
mode = d_hScrollData->mode;
- baseMin = zoomBase().left();
- baseMax = zoomBase().right();
+ baseMin = xMin;
+ baseMax = xMax;
zoomMin = zoomRect().left();
zoomMax = zoomRect().right();
}
@@ -323,7 +405,7 @@ void ScrollZoomer::updateScrollBars()
ScrollBar *sb = scrollBar( Qt::Horizontal );
sb->setPalette( plot()->palette() );
sb->setInverted( !plot()->axisScaleDiv( xAxis ).isIncreasing() );
- sb->setBase( zoomBase().left(), zoomBase().right() );
+ sb->setBase( xMin, xMax );
sb->moveSlider( zoomRect().left(), zoomRect().right() );
if ( !sb->isVisibleTo( canvas() ) )
@@ -462,9 +544,17 @@ void ScrollZoomer::scrollBarMoved(
Q_UNUSED( max );
if ( o == Qt::Horizontal )
+ {
+ hscrollmove = true;
moveTo( QPointF( min, zoomRect().top() ) );
+ hscrollmove = false;
+ }
else
+ {
+ vscrollmove = true;
moveTo( QPointF( zoomRect().left(), min ) );
+ vscrollmove = false;
+ }
Q_EMIT zoomed( zoomRect() );
}
@@ -487,3 +577,30 @@ int ScrollZoomer::oppositeAxis( int axis
return axis;
}
+
+void ScrollZoomer::moveTo( const QPointF &pos )
+{
+ // QwtPlotZoomer::moveTo(pos);
+ // return;
+
+ double x = pos.x();
+ double y = pos.y();
+
+ if ( x < xMin )
+ x = xMin;
+ if ( x > xMax - zoomRect().width() )
+ x = xMax - zoomRect().width();
+
+ if ( y < zoomBase().top() )
+ y = zoomBase().top();
+ if ( y > zoomBase().bottom() - zoomRect().height() )
+ y = zoomBase().bottom() - zoomRect().height();
+
+ if ( x != zoomRect().left() || y != zoomRect().top() )
+ {
+ auto zs = zoomStack();
+ zs[zoomRectIndex()].moveTo( x, y );
+ setZoomStack(zs, zoomRectIndex());
+ rescale();
+ }
+}
diff --git a/src/scrollzoomer.h b/src/scrollzoomer.h
--- a/src/scrollzoomer.h
+++ b/src/scrollzoomer.h
@@ -50,8 +50,14 @@ public:
virtual bool eventFilter( QObject *, QEvent * );
+ void setXLimits(double min, double max);
+ void setHViewSize(double size);
+ virtual void setZoomBase(bool doReplot = true);
virtual void rescale();
+public Q_SLOTS:
+ virtual void moveTo( const QPointF & );
+
protected:
virtual ScrollBar *scrollBar( Qt::Orientation );
virtual void updateScrollBars();
@@ -61,6 +67,10 @@ private Q_SLOTS:
void scrollBarMoved( Qt::Orientation o, double min, double max );
private:
+ QRectF d_limits;
+ double xMin, xMax;
+ double hViewSize;
+
bool needScrollBar( Qt::Orientation ) const;
int oppositeAxis( int ) const;
@@ -71,6 +81,8 @@ private:
bool d_inZoom;
bool d_alignCanvasToScales[ QwtPlot::axisCnt ];
+ bool hscrollmove;
+ bool vscrollmove;
};
#endif
diff --git a/src/setting_defines.h b/src/setting_defines.h
--- a/src/setting_defines.h
+++ b/src/setting_defines.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -30,6 +30,7 @@ const char SettingGroup_Channels[] = "Ch
const char SettingGroup_Plot[] = "Plot";
const char SettingGroup_Commands[] = "Commands";
const char SettingGroup_Record[] = "Record";
+const char SettingGroup_UpdateCheck[] = "UpdateCheck";
// mainwindow setting keys
const char SG_MainWindow_Size[] = "size";
@@ -57,6 +58,8 @@ const char SG_Binary_Endianness[] = "end
// ascii reader keys
const char SG_ASCII_NumOfChannels[] = "numOfChannels";
+const char SG_ASCII_Delimiter[] = "delimiter";
+const char SG_ASCII_CustomDelimiter[] = "customDelimiter";
// framed reader keys
const char SG_CustomFrame_NumOfChannels[] = "numOfChannels";
@@ -68,14 +71,19 @@ const char SG_CustomFrame_Endianness[] =
const char SG_CustomFrame_Checksum[] = "checksum";
const char SG_CustomFrame_DebugMode[] = "debugMode";
-// channel manager keys
+// channel info keys
const char SG_Channels_Channel[] = "channel";
const char SG_Channels_Name[] = "name";
const char SG_Channels_Color[] = "color";
const char SG_Channels_Visible[] = "visible";
+const char SG_Channels_Gain[] = "gain";
+const char SG_Channels_GainEn[] = "gainEnabled";
+const char SG_Channels_Offset[] = "offset";
+const char SG_Channels_OffsetEn[] = "offsetEnabled";
// plot settings keys
const char SG_Plot_NumOfSamples[] = "numOfSamples";
+const char SG_Plot_PlotWidth[] = "plotWidth";
const char SG_Plot_IndexAsX[] = "indexAsX";
const char SG_Plot_XMax[] = "xMax";
const char SG_Plot_XMin[] = "xMin";
@@ -102,5 +110,10 @@ const char SG_Record_StopOnClose[]
const char SG_Record_Header[] = "header";
const char SG_Record_Separator[] = "separator";
const char SG_Record_DisableBuffering[] = "disableBuffering";
+const char SG_Record_Timestamp[] = "timestamp";
+
+// update check settings keys
+const char SG_UpdateCheck_Periodic[] = "periodicCheck";
+const char SG_UpdateCheck_LastCheck[] = "lastCheck";
#endif // SETTING_DEFINES_H
diff --git a/src/sink.cpp b/src/sink.cpp
new file mode 100644
--- /dev/null
+++ b/src/sink.cpp
@@ -0,0 +1,70 @@
+/*
+ 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 .
+*/
+
+#include
+#include "sink.h"
+
+void Sink::connectFollower(Sink* sink)
+{
+ Q_ASSERT(!followers.contains(sink));
+
+ followers.append(sink);
+ sink->setNumChannels(_numChannels, _hasX);
+}
+
+void Sink::disconnectFollower(Sink* sink)
+{
+ Q_ASSERT(followers.contains(sink));
+
+ followers.removeOne(sink);
+}
+
+void Sink::feedIn(const SamplePack& data)
+{
+ for (auto sink : followers)
+ {
+ sink->feedIn(data);
+ }
+}
+
+void Sink::setNumChannels(unsigned nc, bool x)
+{
+ _numChannels = nc;
+ _hasX = x;
+ for (auto sink : followers)
+ {
+ sink->setNumChannels(nc, x);
+ }
+}
+
+void Sink::setSource(Source* s)
+{
+ Q_ASSERT((source == nullptr) != (s == nullptr));
+ source = s;
+}
+
+const Source* Sink::connectedSource() const
+{
+ return source;
+}
+
+Source* Sink::connectedSource()
+{
+ return const_cast(static_cast(*this).connectedSource());
+}
diff --git a/src/sink.h b/src/sink.h
new file mode 100644
--- /dev/null
+++ b/src/sink.h
@@ -0,0 +1,73 @@
+/*
+ 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 .
+*/
+
+#ifndef SINK_H
+#define SINK_H
+
+#include
+#include "samplepack.h"
+
+class Source;
+
+class Sink
+{
+public:
+ /// Placeholder virtual destructor
+ virtual ~Sink() {};
+
+ /// Connects a sink to get any data that this sink
+ /// gets. Connecting an already connected sink is an error.
+ void connectFollower(Sink* sink);
+
+ /// Disconnects a follower. Disconnecting an unconnected sink is
+ /// an error.
+ void disconnectFollower(Sink* sink);
+
+ /// Returns the connected source. `nullptr` if it's not connected.
+ const Source* connectedSource() const;
+ Source* connectedSource();
+
+protected:
+ /// Entry point for incoming data. Re-implementations should
+ /// call this function to feed followers.
+ virtual void feedIn(const SamplePack& data);
+
+ /// Is set by connected source. Re-implementations should call
+ /// this function to update followers.
+ virtual void setNumChannels(unsigned nc, bool x);
+
+ /// Set by the connected source when its connected. When
+ /// disconnecting it's set to `nullptr`.
+ ///
+ /// @note Previous source is disconnected.
+ ///
+ /// @important Trying to connect a source while its already
+ /// connected is an error.
+ void setSource(Source* s);
+
+ friend Source;
+
+private:
+ QList followers;
+ Source* source = nullptr; ///< source that this sink is connected to
+ bool _hasX;
+ unsigned _numChannels;
+};
+
+#endif // SINK_H
diff --git a/src/snapshot.cpp b/src/snapshot.cpp
--- a/src/snapshot.cpp
+++ b/src/snapshot.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -25,14 +25,14 @@
#include "snapshot.h"
#include "snapshotview.h"
-Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel) :
+Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved) :
QObject(parent),
cInfoModel(infoModel),
_showAction(this),
_deleteAction("&Delete", this)
{
_name = name;
- _saved = false;
+ _saved = saved;
view = NULL;
mainWindow = parent;
@@ -108,9 +108,24 @@ void Snapshot::setName(QString name)
emit nameChanged(this);
}
+unsigned Snapshot::numChannels() const
+{
+ return yData.size();
+}
+
+unsigned Snapshot::numSamples() const
+{
+ return yData[0]->size();
+}
+
+const ChannelInfoModel* Snapshot::infoModel() const
+{
+ return &cInfoModel;
+}
+
ChannelInfoModel* Snapshot::infoModel()
{
- return &cInfoModel;
+ return const_cast(static_cast(*this).infoModel());
}
QString Snapshot::channelName(unsigned channel)
@@ -120,31 +135,27 @@ QString Snapshot::channelName(unsigned c
void Snapshot::save(QString fileName)
{
- // TODO: remove code duplication (MainWindow::onExportCsv)
QSaveFile file(fileName);
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream fileStream(&file);
- unsigned numOfChannels = data.size();
- unsigned numOfSamples = data[0].size();
-
// print header
- for (unsigned int ci = 0; ci < numOfChannels; ci++)
+ for (unsigned int ci = 0; ci < numChannels(); ci++)
{
fileStream << channelName(ci);
- if (ci != numOfChannels-1) fileStream << ",";
+ if (ci != numChannels()-1) fileStream << ",";
}
fileStream << '\n';
// print rows
- for (unsigned int i = 0; i < numOfSamples; i++)
+ for (unsigned int i = 0; i < numSamples(); i++)
{
- for (unsigned int ci = 0; ci < numOfChannels; ci++)
+ for (unsigned int ci = 0; ci < numChannels(); ci++)
{
- fileStream << data[ci][i].y();
- if (ci != numOfChannels-1) fileStream << ",";
+ fileStream << yData[ci]->sample(i);
+ if (ci != numChannels()-1) fileStream << ",";
}
fileStream << '\n';
}
diff --git a/src/snapshot.h b/src/snapshot.h
--- a/src/snapshot.h
+++ b/src/snapshot.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -27,6 +27,7 @@
#include
#include "channelinfomodel.h"
+#include "readonlybuffer.h"
class SnapshotView;
class MainWindow;
@@ -36,15 +37,19 @@ class Snapshot : public QObject
Q_OBJECT
public:
- Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel);
+ Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved = false);
~Snapshot();
- QVector> data;
+ // TODO: yData of snapshot shouldn't be public, preferable should be handled in constructor
+ QVector 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);
diff --git a/src/snapshotmanager.cpp b/src/snapshotmanager.cpp
--- a/src/snapshotmanager.cpp
+++ b/src/snapshotmanager.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -31,14 +32,14 @@
#include "snapshotmanager.h"
SnapshotManager::SnapshotManager(MainWindow* mainWindow,
- ChannelManager* channelMan) :
+ Stream* stream) :
_menu("&Snapshots"),
_takeSnapshotAction("&Take Snapshot", this),
loadSnapshotAction("&Load Snapshots", this),
clearAction("&Clear Snapshots", this)
{
_mainWindow = mainWindow;
- _channelMan = channelMan;
+ _stream = stream;
_takeSnapshotAction.setToolTip("Take a snapshot of current plot");
_takeSnapshotAction.setShortcut(QKeySequence("F5"));
@@ -63,21 +64,14 @@ SnapshotManager::~SnapshotManager()
}
}
-Snapshot* SnapshotManager::makeSnapshot()
+Snapshot* SnapshotManager::makeSnapshot() const
{
QString name = QTime::currentTime().toString("'Snapshot ['HH:mm:ss']'");
- auto snapshot = new Snapshot(_mainWindow, name, *(_channelMan->infoModel()));
-
- unsigned numOfChannels = _channelMan->numOfChannels();
- unsigned numOfSamples = _channelMan->numOfSamples();
+ auto snapshot = new Snapshot(_mainWindow, name, *(_stream->infoModel()));
- for (unsigned ci = 0; ci < numOfChannels; ci++)
+ for (unsigned ci = 0; ci < _stream->numChannels(); ci++)
{
- snapshot->data.append(QVector(numOfSamples));
- for (unsigned i = 0; i < numOfSamples; i++)
- {
- snapshot->data[ci][i] = QPointF(i, _channelMan->channelBuffer(ci)->sample(i));
- }
+ snapshot->yData.append(new ReadOnlyBuffer(_stream->channel(ci)->yData()));
}
return snapshot;
@@ -158,18 +152,20 @@ void SnapshotManager::loadSnapshotFromFi
unsigned numOfChannels = channelNames.size();
// read data
- QVector> data(numOfChannels);
+ QVector> data(numOfChannels);
+ QTextStream ts(&file);
+ QString line;
unsigned lineNum = 1;
- while (file.canReadLine())
+ while (ts.readLineInto(&line))
{
// parse line
- auto line = QString(file.readLine());
auto split = line.split(',');
if (split.size() != (int) numOfChannels)
{
qCritical() << "Parsing error at line " << lineNum
<< ": number of columns is not consistent.";
+ qCritical() << "Line " << lineNum << ": " << line;
return;
}
@@ -186,16 +182,20 @@ void SnapshotManager::loadSnapshotFromFi
<< "\" to double.";
return;
}
- data[ci].append(QPointF(lineNum-1, y));
+ data[ci].append(y);
}
lineNum++;
}
- ChannelInfoModel channelInfo(channelNames);
-
+ // create snapshot
auto snapshot = new Snapshot(
- _mainWindow, QFileInfo(fileName).baseName(), ChannelInfoModel(channelNames));
- snapshot->data = data;
+ _mainWindow, QFileInfo(fileName).baseName(),
+ ChannelInfoModel(channelNames), true);
+
+ for (unsigned ci = 0; ci < numOfChannels; ci++)
+ {
+ snapshot->yData.append(new ReadOnlyBuffer(data[ci].data(), data[ci].size()));
+ }
addSnapshot(snapshot, false);
}
diff --git a/src/snapshotmanager.h b/src/snapshotmanager.h
--- a/src/snapshotmanager.h
+++ b/src/snapshotmanager.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -24,8 +24,7 @@
#include
#include
-#include "framebuffer.h"
-#include "channelmanager.h"
+#include "stream.h"
#include "snapshot.h"
class MainWindow;
@@ -35,7 +34,7 @@ class SnapshotManager : public QObject
Q_OBJECT
public:
- SnapshotManager(MainWindow* mainWindow, ChannelManager* channelMan);
+ SnapshotManager(MainWindow* mainWindow, Stream* stream);
~SnapshotManager();
QMenu* menu();
@@ -43,13 +42,13 @@ public:
/// Creates a dynamically allocated snapshot object but doesn't record it in snapshots list.
/// @note Caller is responsible for deletion of the returned `Snapshot` object.
- Snapshot* makeSnapshot();
+ Snapshot* makeSnapshot() const;
bool isAllSaved(); ///< returns `true` if all snapshots are saved to a file
private:
MainWindow* _mainWindow;
- ChannelManager* _channelMan;
+ Stream* _stream;
QList snapshots;
diff --git a/src/snapshotview.cpp b/src/snapshotview.cpp
--- a/src/snapshotview.cpp
+++ b/src/snapshotview.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,26 +23,27 @@
SnapshotView::SnapshotView(MainWindow* parent, Snapshot* snapshot) :
QMainWindow(parent),
ui(new Ui::SnapshotView),
- renameDialog(this)
+ renameDialog(this),
+ plotMenu(parent->viewSettings())
{
_snapshot = snapshot;
ui->setupUi(this);
- plotMan = new PlotManager(ui->plotArea, snapshot->infoModel(), this);
- plotMan->setViewSettings(parent->viewSettings());
+ plotMan = new PlotManager(ui->plotArea, &plotMenu, snapshot, this);
ui->menuSnapshot->insertAction(ui->actionClose, snapshot->deleteAction());
this->setWindowTitle(snapshot->displayName());
// initialize curves
- unsigned numOfChannels = snapshot->data.size();
- unsigned numOfSamples = snapshot->data[0].size();
- for (unsigned ci = 0; ci < numOfChannels; ci++)
- {
- plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
- }
- plotMan->setNumOfSamples(numOfSamples);
+ // unsigned numOfChannels = snapshot->data.size();
+ // unsigned numOfSamples = snapshot->data[0].size();
+ // for (unsigned ci = 0; ci < numOfChannels; ci++)
+ // {
+ // plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
+ // }
+ // plotMan->setNumOfSamples(numOfSamples);
+ // plotMan->setPlotWidth(numOfSamples);
renameDialog.setWindowTitle("Rename Snapshot");
renameDialog.setLabelText("Enter new name:");
@@ -52,11 +53,8 @@ SnapshotView::SnapshotView(MainWindow* p
connect(ui->actionSave, &QAction::triggered,
this, &SnapshotView::save);
- // add 'View' menu items
- for (auto a : plotMan->menuActions())
- {
- ui->menuView->addAction(a);
- }
+ // add "View" menu
+ menuBar()->insertMenu(NULL, &plotMenu);
}
SnapshotView::~SnapshotView()
diff --git a/src/snapshotview.h b/src/snapshotview.h
--- a/src/snapshotview.h
+++ b/src/snapshotview.h
@@ -31,6 +31,7 @@
#include "mainwindow.h"
#include "plotmanager.h"
+#include "plotmenu.h"
#include "snapshot.h"
namespace Ui {
@@ -54,6 +55,7 @@ private:
Snapshot* _snapshot;
QInputDialog renameDialog;
PlotManager* plotMan;
+ PlotMenu plotMenu;
void closeEvent(QCloseEvent *event);
diff --git a/src/snapshotview.ui b/src/snapshotview.ui
--- a/src/snapshotview.ui
+++ b/src/snapshotview.ui
@@ -26,7 +26,7 @@
0
0
544
- 25
+ 24
-
-
diff --git a/src/source.cpp b/src/source.cpp
new file mode 100644
--- /dev/null
+++ b/src/source.cpp
@@ -0,0 +1,79 @@
+/*
+ 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 .
+*/
+
+#include
+
+#include "source.h"
+
+Source::~Source()
+{
+ for (auto sink : sinks)
+ {
+ sink->setSource(nullptr);
+ }
+}
+
+void Source::connectSink(Sink* sink)
+{
+ Q_ASSERT(!sinks.contains(sink));
+
+ auto prevSource = sink->connectedSource();
+ if (prevSource != nullptr)
+ {
+ prevSource->disconnect(sink);
+ }
+
+ sinks.append(sink);
+ sink->setSource(this);
+ sink->setNumChannels(numChannels(), hasX());
+}
+
+void Source::disconnect(Sink* sink)
+{
+ Q_ASSERT(sinks.contains(sink));
+ Q_ASSERT(sink->connectedSource() == this);
+
+ sink->setSource(nullptr);
+ sinks.removeOne(sink);
+}
+
+void Source::disconnectSinks()
+{
+ while (!sinks.isEmpty())
+ {
+ auto sink = sinks.takeFirst();
+ sink->setSource(nullptr);
+ }
+}
+
+void Source::feedOut(const SamplePack& data) const
+{
+ for (auto sink : sinks)
+ {
+ sink->feedIn(data);
+ }
+}
+
+void Source::updateNumChannels() const
+{
+ for (auto sink : sinks)
+ {
+ sink->setNumChannels(numChannels(), hasX());
+ }
+}
diff --git a/src/source.h b/src/source.h
new file mode 100644
--- /dev/null
+++ b/src/source.h
@@ -0,0 +1,64 @@
+/*
+ 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 .
+*/
+
+#ifndef SOURCE_H
+#define SOURCE_H
+
+#include
+
+#include "sink.h"
+#include "samplepack.h"
+
+class Source
+{
+public:
+ /// Virtual destructor. Must be called by implementors to notify sinks.
+ virtual ~Source();
+
+ /// Returns true if source has X data
+ virtual bool hasX() const = 0;
+
+ /// Returns number of channels
+ virtual unsigned numChannels() const = 0;
+
+ /// Connects a sink to this source.
+ ///
+ /// If `Sink` is already connected to a source, it's disconnected first.
+ void connectSink(Sink* sink);
+
+ /// Disconnects an already connected sink. Trying to disconnect an
+ /// unconnected sink is an error.
+ void disconnect(Sink* sink);
+
+ /// Disconnects all connected sinks.
+ void disconnectSinks();
+
+protected:
+ /// Feeds "in" given data to connected sinks
+ virtual void feedOut(const SamplePack& data) const;
+
+ /// Updates "number of channels" of connected sinks. Must be
+ /// called when num. channels or hasX changes.
+ void updateNumChannels() const;
+
+private:
+ QList sinks;
+};
+
+#endif // SOURCE_H
diff --git a/src/stream.cpp b/src/stream.cpp
new file mode 100644
--- /dev/null
+++ b/src/stream.cpp
@@ -0,0 +1,248 @@
+/*
+ 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 .
+*/
+
+#include "stream.h"
+#include "ringbuffer.h"
+#include "indexbuffer.h"
+
+Stream::Stream(unsigned nc, bool x, unsigned ns) :
+ _infoModel(nc)
+{
+ _numSamples = ns;
+ _paused = false;
+
+ // create xdata buffer
+ _hasx = x;
+ if (x)
+ {
+ xData = new RingBuffer(ns);
+ }
+ else
+ {
+ xData = new IndexBuffer(ns);
+ }
+
+ // create channels
+ for (unsigned i = 0; i < nc; i++)
+ {
+ auto c = new StreamChannel(i, xData, new RingBuffer(ns), &_infoModel);
+ channels.append(c);
+ }
+}
+
+Stream::~Stream()
+{
+ for (auto ch : channels)
+ {
+ delete ch;
+ }
+ delete xData;
+}
+
+bool Stream::hasX() const
+{
+ return _hasx;
+}
+
+unsigned Stream::numChannels() const
+{
+ return channels.length();
+}
+
+unsigned Stream::numSamples() const
+{
+ return _numSamples;
+}
+
+const StreamChannel* Stream::channel(unsigned index) const
+{
+ Q_ASSERT(index < numChannels());
+ return channels[index];
+}
+
+StreamChannel* Stream::channel(unsigned index)
+{
+ return const_cast(static_cast(*this).channel(index));
+}
+
+const ChannelInfoModel* Stream::infoModel() const
+{
+ return &_infoModel;
+}
+
+ChannelInfoModel* Stream::infoModel()
+{
+ return const_cast(static_cast(*this).infoModel());
+}
+
+void Stream::setNumChannels(unsigned nc, bool x)
+{
+ unsigned oldNum = numChannels();
+ if (oldNum == nc && x == _hasx) return;
+
+ // adjust the number of channels
+ if (nc > oldNum)
+ {
+ for (unsigned i = oldNum; i < nc; i++)
+ {
+ auto c = new StreamChannel(i, xData, new RingBuffer(_numSamples), &_infoModel);
+ channels.append(c);
+ }
+ }
+ else if (nc < oldNum)
+ {
+ for (unsigned i = oldNum-1; i > nc-1; i--)
+ {
+ delete channels.takeLast();
+ }
+ }
+
+ // change the xdata
+ if (x != _hasx)
+ {
+ if (x)
+ {
+ xData = new RingBuffer(_numSamples);
+ }
+ else
+ {
+ xData = new IndexBuffer(_numSamples);
+ }
+
+ 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);
+}
+
+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);
+ if (gainEn || offsetEn)
+ {
+ double* mdata = mPack->data(ci);
+
+ double gain = infoModel()->gain(ci);
+ double offset = infoModel()->offset(ci);
+
+ if (gainEn)
+ {
+ for (unsigned i = 0; i < ns; i++)
+ {
+ mdata[i] *= gain;
+ }
+ }
+ if (offsetEn)
+ {
+ for (unsigned i = 0; i < ns; i++)
+ {
+ mdata[i] += offset;
+ }
+ }
+ }
+ }
+
+ return mPack;
+}
+
+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(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(channels[ci]->yData());
+ double* data = (mPack == nullptr) ? pack.data(ci) : mPack->data(ci);
+ buf->addSamples(data, ns);
+ }
+
+ Sink::feedIn((mPack == nullptr) ? pack : *mPack);
+
+ if (mPack != nullptr) delete mPack;
+ emit dataAdded();
+}
+
+void Stream::pause(bool paused)
+{
+ _paused = paused;
+}
+
+void Stream::clear()
+{
+ for (auto c : channels)
+ {
+ static_cast(c->yData())->clear();
+ }
+}
+
+void Stream::setNumSamples(unsigned value)
+{
+ if (value == _numSamples) return;
+ _numSamples = value;
+
+ xData->resize(value);
+ for (auto c : channels)
+ {
+ static_cast(c->yData())->resize(value);
+ }
+}
+
+void Stream::saveSettings(QSettings* settings) const
+{
+ _infoModel.saveSettings(settings);
+}
+
+void Stream::loadSettings(QSettings* settings)
+{
+ _infoModel.loadSettings(settings);
+}
diff --git a/src/stream.h b/src/stream.h
new file mode 100644
--- /dev/null
+++ b/src/stream.h
@@ -0,0 +1,117 @@
+/*
+ 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 .
+*/
+
+#ifndef STREAM_H
+#define STREAM_H
+
+#include
+#include
+#include
+#include
+
+#include "sink.h"
+#include "source.h"
+#include "channelinfomodel.h"
+#include "streamchannel.h"
+#include "framebuffer.h"
+
+/**
+ * Main waveform storage class. It consists of channels. Channels are
+ * synchronized with each other.
+ *
+ * Implements `Sink` class for data entry. It's expected to be
+ * 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();
+
+ // implementations for `Source`
+ virtual bool hasX() const;
+ virtual unsigned numChannels() const;
+
+ unsigned numSamples() const;
+ const StreamChannel* channel(unsigned index) const;
+ StreamChannel* channel(unsigned index);
+ 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);
+ void setNumSamples(unsigned value);
+
+ /// 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;
+ QList channels;
+
+ ChannelInfoModel _infoModel;
+
+ /**
+ * 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;
+};
+
+
+#endif // STREAM_H
diff --git a/src/streamchannel.cpp b/src/streamchannel.cpp
new file mode 100644
--- /dev/null
+++ b/src/streamchannel.cpp
@@ -0,0 +1,44 @@
+/*
+ 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 .
+*/
+
+#include "streamchannel.h"
+
+StreamChannel::StreamChannel(unsigned i, const FrameBuffer* 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 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;};
diff --git a/src/streamchannel.h b/src/streamchannel.h
new file mode 100644
--- /dev/null
+++ b/src/streamchannel.h
@@ -0,0 +1,60 @@
+/*
+ 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 .
+*/
+
+#ifndef STREAMCHANNEL_H
+#define STREAMCHANNEL_H
+
+#include "framebuffer.h"
+#include "channelinfomodel.h"
+
+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,
+ FrameBuffer* y,
+ ChannelInfoModel* info);
+ ~StreamChannel();
+
+ unsigned index() const;
+ QString name() const;
+ QColor color() const;
+ bool visible() const;
+ const FrameBuffer* xData() const;
+ FrameBuffer* yData();
+ const FrameBuffer* yData() const;
+ const ChannelInfoModel* info() const;
+ void setX(const FrameBuffer* x);
+
+private:
+ unsigned _index;
+ const FrameBuffer* _x;
+ FrameBuffer* _y;
+ ChannelInfoModel* _info;
+};
+
+#endif // STREAMCHANNEL_H
diff --git a/src/updatecheckdialog.cpp b/src/updatecheckdialog.cpp
new file mode 100644
--- /dev/null
+++ b/src/updatecheckdialog.cpp
@@ -0,0 +1,105 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#include "setting_defines.h"
+#include "updatecheckdialog.h"
+#include "ui_updatecheckdialog.h"
+
+UpdateCheckDialog::UpdateCheckDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::UpdateCheckDialog)
+{
+ ui->setupUi(this);
+
+ // by default start from yesterday, so that we check at first run
+ lastCheck = QDate::currentDate().addDays(-1);
+
+ connect(&updateChecker, &UpdateChecker::checkFailed,
+ [this](QString errorMessage)
+ {
+ lastCheck = QDate::currentDate();
+ ui->label->setText(QString("Update check failed.\n") + errorMessage);
+ });
+
+ connect(&updateChecker, &UpdateChecker::checkFinished,
+ [this](bool found, QString newVersion, QString downloadUrl)
+ {
+ QString text;
+ if (!found)
+ {
+ text = "There is no update yet.";
+ }
+ else
+ {
+ show();
+#ifdef UPDATE_TYPE_PKGMAN
+ text = QString("There is a new version: %1. "
+ "Use your package manager to update"
+ " or click to download.")\
+ .arg(newVersion).arg(downloadUrl);
+#else
+ text = QString("Found update to version %1. Click to download.")\
+ .arg(newVersion).arg(downloadUrl);
+#endif
+ }
+
+ lastCheck = QDate::currentDate();
+ ui->label->setText(text);
+ });
+}
+
+UpdateCheckDialog::~UpdateCheckDialog()
+{
+ delete ui;
+}
+
+void UpdateCheckDialog::showEvent(QShowEvent *event)
+{
+ updateChecker.checkUpdate();
+ ui->label->setText("Checking update...");
+}
+
+void UpdateCheckDialog::closeEvent(QShowEvent *event)
+{
+ if (updateChecker.isChecking()) updateChecker.cancelCheck();
+}
+
+void UpdateCheckDialog::saveSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_UpdateCheck);
+ settings->setValue(SG_UpdateCheck_Periodic, ui->cbPeriodic->isChecked());
+ settings->setValue(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate));
+ settings->endGroup();
+}
+
+void UpdateCheckDialog::loadSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_UpdateCheck);
+ ui->cbPeriodic->setChecked(settings->value(SG_UpdateCheck_Periodic,
+ ui->cbPeriodic->isChecked()).toBool());
+ auto lastCheckS = settings->value(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate)).toString();
+ lastCheck = QDate::fromString(lastCheckS, Qt::ISODate);
+ settings->endGroup();
+
+ // start the periodic update if required
+ if (ui->cbPeriodic->isChecked() && lastCheck < QDate::currentDate())
+ {
+ updateChecker.checkUpdate();
+ }
+}
diff --git a/src/updatecheckdialog.h b/src/updatecheckdialog.h
new file mode 100644
--- /dev/null
+++ b/src/updatecheckdialog.h
@@ -0,0 +1,54 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#ifndef UPDATECHECKDIALOG_H
+#define UPDATECHECKDIALOG_H
+
+#include
+#include
+#include
+#include "updatechecker.h"
+
+namespace Ui {
+class UpdateCheckDialog;
+}
+
+class UpdateCheckDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit UpdateCheckDialog(QWidget *parent = 0);
+ ~UpdateCheckDialog();
+
+ /// Stores update settings into a `QSettings`.
+ void saveSettings(QSettings* settings);
+ /// Loads update settings from a `QSettings`.
+ void loadSettings(QSettings* settings);
+
+private:
+ Ui::UpdateCheckDialog *ui;
+ UpdateChecker updateChecker;
+ QDate lastCheck;
+
+ void showEvent(QShowEvent *event);
+ void closeEvent(QShowEvent *event);
+};
+
+#endif // UPDATECHECKDIALOG_H
diff --git a/src/updatecheckdialog.ui b/src/updatecheckdialog.ui
new file mode 100644
--- /dev/null
+++ b/src/updatecheckdialog.ui
@@ -0,0 +1,71 @@
+
+
+ UpdateCheckDialog
+
+
+
+ 0
+ 0
+ 400
+ 148
+
+
+
+ Check Update
+
+
+ -
+
+
+ Checking update...
+
+
+ true
+
+
+
+ -
+
+
+ Updates will be checked only once a day at first start of the application
+
+
+ Check updates periodically
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Close
+
+
+
+
+
+
+
+
+ buttonBox
+ clicked(QAbstractButton*)
+ UpdateCheckDialog
+ close()
+
+
+ 199
+ 125
+
+
+ 199
+ 73
+
+
+
+
+
diff --git a/src/updatechecker.cpp b/src/updatechecker.cpp
new file mode 100644
--- /dev/null
+++ b/src/updatechecker.cpp
@@ -0,0 +1,222 @@
+/*
+ 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 .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "updatechecker.h"
+
+// This link returns the list of downloads in JSON format. Note that we only use
+// the first page because results are sorted new to old.
+const char BB_DOWNLOADS_URL[] = "https://api.bitbucket.org/2.0/repositories/hyozd/serialplot/downloads?fields=values.name,values.links.self.href";
+
+UpdateChecker::UpdateChecker(QObject *parent) :
+ QObject(parent), nam(this)
+{
+ activeReply = NULL;
+
+ connect(&nam, &QNetworkAccessManager::finished,
+ this, &UpdateChecker::onReqFinished);
+}
+
+bool UpdateChecker::isChecking() const
+{
+ return activeReply != NULL && !activeReply->isFinished();
+}
+
+void UpdateChecker::checkUpdate()
+{
+ if (isChecking()) return;
+
+ auto req = QNetworkRequest(QUrl(BB_DOWNLOADS_URL));
+ activeReply = nam.get(req);
+}
+
+void UpdateChecker::cancelCheck()
+{
+ if (activeReply != NULL) activeReply->abort();
+}
+
+void UpdateChecker::onReqFinished(QNetworkReply* reply)
+{
+ if (reply->error() != QNetworkReply::NoError)
+ {
+ emit checkFailed(QString("Network error: ") + reply->errorString());
+ }
+ else
+ {
+ QJsonParseError error;
+ auto data = QJsonDocument::fromJson(reply->readAll(), &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ emit checkFailed(QString("JSon parsing error: ") + error.errorString());
+ }
+ else
+ {
+ QList files;
+ if (!parseData(data, files))
+ {
+ // TODO: emit detailed data contents for logging
+ emit checkFailed("Data parsing error.");
+ }
+ else
+ {
+ FileInfo updateFile;
+ if (findUpdate(files, updateFile))
+ {
+ emit checkFinished(
+ true, updateFile.version.toString(), updateFile.link);
+ }
+ else
+ {
+ emit checkFinished(false, "", "");
+ }
+ }
+ }
+ }
+ reply->deleteLater();
+ activeReply = NULL;
+}
+
+bool UpdateChecker::parseData(const QJsonDocument& data, QList& files) const
+{
+ /* Data is expected to be in this form:
+
+ {
+ "values": [
+ {
+ "name": "serialplot-0.9.1-x86_64.AppImage",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/repositories/hyOzd/serialplot/downloads/serialplot-0.9.1-x86_64.AppImage"
+ }
+ }
+ }, ... ]
+ }
+ */
+
+ if (!data.isObject()) return false;
+
+ auto values = data.object()["values"];
+ if (values == QJsonValue::Undefined || !values.isArray()) return false;
+
+ for (auto value : values.toArray())
+ {
+ if (!value.isObject()) return false;
+
+ auto name = value.toObject().value("name");
+ if (name.isUndefined() || !name.isString())
+ return false;
+
+ auto links = value.toObject().value("links");
+ if (links.isUndefined() || !links.isObject())
+ return false;
+
+ auto self = links.toObject().value("self");
+ if (self.isUndefined() || !self.isObject())
+ return false;
+
+ auto href = self.toObject().value("href");
+ if (href.isUndefined() || !href.isString())
+ return false;
+
+ FileInfo finfo;
+ finfo.name = name.toString();
+ finfo.link = href.toString();
+ finfo.hasVersion = VersionNumber::extract(name.toString(), finfo.version);
+
+ if (finfo.name.contains("amd64") ||
+ finfo.name.contains("x86_64") ||
+ finfo.name.contains("win64"))
+ {
+ finfo.arch = FileArch::amd64;
+ }
+ else if (finfo.name.contains("win32") ||
+ finfo.name.contains("i386"))
+ {
+ finfo.arch = FileArch::_i386;
+ }
+ else
+ {
+ finfo.arch = FileArch::unknown;
+ }
+
+ files += finfo;
+ }
+
+ return true;
+}
+
+bool UpdateChecker::findUpdate(const QList& files, FileInfo& foundFile) const
+{
+ QList fflist;
+
+ // filter the file list according to extension and version number
+ for (int i = 0; i < files.length(); i++)
+ {
+ // file type to look
+#if defined(Q_OS_WIN)
+ const char ext[] = ".exe";
+#else // of course linux
+ const char ext[] = ".appimage";
+#endif
+
+ // file architecture to look
+#if defined(Q_PROCESSOR_X86_64)
+ const FileArch arch = FileArch::amd64;
+#elif defined(Q_PROCESSOR_X86_32)
+ const FileArch arch = FileArch::_i386;
+#elif defined(Q_PROCESSOR_ARM)
+ const FileArch arch = FileArch::arm;
+#else
+ #error Unknown architecture for update file detection.
+#endif
+
+ // filter the file list
+ auto file = files[i];
+ if (file.name.contains(ext, Qt::CaseInsensitive) &&
+ file.arch == arch &&
+ file.hasVersion && file.version > CurrentVersion)
+ {
+ fflist += file;
+ }
+ }
+
+ // sort and find most up to date file
+ if (!fflist.empty())
+ {
+ std::sort(fflist.begin(), fflist.end(),
+ [](const FileInfo& a, const FileInfo& b)
+ {
+ return a.version > b.version;
+ });
+
+ foundFile = fflist[0];
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/src/updatechecker.h b/src/updatechecker.h
new file mode 100644
--- /dev/null
+++ b/src/updatechecker.h
@@ -0,0 +1,77 @@
+/*
+ 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 .
+*/
+
+#ifndef UPDATECHECKER_H
+#define UPDATECHECKER_H
+
+#include
+#include
+#include
+#include
+
+#include "versionnumber.h"
+
+class UpdateChecker : public QObject
+{
+ Q_OBJECT
+public:
+ explicit UpdateChecker(QObject *parent = 0);
+
+ bool isChecking() const;
+
+signals:
+ void checkFinished(bool found, QString newVersion, QString downloadUrl);
+ void checkFailed(QString errorMessage);
+
+public slots:
+ void checkUpdate();
+ void cancelCheck();
+
+private:
+ enum class FileArch
+ {
+ unknown,
+ _i386,
+ amd64,
+ arm
+ };
+
+ struct FileInfo
+ {
+ QString name;
+ QString link;
+ bool hasVersion;
+ VersionNumber version;
+ FileArch arch;
+ };
+
+ QNetworkAccessManager nam;
+ QNetworkReply* activeReply;
+
+ /// Parses json and creates a list of files
+ bool parseData(const QJsonDocument& data, QList& files) const;
+ /// Finds the update file in the file list. Returns `-1` if no new version
+ /// is found.
+ bool findUpdate(const QList& files, FileInfo& foundFile) const;
+
+private slots:
+ void onReqFinished(QNetworkReply* reply);
+};
+
+#endif // UPDATECHECKER_H
diff --git a/src/versionnumber.cpp b/src/versionnumber.cpp
new file mode 100644
--- /dev/null
+++ b/src/versionnumber.cpp
@@ -0,0 +1,103 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#include
+
+#include "versionnumber.h"
+
+VersionNumber::VersionNumber(unsigned mj, unsigned mn, unsigned pt)
+{
+ major = mj;
+ minor = mn;
+ patch = pt;
+}
+
+QString VersionNumber::toString() const
+{
+ return QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
+}
+
+bool VersionNumber::extract(const QString& str, VersionNumber& number)
+{
+ QRegularExpression regexp("(?:[-_vV \\t]|^)(?\\d+)"
+ "(?:\\.(?\\d+))(?:\\.(?\\d+))?[-_ \\t]?");
+ auto match = regexp.match(str, 0, QRegularExpression::PartialPreferCompleteMatch);
+
+ if (!(match.hasMatch() || match.hasPartialMatch())) return false;
+
+ number.major = match.captured("major").toUInt();
+
+ auto zeroIfNull = [](QString str) -> unsigned
+ {
+ if (str.isNull()) return 0;
+ return str.toUInt();
+ };
+
+ number.minor = zeroIfNull(match.captured("minor"));
+ number.patch = zeroIfNull(match.captured("patch"));
+
+ return true;
+}
+
+bool operator==(const VersionNumber& lhs, const VersionNumber& rhs)
+{
+ return lhs.major == rhs.major &&
+ lhs.minor == rhs.minor &&
+ lhs.patch == rhs.patch;
+}
+
+bool operator<(const VersionNumber& lhs, const VersionNumber& rhs)
+{
+ if (lhs.major < rhs.major)
+ {
+ return true;
+ }
+ else if (lhs.major == rhs.major)
+ {
+ if (lhs.minor < rhs.minor)
+ {
+ return true;
+ }
+ else if (lhs.minor == rhs.minor)
+ {
+ if (lhs.patch < rhs.patch) return true;
+ }
+ }
+ return false;
+}
+
+bool operator>(const VersionNumber& lhs, const VersionNumber& rhs)
+{
+ if (lhs.major > rhs.major)
+ {
+ return true;
+ }
+ else if (lhs.major == rhs.major)
+ {
+ if (lhs.minor > rhs.minor)
+ {
+ return true;
+ }
+ else if (lhs.minor == rhs.minor)
+ {
+ if (lhs.patch > rhs.patch) return true;
+ }
+ }
+ return false;
+}
diff --git a/src/versionnumber.h b/src/versionnumber.h
new file mode 100644
--- /dev/null
+++ b/src/versionnumber.h
@@ -0,0 +1,46 @@
+/*
+ Copyright © 2017 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 .
+*/
+
+#ifndef VERSIONNUMBER_H
+#define VERSIONNUMBER_H
+
+#include
+
+struct VersionNumber
+{
+ unsigned major = 0;
+ unsigned minor = 0;
+ unsigned patch = 0;
+
+ VersionNumber(unsigned mj=0, unsigned mn=0, unsigned pt=0);
+
+ /// Convert version number to string.
+ QString toString() const;
+
+ /// Extracts the version number from given string.
+ static bool extract(const QString& str, VersionNumber& number);
+};
+
+bool operator==(const VersionNumber& lhs, const VersionNumber& rhs);
+bool operator<(const VersionNumber& lhs, const VersionNumber& rhs);
+bool operator>(const VersionNumber& lhs, const VersionNumber& rhs);
+
+const VersionNumber CurrentVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
+
+#endif // VERSIONNUMBER_H
diff --git a/src/zoomer.cpp b/src/zoomer.cpp
--- a/src/zoomer.cpp
+++ b/src/zoomer.cpp
@@ -28,6 +28,8 @@ Zoomer::Zoomer(QWidget* widget, bool doR
{
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();
@@ -62,7 +64,7 @@ QwtText Zoomer::trackerTextF(const QPoin
QwtText b = ScrollZoomer::trackerTextF(pos);
const QPolygon pa = selection();
- if (pa.count() < 2)
+ if (!isActive() || pa.count() < 2)
{
return b;
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,5 +1,5 @@
#
-# Copyright © 2017 Hasan Yavuz Özderya
+# Copyright © 2018 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -19,16 +19,77 @@
# Find the QtWidgets library
find_package(Qt5Widgets)
+find_package(Qt5Test)
include_directories("../src")
add_executable(Test EXCLUDE_FROM_ALL
test.cpp
+ test_stream.cpp
+ ../src/samplepack.cpp
+ ../src/sink.cpp
+ ../src/source.cpp
+ ../src/indexbuffer.cpp
+ ../src/linindexbuffer.cpp
+ ../src/ringbuffer.cpp
+ ../src/readonlybuffer.cpp
+ ../src/stream.cpp
+ ../src/streamchannel.cpp
+ ../src/channelinfomodel.cpp
../src/datachunk.cpp
- ../src/chunkedbuffer.cpp)
+ ../src/chunkedbuffer.cpp
+ )
add_test(NAME test1 COMMAND Test)
qt5_use_modules(Test Widgets)
+qt5_wrap_ui(UI_FILES_T
+ ../src/binarystreamreadersettings.ui
+ ../src/asciireadersettings.ui
+ ../src/framedreadersettings.ui
+ ../src/demoreadersettings.ui
+ ../src/numberformatbox.ui
+ ../src/endiannessbox.ui
+ )
+
+# test for readers
+add_executable(TestReaders EXCLUDE_FROM_ALL
+ test_readers.cpp
+ ../src/samplepack.cpp
+ ../src/sink.cpp
+ ../src/source.cpp
+ ../src/abstractreader.cpp
+ ../src/binarystreamreader.cpp
+ ../src/binarystreamreadersettings.cpp
+ ../src/asciireader.cpp
+ ../src/asciireadersettings.cpp
+ ../src/framedreader.cpp
+ ../src/framedreadersettings.cpp
+ ../src/demoreader.cpp
+ ../src/demoreadersettings.cpp
+ ../src/commandedit.cpp
+ ../src/endiannessbox.cpp
+ ../src/numberformatbox.cpp
+ ../src/numberformat.cpp
+ ${UI_FILES_T}
+ )
+qt5_use_modules(TestReaders Widgets Test)
+add_test(NAME test_readers COMMAND TestReaders)
+
+# test for recroder
+add_executable(TestRecorder EXCLUDE_FROM_ALL
+ test_recorder.cpp
+ ../src/samplepack.cpp
+ ../src/sink.cpp
+ ../src/source.cpp
+ ../src/datarecorder.cpp
+)
+qt5_use_modules(TestRecorder Widgets Test)
+add_test(NAME test_recorder COMMAND TestRecorder)
+
set(CMAKE_CTEST_COMMAND ctest -V)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
-add_dependencies(check Test)
+add_dependencies(check
+ Test
+ TestReaders
+ TestRecorder
+ )
diff --git a/tests/catch.hpp b/tests/catch.hpp
--- a/tests/catch.hpp
+++ b/tests/catch.hpp
@@ -11542,4 +11542,3 @@ using Catch::Detail::Approx;
#endif
#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
-
diff --git a/tests/test.cpp b/tests/test.cpp
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,9 +21,370 @@
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"
+#include "samplepack.h"
+#include "source.h"
+#include "indexbuffer.h"
+#include "linindexbuffer.h"
+#include "ringbuffer.h"
+#include "readonlybuffer.h"
#include "datachunk.h"
#include "chunkedbuffer.h"
+#include "test_helpers.h"
+
+TEST_CASE("samplepack with no X", "[memory]")
+{
+ SamplePack pack(100, 3, false);
+
+ REQUIRE_FALSE(pack.hasX());
+ REQUIRE(pack.numChannels() == 3);
+ REQUIRE(pack.numSamples() == 100);
+
+ double* chan0 = pack.data(0);
+ double* chan1 = pack.data(1);
+ double* chan2 = pack.data(2);
+
+ REQUIRE(chan0 == chan1 - 100);
+ REQUIRE(chan1 == chan2 - 100);
+}
+
+TEST_CASE("samplepack with X", "[memory]")
+{
+ SamplePack pack(100, 3, true);
+
+ REQUIRE(pack.hasX());
+ REQUIRE(pack.numChannels() == 3);
+ REQUIRE(pack.numSamples() == 100);
+ REQUIRE(pack.xData() != nullptr);
+}
+
+TEST_CASE("samplepack copy", "[memory]")
+{
+ SamplePack pack(10, 3, true);
+
+ // fill test data
+ for (int i = 0; i < 10; i++)
+ {
+ pack.xData()[i] = i;
+ pack.data(0)[i] = i+5;
+ pack.data(1)[i] = i*2;
+ pack.data(2)[i] = i*3;
+ }
+
+ SamplePack other = pack;
+ // compare
+ for (int i = 0; i < 10; i++)
+ {
+ REQUIRE(other.xData()[i] == i);
+ REQUIRE(other.data(0)[i] == i+5);
+ REQUIRE(other.data(1)[i] == i*2);
+ REQUIRE(other.data(2)[i] == i*3);
+ }
+}
+
+TEST_CASE("sink", "[memory, stream]")
+{
+ TestSink sink;
+ SamplePack pack(100, 3, false);
+
+ sink.setNumChannels(3, false);
+ REQUIRE(sink.numChannels() == 3);
+
+ sink.feedIn(pack);
+ REQUIRE(sink.totalFed == 100);
+ sink.feedIn(pack);
+ REQUIRE(sink.totalFed == 200);
+
+ TestSink follower;
+
+ sink.connectFollower(&follower);
+ REQUIRE(follower.numChannels() == 3);
+ REQUIRE(follower.hasX() == false);
+
+ sink.feedIn(pack);
+ REQUIRE(sink.totalFed == 300);
+ REQUIRE(follower.totalFed == 100);
+
+ sink.setNumChannels(2, true);
+ REQUIRE(follower.numChannels() == 2);
+ REQUIRE(follower.hasX() == true);
+}
+
+TEST_CASE("sink must be created unconnected", "[memory, stream]")
+{
+ TestSink sink;
+ REQUIRE(sink.connectedSource() == NULL);
+}
+
+TEST_CASE("source", "[memory, stream]")
+{
+ TestSink sink;
+
+ TestSource source(3, false);
+
+ REQUIRE(source.numChannels() == 3);
+ REQUIRE(source.hasX() == false);
+
+ source.connectSink(&sink);
+ REQUIRE(sink.numChannels() == 3);
+ REQUIRE(sink.hasX() == false);
+
+ source._setNumChannels(5, true);
+ REQUIRE(sink.numChannels() == 5);
+ REQUIRE(sink.hasX() == true);
+
+ SamplePack pack(100, 5, true);
+ source._feed(pack);
+ REQUIRE(sink.totalFed == 100);
+
+ source.disconnect(&sink);
+ source._feed(pack);
+ REQUIRE(sink.totalFed == 100);
+}
+
+TEST_CASE("source must set/unset sink 'source'", "[memory, stream]")
+{
+ TestSink sink;
+ TestSource source(3, false);
+
+ source.connectSink(&sink);
+ REQUIRE(sink.connectedSource() == &source);
+
+ source.disconnect(&sink);
+ REQUIRE(sink.connectedSource() == NULL);
+}
+
+TEST_CASE("source disconnect all sinks", "[memory, stream]")
+{
+ TestSink sinks[3];
+ TestSource source(3, false);
+
+ // connect sinks
+ for (int i = 0; i < 3; i++)
+ {
+ source.connectSink(&sinks[i]);
+ }
+
+ source.disconnectSinks();
+ for (int i = 0; i < 3; i++)
+ {
+ REQUIRE(sinks[i].connectedSource() == NULL);
+ }
+}
+
+TEST_CASE("IndexBuffer", "[memory, buffer]")
+{
+ IndexBuffer buf(10);
+
+ REQUIRE(buf.size() == 10);
+ for (unsigned i = 0; i < 10; i++)
+ {
+ REQUIRE(buf.sample(i) == i);
+ }
+ auto l = buf.limits();
+ REQUIRE(l.start == 0);
+ REQUIRE(l.end == 9);
+
+ buf.resize(20);
+ REQUIRE(buf.size() == 20);
+ REQUIRE(buf.sample(15) == 15);
+ l = buf.limits();
+ REQUIRE(l.start == 0);
+ REQUIRE(l.end == 19);
+}
+
+TEST_CASE("LinIndexBuffer", "[memory, buffer]")
+{
+ LinIndexBuffer buf(10, 0., 3.0);
+
+ REQUIRE(buf.size() == 10);
+ REQUIRE(buf.sample(0) == 0.);
+ REQUIRE(buf.sample(9) == 3.0);
+ REQUIRE(buf.sample(4) == Approx(1+1/3.));
+
+ auto l = buf.limits();
+ REQUIRE(l.start == 0.);
+ REQUIRE(l.end == 3.);
+
+ buf.resize(20);
+ REQUIRE(buf.size() == 20);
+ REQUIRE(buf.sample(0) == 0.);
+ REQUIRE(buf.sample(9) == Approx(9.*3./19.));
+ REQUIRE(buf.sample(4) == Approx(4.*3./19.));
+ REQUIRE(buf.sample(19) == 3.0);
+
+ 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);
+}
+
+TEST_CASE("RingBuffer sizing", "[memory, buffer]")
+{
+ RingBuffer buf(10);
+
+ REQUIRE(buf.size() == 10);
+
+ buf.resize(5);
+ REQUIRE(buf.size() == 5);
+
+ buf.resize(15);
+ REQUIRE(buf.size() == 15);
+}
+
+TEST_CASE("RingBuffer initial values should be 0", "[memory, buffer]")
+{
+ RingBuffer buf(10);
+
+ for (unsigned i = 0; i < 10; i++)
+ {
+ REQUIRE(buf.sample(i) == 0.);
+ }
+}
+
+TEST_CASE("RingBuffer data access", "[memory, buffer]")
+{
+ RingBuffer buf(10);
+ double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ buf.addSamples(values, 10);
+
+ REQUIRE(buf.size() == 10);
+ for (unsigned i = 0; i < 10; i++)
+ {
+ REQUIRE(buf.sample(i) == values[i]);
+ }
+
+ buf.addSamples(values, 5);
+
+ REQUIRE(buf.size() == 10);
+ for (unsigned i = 0; i < 5; i++)
+ {
+ REQUIRE(buf.sample(i) == values[i+5]);
+ }
+ for (unsigned i = 5; i < 10; i++)
+ {
+ REQUIRE(buf.sample(i) == values[i-5]);
+ }
+}
+
+TEST_CASE("making RingBuffer bigger should keep end values", "[memory, buffer]")
+{
+ RingBuffer buf(5);
+ double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ buf.addSamples(values, 5);
+ buf.resize(10);
+
+ REQUIRE(buf.size() == 10);
+ for (unsigned i = 0; i < 5; i++)
+ {
+ REQUIRE(buf.sample(i) == 0);
+ }
+ for (unsigned i = 5; i < 10; i++)
+ {
+ REQUIRE(buf.sample(i) == values[i-5]);
+ }
+}
+
+TEST_CASE("making RingBuffer smaller should keep end values", "[memory, buffer]")
+{
+ RingBuffer buf(10);
+ double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ buf.addSamples(values, 10);
+ buf.resize(5);
+
+ REQUIRE(buf.size() == 5);
+ for (unsigned i = 0; i < 5; i++)
+ {
+ REQUIRE(buf.sample(i) == values[i+5]);
+ }
+}
+
+TEST_CASE("RingBuffer limits", "[memory, buffer]")
+{
+ RingBuffer buf(10);
+ double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ auto lim = buf.limits();
+ REQUIRE(lim.start == 0.);
+ REQUIRE(lim.end == 0.);
+
+ buf.addSamples(values, 10);
+ lim = buf.limits();
+ REQUIRE(lim.start == 1.);
+ REQUIRE(lim.end == 10.);
+
+ buf.addSamples(&values[9], 1);
+ lim = buf.limits();
+ REQUIRE(lim.start == 2.);
+ REQUIRE(lim.end == 10.);
+
+ buf.addSamples(values, 9);
+ buf.addSamples(values, 1);
+ lim = buf.limits();
+ REQUIRE(lim.start == 1.);
+ REQUIRE(lim.end == 9.);
+}
+
+TEST_CASE("RingBuffer clear", "[memory, buffer]")
+{
+ RingBuffer buf(10);
+ double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ buf.addSamples(values, 10);
+ buf.clear();
+
+ REQUIRE(buf.size() == 10);
+ for (unsigned i = 0; i < 10; i++)
+ {
+ REQUIRE(buf.sample(i) == 0.);
+ }
+ auto lim = buf.limits();
+ REQUIRE(lim.start == 0.);
+ REQUIRE(lim.end == 0.);
+}
+
+TEST_CASE("ReadOnlyBuffer", "[memory, buffer]")
+{
+ IndexBuffer source(10);
+
+ ReadOnlyBuffer buf(&source);
+
+ REQUIRE(buf.size() == 10);
+ auto lim = buf.limits();
+ REQUIRE(lim.start == 0.);
+ REQUIRE(lim.end == 9.);
+ for (unsigned i = 0; i < 10; i++)
+ {
+ REQUIRE(buf.sample(i) == i);
+ }
+}
+
+TEST_CASE("ReadOnlyBuffer sliced constructor", "[memory, buffer]")
+{
+ IndexBuffer source(10);
+
+ ReadOnlyBuffer buf(&source, 5, 4);
+
+ REQUIRE(buf.size() == 4);
+ auto lim = buf.limits();
+ REQUIRE(lim.start == 5.);
+ REQUIRE(lim.end == 8.);
+ for (unsigned i = 0; i < 4; i++)
+ {
+ REQUIRE(buf.sample(i) == (i + 5));
+ }
+}
+
TEST_CASE("DataChunk created empty", "[memory]")
{
DataChunk c(0, 1000);
diff --git a/tests/test_helpers.h b/tests/test_helpers.h
new file mode 100644
--- /dev/null
+++ b/tests/test_helpers.h
@@ -0,0 +1,86 @@
+#ifndef TEST_HELPERS_H
+#define TEST_HELPERS_H
+
+#include "source.h"
+#include "sink.h"
+
+class TestSink : public Sink
+{
+public:
+ int totalFed;
+ int _numChannels;
+ bool _hasX;
+
+ TestSink()
+ {
+ totalFed = 0;
+ _numChannels = 0;
+ _hasX = false;
+ };
+
+ void feedIn(const SamplePack& data)
+ {
+ REQUIRE(data.numChannels() == numChannels());
+
+ totalFed += data.numSamples();
+
+ Sink::feedIn(data);
+ };
+
+ void setNumChannels(unsigned nc, bool x)
+ {
+ _numChannels = nc;
+ _hasX = x;
+
+ Sink::setNumChannels(nc, x);
+ };
+
+ virtual unsigned numChannels() const
+ {
+ return _numChannels;
+ };
+
+ virtual bool hasX() const
+ {
+ return _hasX;
+ };
+};
+
+class TestSource : public Source
+{
+public:
+ int _numChannels;
+ bool _hasX;
+
+ TestSource(unsigned nc, bool x)
+ {
+ _numChannels = nc;
+ _hasX = x;
+ };
+
+ virtual unsigned numChannels() const
+ {
+ return _numChannels;
+ };
+
+ virtual bool hasX() const
+ {
+ return _hasX;
+ };
+
+ void _feed(const SamplePack& data) const
+ {
+ feedOut(data);
+ };
+
+ void _setNumChannels(unsigned nc, bool x)
+ {
+ _numChannels = nc;
+ _hasX = x;
+
+ updateNumChannels();
+ };
+};
+
+
+#endif // TEST_HELPERS_H
diff --git a/tests/test_readers.cpp b/tests/test_readers.cpp
new file mode 100644
--- /dev/null
+++ b/tests/test_readers.cpp
@@ -0,0 +1,210 @@
+/*
+ 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 .
+*/
+
+// This tells Catch to provide a main() - only do this in one cpp file per executable
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+#include
+#include
+#include "binarystreamreader.h"
+#include "asciireader.h"
+#include "framedreader.h"
+#include "demoreader.h"
+
+#include "test_helpers.h"
+
+static const int READYREAD_TIMEOUT = 10; // milliseconds
+
+TEST_CASE("reading data with BinaryStreamReader", "[reader]")
+{
+ QBuffer bufferDev;
+ BinaryStreamReader bs(&bufferDev);
+ bs.enable(true);
+
+ TestSink sink;
+ bs.connectSink(&sink);
+
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+
+ bufferDev.open(QIODevice::ReadWrite);
+ const char data[] = {0x01, 0x02, 0x03, 0x04};
+ bufferDev.write(data, 4);
+ bufferDev.seek(0);
+
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ REQUIRE(spy.wait(READYREAD_TIMEOUT));
+ REQUIRE(sink.totalFed == 4);
+}
+
+TEST_CASE("disabled BinaryStreamReader shouldn't read", "[reader]")
+{
+ QBuffer bufferDev;
+ BinaryStreamReader bs(&bufferDev); // disabled by default
+
+ TestSink sink;
+ bs.connectSink(&sink);
+
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+
+ bufferDev.open(QIODevice::ReadWrite);
+ const char data[] = {0x01, 0x02, 0x03, 0x04};
+ bufferDev.write(data, 4);
+ bufferDev.seek(0);
+
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ // readyRead isn't signaled because there are no connections to it
+ REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
+ REQUIRE(sink.totalFed == 0);
+}
+
+TEST_CASE("reading data with AsciiReader", "[reader, ascii]")
+{
+ QBuffer bufferDev;
+ AsciiReader reader(&bufferDev);
+ reader.enable(true);
+
+ TestSink sink;
+ reader.connectSink(&sink);
+
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+
+ // inject data to the buffer
+ bufferDev.open(QIODevice::ReadWrite);
+ bufferDev.write("0,1,3\n0,1,3\n0,1,3\n0,1,3\n");
+ bufferDev.seek(0);
+
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ REQUIRE(spy.wait(READYREAD_TIMEOUT));
+ REQUIRE(sink._numChannels == 3);
+ REQUIRE(sink._hasX == false);
+ REQUIRE(sink.totalFed == 3);
+}
+
+TEST_CASE("AsciiReader shouldn't read when disabled", "[reader, ascii]")
+{
+ QBuffer bufferDev;
+ AsciiReader reader(&bufferDev); // disabled by default
+
+ TestSink sink;
+ reader.connectSink(&sink);
+
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+
+ // inject data to the buffer
+ bufferDev.open(QIODevice::ReadWrite);
+ bufferDev.write("0,1,3\n0,1,3\n0,1,3\n0,1,3\n");
+ bufferDev.seek(0);
+
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+ REQUIRE(sink.totalFed == 0);
+}
+
+TEST_CASE("reading data with FramedReader", "[reader]")
+{
+ QBuffer bufferDev;
+ FramedReader reader(&bufferDev);
+ reader.enable(true);
+
+ TestSink sink;
+ reader.connectSink(&sink);
+
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+
+ bufferDev.open(QIODevice::ReadWrite);
+ const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
+ bufferDev.write((const char*) data, 7);
+ bufferDev.seek(0);
+
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ REQUIRE(spy.wait(READYREAD_TIMEOUT));
+ REQUIRE(sink.totalFed == 4);
+}
+
+TEST_CASE("FramedReader shouldn't read when disabled", "[reader]")
+{
+ QBuffer bufferDev;
+ FramedReader reader(&bufferDev);
+
+ TestSink sink;
+ reader.connectSink(&sink);
+
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+
+ bufferDev.open(QIODevice::ReadWrite);
+ const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
+ bufferDev.write((const char*) data, 7);
+ bufferDev.seek(0);
+
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
+ REQUIRE(sink.totalFed == 0);
+}
+
+TEST_CASE("Generating data with DemoReader", "[reader, demo]")
+{
+ QBuffer bufferDev; // not actually used
+ DemoReader demoReader(&bufferDev);
+ demoReader.enable(true);
+
+ TestSink sink;
+ demoReader.connectSink(&sink);
+ REQUIRE(sink._numChannels == 1);
+ REQUIRE(sink._hasX == false);
+
+ // we need to wait somehow, we are not actually looking for signals
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ REQUIRE_FALSE(spy.wait(1000)); // we need some time for demoreader to produce data
+ REQUIRE(sink.totalFed >= 9);
+}
+
+TEST_CASE("DemoReader shouldn't generate data when paused", "[reader, demo]")
+{
+ QBuffer bufferDev; // not actually used
+ DemoReader demoReader(&bufferDev); // paused by default
+
+ TestSink sink;
+ demoReader.connectSink(&sink);
+ REQUIRE(sink._numChannels == 1);
+
+ // we need to wait somehow, we are not actually looking for signals
+ QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
+ REQUIRE_FALSE(spy.wait(1000)); // we need some time for demoreader to produce data
+ REQUIRE(sink.totalFed == 0);
+}
+
+// Note: this is added because `QApplication` must be created for widgets
+#include
+int main(int argc, char* argv[])
+{
+ QApplication a(argc, argv);
+
+ int result = Catch::Session().run( argc, argv );
+
+ return result;
+}
diff --git a/tests/test_recorder.cpp b/tests/test_recorder.cpp
new file mode 100644
--- /dev/null
+++ b/tests/test_recorder.cpp
@@ -0,0 +1,107 @@
+/*
+ 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 .
+*/
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#include "catch.hpp"
+
+#include
+#include "datarecorder.h"
+#include "test_helpers.h"
+
+#define TEST_FILE_NAME "sp_test_recording.csv"
+
+TEST_CASE("test recording single channel", "[recorder]")
+{
+ DataRecorder rec;
+ TestSource source(1, false);
+
+ // temporary file, remove if exists
+ auto fileName = QDir::tempPath() + QString("/" TEST_FILE_NAME);
+ if (QFile::exists(fileName)) QFile::remove(fileName);
+
+ // connect source → sink
+ source.connectSink(&rec);
+
+ // prepare data
+ QStringList channelNames({"Channel 1"});
+ SamplePack samples(5, 1);
+ for (int i = 0; i < 5; i++)
+ {
+ samples.data(0)[i] = i+1;
+ }
+
+ // test
+ rec.startRecording(fileName, ",", channelNames, false);
+ source._feed(samples);
+ rec.stopRecording();
+
+ // read file contents back
+ QFile recordFile(fileName);
+ REQUIRE(recordFile.open(QIODevice::ReadOnly | QIODevice::Text));
+ // NOTE: mind the extra parantheses, otherwise 'catch' macros fail to compile
+ REQUIRE((recordFile.readLine() == "Channel 1\n"));
+ for (int i = 0; i < 5; i++)
+ REQUIRE((recordFile.readLine() == QString("%1\n").arg(i+1)));
+
+ // cleanup
+ if (QFile::exists(fileName)) QFile::remove(fileName);
+}
+
+TEST_CASE("test recording multiple channels", "[recorder]")
+{
+ DataRecorder rec;
+ TestSource source(3, false);
+
+ // temporary file, remove if exists
+ auto fileName = QDir::tempPath() + QString("/" TEST_FILE_NAME);
+ if (QFile::exists(fileName)) QFile::remove(fileName);
+
+ // connect source → sink
+ source.connectSink(&rec);
+
+ // prepare data
+ QStringList channelNames({"Channel 1", "Channel 2", "Channel 3"});
+ SamplePack samples(5, 3);
+ for (int ci = 0; ci < 3; ci++)
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ samples.data(ci)[i] = (ci+1)*(i+1);
+ }
+ }
+
+ // test
+ rec.startRecording(fileName, ",", channelNames, false);
+ source._feed(samples);
+ rec.stopRecording();
+
+ // read file contents back
+ QFile recordFile(fileName);
+ REQUIRE(recordFile.open(QIODevice::ReadOnly | QIODevice::Text));
+ // NOTE: mind the extra parantheses, otherwise 'catch' macros fail to compile
+ REQUIRE((recordFile.readLine() == "Channel 1,Channel 2,Channel 3\n"));
+ REQUIRE((recordFile.readLine() == "1,2,3\n"));
+ REQUIRE((recordFile.readLine() == "2,4,6\n"));
+ REQUIRE((recordFile.readLine() == "3,6,9\n"));
+ REQUIRE((recordFile.readLine() == "4,8,12\n"));
+ REQUIRE((recordFile.readLine() == "5,10,15\n"));
+
+ // cleanup
+ if (QFile::exists(fileName)) QFile::remove(fileName);
+}
diff --git a/tests/test_stream.cpp b/tests/test_stream.cpp
new file mode 100644
--- /dev/null
+++ b/tests/test_stream.cpp
@@ -0,0 +1,246 @@
+/*
+ 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 .
+*/
+
+#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.hasX());
+ REQUIRE(s.numSamples() == 0);
+}
+
+TEST_CASE("construction of stream with parameters", "[memory, stream]")
+{
+ Stream s(4, true, 100);
+
+ REQUIRE(s.numChannels() == 4);
+ 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]")
+{
+ Stream s;
+ TestSource so(3, false);
+ 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);
+ }
+
+ // 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);
+ }
+
+ // 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);
+ }
+}
+
+TEST_CASE("adding data to a stream with no X", "[memory, stream, data, sink]")
+{
+ 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;
+ }
+ }
+
+ TestSource so(3, false);
+ 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);
+ }
+ for (unsigned i = 5; i < 10; i++)
+ {
+ REQUIRE(y->sample(i) == i-5);
+ }
+ }
+}
+
+TEST_CASE("adding data to a stream with X", "[memory, stream, data, sink]")
+{
+ Stream s(3, true, 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);
+
+ // 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);
+ }
+ for (unsigned i = 5; i < 10; i++)
+ {
+ REQUIRE(y->sample(i) == i-5);
+ }
+ }
+
+ // 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);
+ }
+}
+
+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;
+ }
+ }
+
+ TestSource so(3, false);
+ so.connectSink(&s);
+
+ // test
+ s.pause(true);
+ 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 < 10; i++)
+ {
+ REQUIRE(y->sample(i) == 0);
+ }
+ }
+}
+
+TEST_CASE("clear stream 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;
+ }
+ }
+
+ TestSource so(3, false);
+ so.connectSink(&s);
+
+ // test
+ so._feed(pack);
+ s.clear();
+
+ for (unsigned ci = 0; ci < 3; ci++)
+ {
+ const StreamChannel* c = s.channel(ci);
+ const FrameBuffer* y = c->yData();
+
+ for (unsigned i = 0; i < 10; i++)
+ {
+ REQUIRE(y->sample(i) == 0);
+ }
+ }
+}