diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
#
-# Copyright © 2018 Hasan Yavuz Özderya
+# Copyright © 2019 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -44,31 +44,11 @@ else (BUILD_QWT)
find_package(Qwt 6.1 REQUIRED)
endif (BUILD_QWT)
-# If set, cmake will download QtColorWidgets over git, build and use it as a static library.
-set(BUILD_QTCOLORWIDGETS true CACHE BOOL "Download and build QtColorWidgets library automatically.")
-if (BUILD_QTCOLORWIDGETS)
- include(BuildQColorWidgets)
-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
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QTCOLORWIDGETS_FLAGS}")
-
# wrap UI and resource files
qt5_wrap_ui(UI_FILES
src/mainwindow.ui
@@ -87,6 +67,7 @@ qt5_wrap_ui(UI_FILES
src/framedreadersettings.ui
src/demoreadersettings.ui
src/updatecheckdialog.ui
+ src/datatextview.ui
)
if (WIN32)
@@ -126,6 +107,7 @@ add_executable(${PROGRAM_NAME} WIN32
src/ringbuffer.cpp
src/ringbuffer.cpp
src/indexbuffer.cpp
+ src/linindexbuffer.cpp
src/readonlybuffer.cpp
src/framebufferseries.cpp
src/numberformatbox.cpp
@@ -152,6 +134,9 @@ add_executable(${PROGRAM_NAME} WIN32
src/source.cpp
src/sink.cpp
src/samplecounter.cpp
+ src/ledwidget.cpp
+ src/datatextview.cpp
+ src/bpslabel.cpp
misc/windows_icon.rc
${UI_FILES}
${RES_FILES}
@@ -160,8 +145,6 @@ add_executable(${PROGRAM_NAME} WIN32
# Use the Widgets module from Qt 5.
target_link_libraries(${PROGRAM_NAME}
${QWT_LIBRARY}
- ${QTCOLORWIDGETS_LIBRARIES}
- ${LEDWIDGET_LIBRARY}
)
qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Network)
@@ -169,15 +152,6 @@ if (BUILD_QWT)
add_dependencies(${PROGRAM_NAME} QWT)
endif ()
-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")
@@ -205,6 +179,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}
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}\\\" ")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROGRAM_NAME=\\\"${PROGRAM_NAME}\\\" ")
# add make run target
add_custom_target(run
diff --git a/cmake/modules/BuildLedWidget.cmake b/cmake/modules/BuildLedWidget.cmake
deleted file mode 100644
--- a/cmake/modules/BuildLedWidget.cmake
+++ /dev/null
@@ -1,30 +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(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/BuildQColorWidgets.cmake b/cmake/modules/BuildQColorWidgets.cmake
deleted file mode 100644
--- a/cmake/modules/BuildQColorWidgets.cmake
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# 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(ExternalProject)
-
-ExternalProject_Add(QCW
- PREFIX qcw
- 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 "")
-
-ExternalProject_Get_Property(QCW binary_dir source_dir)
-set(QTCOLORWIDGETS_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED")
-set(QTCOLORWIDGETS_LIBRARY ${binary_dir}/libColorWidgets-qt5.a)
-set(QTCOLORWIDGETS_INCLUDE_DIR ${source_dir}/include)
-
-set(QTCOLORWIDGETS_LIBRARIES ${QTCOLORWIDGETS_LIBRARY})
-set(QTCOLORWIDGETS_INCLUDE_DIRS ${QTCOLORWIDGETS_INCLUDE_DIR})
diff --git a/cmake/modules/BuildQwt.cmake b/cmake/modules/BuildQwt.cmake
--- a/cmake/modules/BuildQwt.cmake
+++ b/cmake/modules/BuildQwt.cmake
@@ -1,5 +1,5 @@
#
-# Copyright © 2016 Hasan Yavuz Özderya
+# Copyright © 2019 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -21,7 +21,8 @@ include(ExternalProject)
ExternalProject_Add(QWT
PREFIX qwt
- SVN_REPOSITORY svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1
+ # SVN_REPOSITORY svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1
+ URL https://sourceforge.net/projects/qwt/files/qwt/6.1.4/qwt-6.1.4.tar.bz2
# disable QwtDesigner plugin and enable static build
PATCH_COMMAND sed -i -r -e "s/QWT_CONFIG\\s*\\+=\\s*QwtDesigner/#&/"
-e "s/QWT_CONFIG\\s*\\+=\\s*QwtDll/#&/"
diff --git a/cmake/modules/FindQtColorWidgets.cmake b/cmake/modules/FindQtColorWidgets.cmake
deleted file mode 100644
--- a/cmake/modules/FindQtColorWidgets.cmake
+++ /dev/null
@@ -1,35 +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 .
-#
-
-# Note: this script is intended for the debian package created for serialplot.
-
-find_library(QTCOLORWIDGETS_LIBRARY "libColorWidgets-qt5.a")
-find_path(QTCOLORWIDGETS_INCLUDE_DIR "color_preview.hpp" PATHS "/usr/include/qtcolorwidgets/" NO_DEFAULT_PATH)
-
-mark_as_advanced(QTCOLORWIDGETS_LIBRARY QTCOLORWIDGETS_INCLUDE_DIR)
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(QtColorWidgets DEFAULT_MSG QTCOLORWIDGETS_LIBRARY QTCOLORWIDGETS_INCLUDE_DIR)
-
-if (QTCOLORWIDGETS_FOUND)
- set(QTCOLORWIDGETS_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED")
- mark_as_advanced(QTCOLORWIDGETS_FLAGS)
- set(QTCOLORWIDGETS_LIBRARIES ${QTCOLORWIDGETS_LIBRARY})
- set(QTCOLORWIDGETS_INCLUDE_DIRS ${QTCOLORWIDGETS_INCLUDE_DIR})
-endif (QTCOLORWIDGETS_FOUND)
diff --git a/cmake/modules/FindQwt.cmake b/cmake/modules/FindQwt.cmake
--- a/cmake/modules/FindQwt.cmake
+++ b/cmake/modules/FindQwt.cmake
@@ -1,5 +1,5 @@
#
-# Copyright © 2017 Hasan Yavuz Özderya
+# Copyright © 2019 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -66,8 +66,7 @@ endif(qwt_roots)
if(QWT_ROOT)
set(QWT_INCLUDE_DIR "${QWT_ROOT}/include")
- find_library(QWT_LIBRARY "qwt-qt5"
- PATHS "${QWT_ROOT}/lib")
+ find_library(QWT_LIBRARY NAMES "qwt-qt5" "qwt" PATHS "${QWT_ROOT}/lib")
else (QWT_ROOT)
## Look into system locations
find_path(QWT_INCLUDE_DIR qwt_plot.h PATHS /usr/include/qwt)
@@ -90,7 +89,7 @@ else (QWT_ROOT)
endif(qwt_version_string)
endif (QWT_INCLUDE_DIR)
# look into system locations for lib file
- find_library(QWT_LIBRARY "qwt-qt5" PATHS /usr/lib)
+ find_library(QWT_LIBRARY NAMES "qwt-qt5" "qwt" PATHS /usr/lib)
endif(QWT_ROOT)
# set version variables
@@ -103,8 +102,24 @@ if(QWT_VERSION)
QWT_PATCH_VERSION ${QWT_VERSION})
endif(QWT_VERSION)
+# check Qwt library 'Qt' version
+if (QWT_LIBRARY)
+ include(GetPrerequisites)
+ GET_PREREQUISITES(${QWT_LIBRARY} qwt_lib_deps 0 0 "" "")
+ set(qwt_is_qt5 FALSE)
+ foreach (dep ${qwt_lib_deps})
+ if (${dep} MATCHES "libQt5Gui")
+ set(qwt_is_qt5 TRUE)
+ endif()
+ endforeach ()
+ if (NOT qwt_is_qt5)
+ message(WARNING "Found qwt library (${QWT_LIBRARY}) isn't compiled with Qt5!")
+ LIST_PREREQUISITES(${QWT_LIBRARY})
+ endif()
+endif (QWT_LIBRARY)
+
# set QWT_FOUND
-if(QWT_INCLUDE_DIR AND QWT_LIBRARY)
+if(QWT_INCLUDE_DIR AND QWT_LIBRARY AND qwt_is_qt5)
set(QWT_INCLUDE_DIRS ${QWT_INCLUDE_DIR})
set(QWT_LIBRARIES ${QWT_LIBRARY})
set(QWT_FOUND true)
@@ -114,6 +129,9 @@ endif()
# errors
if(NOT QWT_FOUND)
+ unset(QWT_INCLUDE_DIR CACHE)
+ unset(QWT_LIBRARY CACHE)
+
if(Qwt_FIND_QUIET)
message(WARNING "Couldn't find Qwt.")
elseif(Qwt_FIND_REQUIRED)
diff --git a/serialplot.pro b/serialplot.pro
--- a/serialplot.pro
+++ b/serialplot.pro
@@ -1,5 +1,5 @@
#
-# Copyright © 2015 Hasan Yavuz Özderya
+# Copyright © 2019 Hasan Yavuz Özderya
#
# This file is part of serialplot.
#
@@ -23,7 +23,7 @@
#
#-------------------------------------------------
-QT += core gui serialport
+QT += core gui serialport network svg
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
@@ -31,47 +31,72 @@ TARGET = serialplot
TEMPLATE = app
CONFIG += qwt
+# LIBS += -lqwt # enable this line if qwt pri files aren't installed
+
+DEFINES += PROGRAM_NAME="\\\"serialplot\\\""
+
+DEFINES += VERSION_MAJOR=10 VERSION_MINOR=0 VERSION_PATCH=0 VERSION_STRING=\\\"10.0.0\\\"
SOURCES += \
- src/main.cpp\
+ src/main.cpp \
src/mainwindow.cpp \
src/portcontrol.cpp \
src/plot.cpp \
src/zoomer.cpp \
+ src/scrollzoomer.cpp \
+ src/scrollbar.cpp \
src/hidabletabwidget.cpp \
- src/framebuffer.cpp \
src/scalepicker.cpp \
src/scalezoomer.cpp \
src/portlist.cpp \
+ src/snapshot.cpp \
src/snapshotview.cpp \
src/snapshotmanager.cpp \
- src/snapshot.cpp \
src/plotsnapshotoverlay.cpp \
src/commandpanel.cpp \
src/commandwidget.cpp \
src/commandedit.cpp \
src/dataformatpanel.cpp \
+ src/plotcontrolpanel.cpp \
+ src/recordpanel.cpp \
+ 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/indexbuffer.cpp \
+ src/linindexbuffer.cpp \
+ src/readonlybuffer.cpp \
src/framebufferseries.cpp \
- src/plotcontrolpanel.cpp \
src/numberformatbox.cpp \
src/endiannessbox.cpp \
- src/framedreadersettings.cpp \
src/abstractreader.cpp \
src/binarystreamreader.cpp \
src/binarystreamreadersettings.cpp \
+ src/asciireader.cpp \
src/asciireadersettings.cpp \
- src/asciireader.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/recordpanel.cpp \
src/updatechecker.cpp \
+ src/versionnumber.cpp \
src/updatecheckdialog.cpp \
- src/demoreadersettings.cpp
+ src/samplepack.cpp \
+ src/source.cpp \
+ src/sink.cpp \
+ src/samplecounter.cpp \
+ src/ledwidget.cpp \
+ src/datatextview.cpp \
+ src/bpslabel.cpp
HEADERS += \
src/mainwindow.h \
@@ -79,7 +104,6 @@ HEADERS += \
src/portcontrol.h \
src/floatswap.h \
src/plot.h \
- src/zoomer.h \
src/hidabletabwidget.h \
src/framebuffer.h \
src/scalepicker.h \
@@ -95,7 +119,6 @@ HEADERS += \
src/dataformatpanel.h \
src/tooltipfilter.h \
src/sneakylineedit.h \
- src/channelmanager.h \
src/framebufferseries.h \
src/plotcontrolpanel.h \
src/numberformatbox.h \
@@ -114,7 +137,32 @@ HEADERS += \
src/recordpanel.h \
src/updatechecker.h \
src/updatecheckdialog.h \
- src/demoreadersettings.h
+ src/demoreadersettings.h \
+ src/datatextview.h \
+ src/bpslabel.h \
+ src/barchart.h \
+ src/barplot.h \
+ src/barscaledraw.h \
+ src/channelinfomodel.h \
+ src/datarecorder.h \
+ src/defines.h \
+ src/indexbuffer.h \
+ src/ledwidget.h \
+ src/linindexbuffer.h \
+ src/plotmenu.h \
+ src/readonlybuffer.h \
+ src/ringbuffer.h \
+ src/samplecounter.h \
+ src/samplepack.h \
+ src/scrollbar.h \
+ src/scrollzoomer.h \
+ src/sink.h \
+ src/source.h \
+ src/streamchannel.h \
+ src/stream.h \
+ src/version.h \
+ src/versionnumber.h \
+ src/zoomer.h
FORMS += \
src/mainwindow.ui \
@@ -132,7 +180,8 @@ FORMS += \
src/asciireadersettings.ui \
src/recordpanel.ui \
src/updatecheckdialog.ui \
- src/demoreadersettings.ui
+ src/demoreadersettings.ui \
+ src/datatextview.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 © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -23,6 +23,7 @@ AbstractReader::AbstractReader(QIODevice
QObject(parent)
{
_device = device;
+ bytesRead = 0;
}
void AbstractReader::pause(bool enabled)
@@ -43,3 +44,15 @@ void AbstractReader::enable(bool enabled
disconnectSinks();
}
}
+
+void AbstractReader::onDataReady()
+{
+ bytesRead += readData();
+}
+
+unsigned AbstractReader::getBytesRead()
+{
+ unsigned r = bytesRead;
+ bytesRead = 0;
+ return r;
+}
diff --git a/src/abstractreader.h b/src/abstractreader.h
--- a/src/abstractreader.h
+++ b/src/abstractreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -49,6 +49,9 @@ public:
/// None of the current readers support X channel at the moment
bool hasX() const final { return false; };
+ /// Read and 'zero' the byte counter
+ unsigned getBytesRead();
+
signals:
// TODO: should we keep this?
void numOfChannelsChanged(unsigned);
@@ -63,12 +66,25 @@ public slots:
void pause(bool enabled);
protected:
+ /// Reader should read from this device in `readData()` function.
QIODevice* _device;
+
+ /// Reader should check this variable to determine if reading is
+ /// paused in `readData()`
bool paused;
-protected slots:
- /// all derived readers has to override this function
- virtual void onDataReady() = 0;
+ /**
+ * Called when `readyRead` is signaled by the device. This is
+ * where the implementors should read the data and return the
+ * exact number of bytes read from the device.
+ */
+ virtual unsigned readData() = 0;
+
+private:
+ unsigned bytesRead;
+
+private slots:
+ void onDataReady();
};
#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 © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -70,21 +70,20 @@ void AsciiReader::enable(bool enabled)
if (enabled)
{
firstReadAfterEnable = true;
- QObject::connect(_device, &QIODevice::readyRead,
- this, &AsciiReader::onDataReady);
}
- else
- {
- QObject::disconnect(_device, 0, this, 0);
- disconnectSinks();
- }
+
+ AbstractReader::enable(enabled);
}
-void AsciiReader::onDataReady()
+unsigned AsciiReader::readData()
{
+ unsigned numBytesRead = 0;
+
while(_device->canReadLine())
{
- QString line = QString(_device->readLine());
+ QByteArray bytes = _device->readLine();
+ QString line = QString(bytes);
+ numBytesRead += bytes.size();
// discard only once when we just started reading
if (firstReadAfterEnable)
@@ -127,8 +126,11 @@ void AsciiReader::onDataReady()
// commit data
feedOut(*samples);
+ delete samples;
}
}
+
+ return numBytesRead;
}
SamplePack* AsciiReader::parseLine(const QString& line) const
diff --git a/src/asciireader.h b/src/asciireader.h
--- a/src/asciireader.h
+++ b/src/asciireader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -50,8 +50,10 @@ private:
bool firstReadAfterEnable = false;
+ unsigned readData() override;
+
private slots:
- void onDataReady() override;
+
/**
* Parses given line and returns sample pack.
*
diff --git a/src/asciireadersettings.cpp b/src/asciireadersettings.cpp
--- a/src/asciireadersettings.cpp
+++ b/src/asciireadersettings.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,6 +21,7 @@
#include
#include "utils.h"
+#include "defines.h"
#include "setting_defines.h"
#include "asciireadersettings.h"
@@ -35,6 +36,8 @@ AsciiReaderSettings::AsciiReaderSettings
auto validator = new QRegularExpressionValidator(QRegularExpression("[^\\d]?"), this);
ui->leDelimiter->setValidator(validator);
+ ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS);
+
connect(ui->rbComma, &QAbstractButton::toggled,
this, &AsciiReaderSettings::delimiterToggled);
connect(ui->rbSpace, &QAbstractButton::toggled,
diff --git a/src/binarystreamreader.cpp b/src/binarystreamreader.cpp
--- a/src/binarystreamreader.cpp
+++ b/src/binarystreamreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -107,50 +107,58 @@ void BinaryStreamReader::onNumOfChannels
emit numOfChannelsChanged(value);
}
-void BinaryStreamReader::onDataReady()
+unsigned BinaryStreamReader::readData()
{
// a package is a set of channel data like {CHAN0_SAMPLE, CHAN1_SAMPLE...}
- int packageSize = sampleSize * _numChannels;
- int bytesAvailable = _device->bytesAvailable();
+ unsigned packageSize = sampleSize * _numChannels;
+ unsigned bytesAvailable = _device->bytesAvailable();
+ unsigned totalRead = 0;
// skip 1 byte if requested
if (skipByteRequested && bytesAvailable > 0)
{
_device->read(1);
+ totalRead++;
skipByteRequested = false;
bytesAvailable--;
}
// skip 1 sample (channel) if requested
- if (skipSampleRequested && bytesAvailable >= (int) sampleSize)
+ if (skipSampleRequested && bytesAvailable >= sampleSize)
{
_device->read(sampleSize);
+ totalRead += sampleSize;
skipSampleRequested = false;
bytesAvailable -= sampleSize;
}
- if (bytesAvailable < packageSize) return;
+ if (bytesAvailable < packageSize) return totalRead;
- int numOfPackagesToRead =
+ unsigned numOfPackagesToRead =
(bytesAvailable - (bytesAvailable % packageSize)) / packageSize;
+ unsigned numBytesToRead = numOfPackagesToRead * packageSize;
+
+ totalRead += numBytesToRead;
if (paused)
{
// read and discard data
- _device->read(numOfPackagesToRead*packageSize);
- return;
+ _device->read(numBytesToRead);
+ return totalRead;
}
// actual reading
SamplePack samples(numOfPackagesToRead, _numChannels);
- for (int i = 0; i < numOfPackagesToRead; i++)
+ for (unsigned i = 0; i < numOfPackagesToRead; i++)
{
- for (unsigned int ci = 0; ci < _numChannels; ci++)
+ for (unsigned ci = 0; ci < _numChannels; ci++)
{
samples.data(ci)[i] = (this->*readSample)();
}
}
feedOut(samples);
+
+ return totalRead;
}
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 © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -60,10 +60,11 @@ private:
*/
template double readSampleAs();
+ unsigned readData() override;
+
private slots:
void onNumberFormatChanged(NumberFormat numberFormat);
void onNumOfChannelsChanged(unsigned value);
- void onDataReady() override;
};
#endif // BINARYSTREAMREADER_H
diff --git a/src/binarystreamreadersettings.cpp b/src/binarystreamreadersettings.cpp
--- a/src/binarystreamreadersettings.cpp
+++ b/src/binarystreamreadersettings.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,6 +21,7 @@
#include "ui_binarystreamreadersettings.h"
#include "utils.h"
+#include "defines.h"
#include "setting_defines.h"
BinaryStreamReaderSettings::BinaryStreamReaderSettings(QWidget *parent) :
@@ -29,6 +30,8 @@ BinaryStreamReaderSettings::BinaryStream
{
ui->setupUi(this);
+ ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS);
+
// Note: if directly connected we get a runtime warning on incompatible signal arguments
connect(ui->spNumOfChannels, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged),
[this](int value)
diff --git a/src/bpslabel.cpp b/src/bpslabel.cpp
new file mode 100644
--- /dev/null
+++ b/src/bpslabel.cpp
@@ -0,0 +1,80 @@
+/*
+ Copyright © 2019 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 "bpslabel.h"
+
+const char* BPS_TOOLTIP = "bits per second";
+const char* BPS_TOOLTIP_ERR = "Maximum baud rate may be reached!";
+
+BPSLabel::BPSLabel(PortControl* portControl,
+ DataFormatPanel* dataFormatPanel,
+ QWidget *parent) :
+ QLabel(parent)
+{
+ _portControl = portControl;
+ _dataFormatPanel = dataFormatPanel;
+ prevBytesRead = 0;
+
+ setText("0bps");
+ setToolTip(tr(BPS_TOOLTIP));
+
+ connect(&bpsTimer, &QTimer::timeout,
+ this, &BPSLabel::onBpsTimeout);
+
+ connect(portControl, &PortControl::portToggled,
+ this, &BPSLabel::onPortToggled);
+}
+
+void BPSLabel::onBpsTimeout()
+{
+ uint64_t curBytesRead = _dataFormatPanel->bytesRead();
+ uint64_t bytesRead = curBytesRead - prevBytesRead;
+ prevBytesRead = curBytesRead;
+
+ unsigned bits = bytesRead * 8;
+ unsigned maxBps = _portControl->maxBitRate();
+ QString str;
+ if (bits >= maxBps)
+ {
+ // TODO: an icon for bps warning
+ str = QString(tr("!%1/%2bps")).arg(bits).arg(maxBps);
+ setToolTip(tr(BPS_TOOLTIP_ERR));
+ }
+ else
+ {
+ str = QString(tr("%1bps")).arg(bits);
+ setToolTip(tr(BPS_TOOLTIP));
+ }
+ setText(str);
+}
+
+void BPSLabel::onPortToggled(bool open)
+{
+ if (open)
+ {
+ bpsTimer.start(1000);
+ }
+ else
+ {
+ bpsTimer.stop();
+ // if not cleared last displayed value is stuck
+ setText("0bps");
+ setToolTip(tr(BPS_TOOLTIP));
+ }
+}
diff --git a/src/bpslabel.h b/src/bpslabel.h
new file mode 100644
--- /dev/null
+++ b/src/bpslabel.h
@@ -0,0 +1,55 @@
+/*
+ Copyright © 2019 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 BPSLABEL_H
+#define BPSLABEL_H
+
+#include
+#include
+
+#include "portcontrol.h"
+#include "dataformatpanel.h"
+
+/**
+ * Displays bits per second read from device.
+ *
+ * Displays a warning if maximum bit rate is reached.
+ */
+class BPSLabel : public QLabel
+{
+ Q_OBJECT
+
+public:
+ explicit BPSLabel(PortControl* portControl,
+ DataFormatPanel* dataFormatPanel,
+ QWidget *parent = 0);
+
+private:
+ PortControl* _portControl;
+ DataFormatPanel* _dataFormatPanel;
+ QTimer bpsTimer;
+
+ uint64_t prevBytesRead;
+
+private slots:
+ void onBpsTimeout();
+ void onPortToggled(bool open);
+};
+
+#endif // BPSLABEL_H
diff --git a/src/dataformatpanel.cpp b/src/dataformatpanel.cpp
--- a/src/dataformatpanel.cpp
+++ b/src/dataformatpanel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -39,6 +39,7 @@ DataFormatPanel::DataFormatPanel(QSerial
serialPort = port;
paused = false;
readerBeforeDemo = nullptr;
+ _bytesRead = 0;
// initalize default reader
currentReader = &bsReader;
@@ -130,6 +131,12 @@ void DataFormatPanel::selectReader(Abstr
emit sourceChanged(currentReader);
}
+uint64_t DataFormatPanel::bytesRead()
+{
+ _bytesRead += currentReader->getBytesRead();
+ return _bytesRead;
+}
+
void DataFormatPanel::saveSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_DataFormat);
diff --git a/src/dataformatpanel.h b/src/dataformatpanel.h
--- a/src/dataformatpanel.h
+++ b/src/dataformatpanel.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -20,9 +20,9 @@
#ifndef DATAFORMATPANEL_H
#define DATAFORMATPANEL_H
+#include
#include
#include
-#include
#include
#include
#include
@@ -50,6 +50,8 @@ public:
unsigned numChannels() const;
/// Returns active source (reader)
Source* activeSource();
+ /// Returns total number of bytes read
+ uint64_t bytesRead();
/// Stores data format panel settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads data format panel settings from a `QSettings`.
@@ -77,6 +79,7 @@ private:
void selectReader(AbstractReader* reader);
bool paused;
+ uint64_t _bytesRead;
DemoReader demoReader;
AbstractReader* readerBeforeDemo;
diff --git a/src/datatextview.cpp b/src/datatextview.cpp
new file mode 100644
--- /dev/null
+++ b/src/datatextview.cpp
@@ -0,0 +1,110 @@
+/*
+ Copyright © 2019 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 "datatextview.h"
+#include "ui_datatextview.h"
+
+#include "setting_defines.h"
+#include "utils.h"
+
+class DataTextViewSink : public Sink
+{
+public:
+ DataTextViewSink(DataTextView* textView)
+ {
+ _textView = textView;
+ }
+
+protected:
+ virtual void feedIn(const SamplePack& data) override
+ {
+ _textView->addData(data);
+ };
+
+private:
+ DataTextView* _textView;
+};
+
+DataTextView::DataTextView(Stream* stream, QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::DataTextView)
+{
+ _stream = stream;
+ ui->setupUi(this);
+ sink = new DataTextViewSink(this);
+
+ connect(ui->cbEnable, &QCheckBox::toggled, [this](bool checked)
+ {
+ if (checked)
+ {
+ _stream->connectFollower(sink);
+ }
+ else
+ {
+ _stream->disconnectFollower(sink);
+ }
+ });
+
+ ui->textView->setMaximumBlockCount(ui->spNumLines->value());
+ connect(ui->spNumLines, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged),
+ [this](int value)
+ {
+ ui->textView->setMaximumBlockCount(value);
+ });
+
+ connect(ui->pbClear, &QPushButton::clicked, ui->textView, &QPlainTextEdit::clear);
+}
+
+DataTextView::~DataTextView()
+{
+ delete sink;
+ delete ui;
+}
+
+void DataTextView::addData(const SamplePack& data)
+{
+ for (unsigned int i = 0; i < data.numSamples(); i++)
+ {
+ QString str;
+ for (unsigned ci = 0; ci < data.numChannels(); ci++)
+ {
+ str += QString::number(data.data(ci)[i], 'f', ui->spDecimals->value());
+ if (ci != data.numChannels()-1) str += " ";
+ }
+ ui->textView->appendPlainText(str);
+ }
+}
+
+void DataTextView::saveSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Plot);
+ settings->setValue(SG_TextView_NumLines, ui->spNumLines->value());
+ settings->setValue(SG_TextView_Decimals, ui->spDecimals->value());
+ settings->endGroup();
+}
+
+void DataTextView::loadSettings(QSettings* settings)
+{
+ settings->beginGroup(SettingGroup_Plot);
+ ui->spNumLines->setValue(
+ settings->value(SG_TextView_NumLines, ui->spNumLines->value()).toInt());
+ ui->spDecimals->setValue(
+ settings->value(SG_TextView_Decimals, ui->spDecimals->value()).toInt());
+ settings->endGroup();
+}
diff --git a/src/datatextview.h b/src/datatextview.h
new file mode 100644
--- /dev/null
+++ b/src/datatextview.h
@@ -0,0 +1,57 @@
+/*
+ Copyright © 2019 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 DATATEXTVIEW_H
+#define DATATEXTVIEW_H
+
+#include
+
+#include "stream.h"
+
+namespace Ui {
+class DataTextView;
+}
+
+class DataTextViewSink;
+
+class DataTextView : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit DataTextView(Stream* stream, QWidget *parent = 0);
+ ~DataTextView();
+
+ /// Stores settings into a `QSettings`
+ void saveSettings(QSettings* settings);
+ /// Loads settings from a `QSettings`.
+ void loadSettings(QSettings* settings);
+
+protected:
+ void addData(const SamplePack& data);
+
+ friend DataTextViewSink;
+
+private:
+ Ui::DataTextView *ui;
+ DataTextViewSink* sink;
+ Stream* _stream;
+};
+
+#endif // DATATEXTVIEW_H
diff --git a/src/datatextview.ui b/src/datatextview.ui
new file mode 100644
--- /dev/null
+++ b/src/datatextview.ui
@@ -0,0 +1,99 @@
+
+
+ DataTextView
+
+
+
+ 0
+ 0
+ 451
+ 212
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
+ Enable display of plotted data as text.
+
+
+ Enable
+
+
+
+ -
+
+
+ Num. Lines:
+
+
+
+ -
+
+
+ 1
+
+
+ 10000
+
+
+ 1000
+
+
+
+ -
+
+
+ Decimals:
+
+
+
+ -
+
+
+ 9
+
+
+ 6
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+ -
+
+
+ Clear
+
+
+
+
+
+ -
+
+
+ true
+
+
+
+
+
+
+
+
diff --git a/src/defines.h b/src/defines.h
--- a/src/defines.h
+++ b/src/defines.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -17,4 +17,12 @@
along with serialplot. If not, see .
*/
-const char* BUG_REPORT_URL = "https://bitbucket.org/hyOzd/serialplot/issues/new";
+#ifndef DEFINES_H
+#define DEFINES_H
+
+const char BUG_REPORT_URL[] = "https://bitbucket.org/hyOzd/serialplot/issues/new";
+
+/// Maximum number of channels that can be set by user
+const unsigned MAX_NUM_CHANNELS = 64;
+
+#endif // DEFINES_H
diff --git a/src/demoreader.cpp b/src/demoreader.cpp
--- a/src/demoreader.cpp
+++ b/src/demoreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -53,8 +53,9 @@ void DemoReader::enable(bool enabled)
else
{
timer.stop();
- disconnectSinks();
}
+
+ AbstractReader::enable(enabled);
}
unsigned DemoReader::numChannels() const
@@ -91,7 +92,8 @@ void DemoReader::onNumChannelsChanged(un
updateNumChannels();
}
-void DemoReader::onDataReady()
+unsigned DemoReader::readData()
{
// intentionally empty, required by AbstractReader
+ return 0;
}
diff --git a/src/demoreader.h b/src/demoreader.h
--- a/src/demoreader.h
+++ b/src/demoreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -55,10 +55,11 @@ private:
QTimer timer;
int count;
+ unsigned readData() override;
+
private slots:
void demoTimerTimeout();
void onNumChannelsChanged(unsigned value);
- void onDataReady() override;
};
#endif // DEMOREADER_H
diff --git a/src/demoreadersettings.cpp b/src/demoreadersettings.cpp
--- a/src/demoreadersettings.cpp
+++ b/src/demoreadersettings.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,6 +21,7 @@
#include "ui_demoreadersettings.h"
#include "utils.h"
+#include "defines.h"
DemoReaderSettings::DemoReaderSettings(QWidget *parent) :
QWidget(parent),
@@ -28,6 +29,8 @@ DemoReaderSettings::DemoReaderSettings(Q
{
ui->setupUi(this);
+ ui->spNumChannels->setMaximum(MAX_NUM_CHANNELS);
+
connect(ui->spNumChannels, SELECT::OVERLOAD_OF(&QSpinBox::valueChanged),
[this](int value)
{
diff --git a/src/floatswap.h b/src/floatswap.h
--- a/src/floatswap.h
+++ b/src/floatswap.h
@@ -1,5 +1,7 @@
#include
+#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
+
template <> inline float qbswap(float source)
{
float result;
@@ -11,3 +13,5 @@ template <> inline float qbswap(f
t[3] = s[0];
return result;
}
+
+#endif
diff --git a/src/framebuffer.h b/src/framebuffer.h
--- a/src/framebuffer.h
+++ b/src/framebuffer.h
@@ -61,4 +61,24 @@ class WFrameBuffer : public ResizableBuf
virtual void clear() = 0;
};
+/**
+ * Abstract base class for X buffers.
+ *
+ * These buffers only contain increasing or equal (to previous) values.
+ */
+class XFrameBuffer : public ResizableBuffer
+{
+public:
+ enum Index {OUT_OF_RANGE = -1};
+
+ /**
+ * Finds index for given value.
+ *
+ * If given value is bigger than max or smaller than minimum
+ * returns `OUT_OF_RANGE`. If it's in between values, smaller
+ * index is returned (not closer one).
+ */
+ virtual int findIndex(double value) const = 0;
+};
+
#endif // FRAMEBUFFER_H
diff --git a/src/framebufferseries.cpp b/src/framebufferseries.cpp
--- a/src/framebufferseries.cpp
+++ b/src/framebufferseries.cpp
@@ -20,75 +20,64 @@
#include
#include "framebufferseries.h"
-FrameBufferSeries::FrameBufferSeries(const FrameBuffer* buffer)
+FrameBufferSeries::FrameBufferSeries(const XFrameBuffer* x, const FrameBuffer* y)
{
- xAsIndex = true;
- _xmin = 0;
- _xmax = 1;
- _buffer = buffer;
+ _x = x;
+ _y = y;
+
int_index_start = 0;
- int_index_end = _buffer->size();
+ int_index_end = _y->size();
}
-void FrameBufferSeries::setXAxis(bool asIndex, double xmin, double xmax)
+void FrameBufferSeries::setX(const XFrameBuffer* x)
{
- xAsIndex = asIndex;
- _xmin = xmin;
- _xmax = xmax;
+ _x = x;
}
size_t FrameBufferSeries::size() const
{
- return int_index_end - int_index_start;
+ return int_index_end - int_index_start + 1;
}
QPointF FrameBufferSeries::sample(size_t i) const
{
i += int_index_start;
- if (xAsIndex)
- {
- return QPointF(i, _buffer->sample(i));
- }
- else
- {
- return QPointF(i * (_xmax - _xmin) / _buffer->size() + _xmin, _buffer->sample(i));
- }
+ return QPointF(_x->sample(i), _y->sample(i));
}
QRectF FrameBufferSeries::boundingRect() const
{
QRectF rect;
- auto yLim = _buffer->limits();
+ auto yLim = _y->limits();
+ auto xLim = _x->limits();
rect.setBottom(yLim.start);
rect.setTop(yLim.end);
- if (xAsIndex)
- {
- rect.setLeft(0);
- rect.setRight(size());
- }
- else
- {
- rect.setLeft(_xmin);
- rect.setRight(_xmax);
- }
+ rect.setLeft(xLim.start);
+ rect.setRight(xLim.end);
+
return rect.normalized();
}
void FrameBufferSeries::setRectOfInterest(const QRectF& rect)
{
- if (xAsIndex)
+ int_index_start = _x->findIndex(rect.left());
+ int_index_end = _x->findIndex(rect.right());
+
+ if (int_index_start == XFrameBuffer::OUT_OF_RANGE)
{
- int_index_start = floor(rect.left())-1;
- int_index_end = ceil(rect.right())+1;
+ int_index_start = 0;
}
- else
+ else if (int_index_start > 0)
{
- double xsize = _xmax - _xmin;
- size_t bsize = _buffer->size();
- int_index_start = floor(bsize * (rect.left()-_xmin) / xsize)-1;
- int_index_end = ceil(bsize * (rect.right()-_xmin) / xsize)+1;
+ int_index_start -= 1;
}
- int_index_start = std::max(int_index_start, 0);
- int_index_end = std::min((int) _buffer->size(), int_index_end);
+ if (int_index_end == XFrameBuffer::OUT_OF_RANGE)
+ {
+ int_index_end = _x->size()-1;
+ }
+ else if (int_index_end < (int)_x->size()-1)
+ {
+ int_index_end += 1;
+ }
}
diff --git a/src/framebufferseries.h b/src/framebufferseries.h
--- a/src/framebufferseries.h
+++ b/src/framebufferseries.h
@@ -35,10 +35,9 @@
class FrameBufferSeries : public QwtSeriesData
{
public:
- FrameBufferSeries(const FrameBuffer* buffer);
+ FrameBufferSeries(const XFrameBuffer* x, const FrameBuffer* y);
- /// Behavior of X axis
- void setXAxis(bool asIndex, double xmin, double xmax);
+ void setX(const XFrameBuffer* x);
// QwtSeriesData implementations
size_t size() const;
@@ -47,10 +46,8 @@ public:
void setRectOfInterest(const QRectF& rect);
private:
- const FrameBuffer* _buffer;
- bool xAsIndex;
- double _xmin;
- double _xmax;
+ const XFrameBuffer* _x;
+ const FrameBuffer* _y;
int int_index_start; ///< starting index of "rectangle of interest"
int int_index_end; ///< ending index of "rectangle of interest"
diff --git a/src/framedreader.cpp b/src/framedreader.cpp
--- a/src/framedreader.cpp
+++ b/src/framedreader.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2020 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -159,6 +159,7 @@ void FramedReader::onNumOfChannelsChange
_numChannels = value;
checkSettings();
reset();
+ updateNumChannels();
emit numOfChannelsChanged(value);
}
@@ -184,9 +185,11 @@ void FramedReader::onFrameSizeChanged(un
reset();
}
-void FramedReader::onDataReady()
+unsigned FramedReader::readData()
{
- if (settingsInvalid) return;
+ unsigned numBytesRead = 0;
+
+ if (settingsInvalid) return numBytesRead;
// loop until we run out of bytes or more bytes is required
unsigned bytesAvailable;
@@ -196,6 +199,7 @@ void FramedReader::onDataReady()
{
char c;
_device->getChar(&c);
+ numBytesRead++;
if (c == syncWord[sync_i]) // correct sync byte?
{
sync_i++;
@@ -213,6 +217,7 @@ void FramedReader::onDataReady()
{
frameSize = 0;
_device->getChar((char*) &frameSize);
+ numBytesRead++;
if (frameSize == 0) // check size
{
@@ -242,10 +247,13 @@ void FramedReader::onDataReady()
else // read data bytes and checksum
{
readFrameDataAndCheck();
+ numBytesRead += checksumEnabled ? frameSize+1 : frameSize;
reset();
}
}
}
+
+ return numBytesRead;
}
void FramedReader::reset()
@@ -263,7 +271,7 @@ void FramedReader::readFrameDataAndCheck
// if paused just read and waste data
if (paused)
{
- _device->read((checksumEnabled ? frameSize+1 : frameSize));
+ _device->read(checksumEnabled ? frameSize+1 : frameSize);
return;
}
diff --git a/src/framedreader.h b/src/framedreader.h
--- a/src/framedreader.h
+++ b/src/framedreader.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -79,8 +79,9 @@ private:
/// @note should be called only if there are enough bytes on device
void readFrameDataAndCheck();
+ unsigned readData() override;
+
private slots:
- void onDataReady() override;
void onNumberFormatChanged(NumberFormat numberFormat);
void onNumOfChannelsChanged(unsigned value);
diff --git a/src/framedreadersettings.cpp b/src/framedreadersettings.cpp
--- a/src/framedreadersettings.cpp
+++ b/src/framedreadersettings.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2016 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -20,6 +20,7 @@
#include
#include "utils.h"
+#include "defines.h"
#include "setting_defines.h"
#include "framedreadersettings.h"
#include "ui_framedreadersettings.h"
@@ -32,6 +33,7 @@ FramedReaderSettings::FramedReaderSettin
ui->leSyncWord->setMode(false); // hex mode
ui->leSyncWord->setText("AA BB");
+ ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS);
connect(ui->cbChecksum, &QCheckBox::toggled,
[this](bool enabled)
diff --git a/src/indexbuffer.cpp b/src/indexbuffer.cpp
--- a/src/indexbuffer.cpp
+++ b/src/indexbuffer.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -47,3 +47,15 @@ Range IndexBuffer::limits() const
{
return Range{0, _size-1.};
}
+
+int IndexBuffer::findIndex(double value) const
+{
+ if (value < 0 || value > size() - 1)
+ {
+ return OUT_OF_RANGE;
+ }
+ else
+ {
+ return value;
+ }
+}
diff --git a/src/indexbuffer.h b/src/indexbuffer.h
--- a/src/indexbuffer.h
+++ b/src/indexbuffer.h
@@ -26,15 +26,16 @@
/// sample value.
///
/// @note This buffer isn't for storing data.
-class IndexBuffer : public ResizableBuffer
+class IndexBuffer : public XFrameBuffer
{
public:
IndexBuffer(unsigned n);
- unsigned size() const;
- double sample(unsigned i) const;
- Range limits() const;
- void resize(unsigned n);
+ unsigned size() const override;
+ double sample(unsigned i) const override;
+ Range limits() const override;
+ void resize(unsigned n) override;
+ int findIndex(double value) const override;
private:
unsigned _size;
diff --git a/src/ledwidget.cpp b/src/ledwidget.cpp
new file mode 100644
--- /dev/null
+++ b/src/ledwidget.cpp
@@ -0,0 +1,129 @@
+/*
+ Copyright © 2018 Hasan Yavuz Özderya
+
+ This file is part of serialplot.
+
+ ledwidget 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.
+
+ ledwidget 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 ledwidget. If not, see .
+*/
+
+#include "ledwidget.h"
+#include
+#include
+#include
+
+LedWidget::LedWidget(QWidget* parent) : QWidget(parent),
+ m_color(107,223,51),
+ m_on(true)
+{
+ resize(20,20);
+}
+
+QSize LedWidget::sizeHint() const
+{
+ return QSize(20,20);
+}
+
+QSize LedWidget::minimumSizeHint() const
+{
+ return QSize(10, 10);
+}
+
+void LedWidget::setColor(QColor ledColor)
+{
+ if (m_color == ledColor) return;
+ m_color = ledColor;
+ update();
+ emit colorChanged(m_color);
+}
+
+bool LedWidget::isOn()
+{
+ return m_on;
+}
+
+void LedWidget::setOn(bool on)
+{
+ if (on == m_on) return;
+ m_on = on;
+ update();
+ emit onChanged(on);
+}
+
+void LedWidget::turnOn()
+{
+ setOn(true);
+}
+
+void LedWidget::turnOff()
+{
+ setOn(false);
+}
+
+void LedWidget::toggle()
+{
+ setOn(!m_on);
+}
+
+void LedWidget::paintEvent(QPaintEvent* event)
+{
+ Q_UNUSED(event)
+
+ const qreal r = std::min(width(), height()) / 2; // maximum radius including glow
+ const qreal glowOffset = std::max(2., r/5.);
+ const qreal borderOffset = std::max(1., r/10.);
+ const qreal shineOffset = std::max(1., r/20.);
+ const QPointF center(width()/2, height()/2);
+
+ const qreal gr = r;
+ const qreal br = gr - glowOffset; // border shape radius
+ const qreal ir = br - borderOffset; // inner fill radius
+ const qreal sr = ir - shineOffset;
+
+ QColor borderColor(130,130,130);
+ QColor shineColor(255, 255, 255, m_on ? 200 : 50);
+ QColor fillColor(m_color);
+
+ QPainter painter(this);
+ painter.setRenderHints(QPainter::Antialiasing);
+
+ // draw border
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(borderColor);
+ painter.drawEllipse(center, br, br);
+
+ // draw infill
+ if (!m_on) fillColor = fillColor.darker();
+ painter.setBrush(fillColor);
+ painter.drawEllipse(center, ir, ir);
+
+ // draw glow
+ if (m_on)
+ {
+ QColor glowColor(m_color);
+ glowColor.setAlphaF(0.5);
+ QRadialGradient glowGradient(center, gr, center);
+ glowGradient.setColorAt(0, glowColor);
+ glowGradient.setColorAt((r-glowOffset)/r, glowColor);
+ glowGradient.setColorAt(1, Qt::transparent);
+ painter.setBrush(glowGradient);
+ painter.drawEllipse(center, gr, gr);
+ }
+
+ // draw shine
+ QRadialGradient shineGradient(center, sr, center-QPoint(sr/2,sr/2));
+ shineGradient.setColorAt(0, shineColor);
+ shineGradient.setColorAt(1, Qt::transparent);
+ painter.setBrush(shineGradient);
+ painter.drawEllipse(center, sr, sr);
+}
diff --git a/src/ledwidget.h b/src/ledwidget.h
new file mode 100644
--- /dev/null
+++ b/src/ledwidget.h
@@ -0,0 +1,61 @@
+/*
+ Copyright © 2018 Hasan Yavuz Özderya
+
+ This file is part of serialplot.
+
+ ledwidget 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.
+
+ ledwidget 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 ledwidget. If not, see .
+*/
+
+#ifndef LEDWIDGET_H
+#define LEDWIDGET_H
+
+#include
+#include
+#include
+
+class LedWidget : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor color MEMBER m_color WRITE setColor NOTIFY colorChanged)
+ Q_PROPERTY(bool on READ isOn WRITE setOn NOTIFY onChanged)
+
+public:
+ explicit LedWidget(QWidget *parent = 0);
+
+ void setColor(QColor ledColor);
+ bool isOn();
+
+ QSize sizeHint() const Q_DECL_OVERRIDE;
+ QSize minimumSizeHint() const Q_DECL_OVERRIDE;
+
+signals:
+ void colorChanged(QColor ledColor);
+ void onChanged(bool on);
+
+public slots:
+ void setOn(bool on);
+ void turnOn();
+ void turnOff();
+ void toggle();
+
+protected:
+ void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
+
+private:
+ QColor m_color;
+ bool m_on;
+};
+
+#endif // LEDWIDGET_H
diff --git a/src/linindexbuffer.cpp b/src/linindexbuffer.cpp
--- a/src/linindexbuffer.cpp
+++ b/src/linindexbuffer.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -18,12 +18,14 @@
*/
#include
+#include
#include "linindexbuffer.h"
LinIndexBuffer::LinIndexBuffer(unsigned n, Range lim)
{
- Q_ASSERT(n > 0);
+ // Note that calculation of _step would cause divide by 0
+ Q_ASSERT(n > 1);
_size = n;
setLimits(lim);
@@ -50,6 +52,18 @@ void LinIndexBuffer::resize(unsigned n)
setLimits(_limits); // called to update `_step`
}
+int LinIndexBuffer::findIndex(double value) const
+{
+ if (value < _limits.start || value > _limits.end)
+ {
+ return OUT_OF_RANGE;
+ }
+
+ int r = (value - _limits.start) / _step;
+ // Note: we are limiting return value because of floating point in-accuracies
+ return std::min(std::max(r, 0), (_size-1));
+}
+
void LinIndexBuffer::setLimits(Range lim)
{
_limits = lim;
diff --git a/src/linindexbuffer.h b/src/linindexbuffer.h
--- a/src/linindexbuffer.h
+++ b/src/linindexbuffer.h
@@ -26,17 +26,19 @@
/// intermediate values are calculated linearly.
///
/// @note This buffer isn't for storing data.
-class LinIndexBuffer : public ResizableBuffer
+class LinIndexBuffer : public XFrameBuffer
{
public:
LinIndexBuffer(unsigned n, Range lim);
LinIndexBuffer(unsigned n, double min, double max) :
LinIndexBuffer(n, {min, max}) {};
- unsigned size() const;
- double sample(unsigned i) const;
- Range limits() const;
- void resize(unsigned n);
+ unsigned size() const override;
+ double sample(unsigned i) const override;
+ Range limits() const override;
+ void resize(unsigned n) override;
+ int findIndex(double value) const override;
+
/// Sets minimum and maximum sample values of the buffer.
void setLimits(Range lim);
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -19,28 +19,64 @@
#include
#include
+#include
#include "mainwindow.h"
#include "tooltipfilter.h"
#include "version.h"
-MainWindow* pMainWindow;
+MainWindow* pMainWindow = nullptr;
void messageHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg)
{
- // TODO: don't call MainWindow::messageHandler if window is destroyed
- pMainWindow->messageHandler(type, context, msg);
+ QString logString;
+
+ switch (type)
+ {
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
+ case QtInfoMsg:
+ logString = "[Info] " + msg;
+ break;
+#endif
+ case QtDebugMsg:
+ logString = "[Debug] " + msg;
+ break;
+ case QtWarningMsg:
+ logString = "[Warning] " + msg;
+ break;
+ case QtCriticalMsg:
+ logString = "[Error] " + msg;
+ break;
+ case QtFatalMsg:
+ logString = "[Fatal] " + msg;
+ break;
+ }
+
+ std::cerr << logString.toStdString() << std::endl;
+
+ if (pMainWindow != nullptr)
+ {
+ // TODO: don't call MainWindow::messageHandler if window is destroyed
+ pMainWindow->messageHandler(type, logString, msg);
+ }
+
+ if (type == QtFatalMsg)
+ {
+ __builtin_trap();
+ }
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
+ QApplication::setApplicationName(PROGRAM_NAME);
+ QApplication::setApplicationVersion(VERSION_STRING);
+
+ qInstallMessageHandler(messageHandler);
MainWindow w;
pMainWindow = &w;
- qInstallMessageHandler(messageHandler);
-
ToolTipFilter ttf;
a.installEventFilter(&ttf);
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -29,10 +29,13 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
#include
+#include
#include
#include
@@ -55,7 +58,8 @@ const QMap panelSettingMap
{2, "Plot"},
{3, "Commands"},
{4, "Record"},
- {5, "Log"}
+ {5, "TextView"},
+ {6, "Log"}
});
MainWindow::MainWindow(QWidget *parent) :
@@ -68,7 +72,9 @@ MainWindow::MainWindow(QWidget *parent)
commandPanel(&serialPort),
dataFormatPanel(&serialPort),
recordPanel(&stream),
- updateCheckDialog(this)
+ textView(&stream),
+ updateCheckDialog(this),
+ bpsLabel(&portControl, &dataFormatPanel, this)
{
ui->setupUi(this);
@@ -79,6 +85,7 @@ MainWindow::MainWindow(QWidget *parent)
ui->tabWidget->insertTab(2, &plotControlPanel, "Plot");
ui->tabWidget->insertTab(3, &commandPanel, "Commands");
ui->tabWidget->insertTab(4, &recordPanel, "Record");
+ ui->tabWidget->insertTab(5, &textView, "Text View");
ui->tabWidget->setCurrentIndex(0);
auto tbPortControl = portControl.toolBar();
addToolBar(tbPortControl);
@@ -169,6 +176,9 @@ MainWindow::MainWindow(QWidget *parent)
plotMan, &PlotManager::setYAxis);
connect(&plotControlPanel, &PlotControlPanel::xScaleChanged,
+ &stream, &Stream::setXAxis);
+
+ connect(&plotControlPanel, &PlotControlPanel::xScaleChanged,
plotMan, &PlotManager::setXAxis);
connect(&plotControlPanel, &PlotControlPanel::plotWidthChanged,
@@ -215,6 +225,9 @@ MainWindow::MainWindow(QWidget *parent)
plotControlPanel.setChannelInfoModel(stream.infoModel());
// init scales
+ stream.setXAxis(plotControlPanel.xAxisAsIndex(),
+ plotControlPanel.xMin(), plotControlPanel.xMax());
+
plotMan->setYAxis(plotControlPanel.autoScale(),
plotControlPanel.yMin(), plotControlPanel.yMax());
plotMan->setXAxis(plotControlPanel.xAxisAsIndex(),
@@ -222,13 +235,21 @@ MainWindow::MainWindow(QWidget *parent)
plotMan->setNumOfSamples(numOfSamples);
plotMan->setPlotWidth(plotControlPanel.plotWidth());
+ // init bps (bits per second) counter
+ ui->statusBar->addPermanentWidget(&bpsLabel);
+
// Init sps (sample per second) counter
spsLabel.setText("0sps");
- spsLabel.setToolTip("samples per second (per channel)");
+ spsLabel.setToolTip(tr("samples per second (per channel)"));
ui->statusBar->addPermanentWidget(&spsLabel);
connect(&sampleCounter, &SampleCounter::spsChanged,
this, &MainWindow::onSpsChanged);
+ bpsLabel.setMinimumWidth(70);
+ bpsLabel.setAlignment(Qt::AlignRight);
+ spsLabel.setMinimumWidth(70);
+ spsLabel.setAlignment(Qt::AlignRight);
+
// init demo
QObject::connect(ui->actionDemoMode, &QAction::toggled,
this, &MainWindow::enableDemo);
@@ -242,9 +263,11 @@ MainWindow::MainWindow(QWidget *parent)
onSourceChanged(dataFormatPanel.activeSource());
// load default settings
- QSettings settings("serialplot", "serialplot");
+ QSettings settings(PROGRAM_NAME, PROGRAM_NAME);
loadAllSettings(&settings);
+ handleCommandLineOptions(*QApplication::instance());
+
// ensure command panel has 1 command if none loaded
if (!commandPanel.numOfCommands())
{
@@ -291,7 +314,7 @@ void MainWindow::closeEvent(QCloseEvent
}
// save settings
- QSettings settings("serialplot", "serialplot");
+ QSettings settings(PROGRAM_NAME, PROGRAM_NAME);
saveAllSettings(&settings);
settings.sync();
@@ -346,6 +369,11 @@ void MainWindow::onPortToggled(bool open
// make sure demo mode is disabled
if (open && isDemoRunning()) enableDemo(false);
ui->actionDemoMode->setEnabled(!open);
+
+ if (!open)
+ {
+ spsLabel.setText("0sps");
+ }
}
void MainWindow::onSourceChanged(Source* source)
@@ -465,44 +493,16 @@ PlotViewSettings MainWindow::viewSetting
}
void MainWindow::messageHandler(QtMsgType type,
- const QMessageLogContext &context,
+ const QString &logString,
const QString &msg)
{
- QString logString;
-
- switch (type)
- {
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
- case QtInfoMsg:
- logString = "[Info] " + msg;
- break;
-#endif
- case QtDebugMsg:
- logString = "[Debug] " + msg;
- break;
- case QtWarningMsg:
- logString = "[Warning] " + msg;
- break;
- case QtCriticalMsg:
- logString = "[Error] " + msg;
- break;
- case QtFatalMsg:
- logString = "[Fatal] " + msg;
- break;
- }
-
- if (ui != NULL) ui->ptLog->appendPlainText(logString);
- std::cerr << logString.toStdString() << std::endl;
+ if (ui != NULL)
+ ui->ptLog->appendPlainText(logString);
if (type != QtDebugMsg && ui != NULL)
{
ui->statusBar->showMessage(msg, 5000);
}
-
- if (type == QtFatalMsg)
- {
- __builtin_trap();
- }
}
void MainWindow::saveAllSettings(QSettings* settings)
@@ -515,6 +515,7 @@ void MainWindow::saveAllSettings(QSettin
plotMenu.saveSettings(settings);
commandPanel.saveSettings(settings);
recordPanel.saveSettings(settings);
+ textView.saveSettings(settings);
updateCheckDialog.saveSettings(settings);
}
@@ -528,6 +529,7 @@ void MainWindow::loadAllSettings(QSettin
plotMenu.loadSettings(settings);
commandPanel.loadSettings(settings);
recordPanel.loadSettings(settings);
+ textView.loadSettings(settings);
updateCheckDialog.loadSettings(settings);
}
@@ -605,3 +607,56 @@ void MainWindow::onLoadSettings()
loadAllSettings(&settings);
}
}
+
+void MainWindow::handleCommandLineOptions(const QCoreApplication &app)
+{
+ QCommandLineParser parser;
+ parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsCompactedShortOptions);
+ parser.setApplicationDescription("Small and simple software for plotting data from serial port in realtime.");
+ parser.addHelpOption();
+ parser.addVersionOption();
+
+ QCommandLineOption configOpt({"c", "config"}, "Load configuration from file.", "filename");
+ QCommandLineOption portOpt({"p", "port"}, "Set port name.", "port name");
+ QCommandLineOption baudrateOpt({"b" ,"baudrate"}, "Set port baud rate.", "baud rate");
+ QCommandLineOption openPortOpt({"o", "open"}, "Open serial port.");
+
+ parser.addOption(configOpt);
+ parser.addOption(portOpt);
+ parser.addOption(baudrateOpt);
+ parser.addOption(openPortOpt);
+
+ parser.process(app);
+
+ if (parser.isSet(configOpt))
+ {
+ QString fileName = parser.value(configOpt);
+ QFileInfo fileInfo(fileName);
+
+ if (fileInfo.exists() && fileInfo.isFile())
+ {
+ QSettings settings(fileName, QSettings::IniFormat);
+ loadAllSettings(&settings);
+ }
+ else
+ {
+ qCritical() << "Configuration file not exist. Closing application.";
+ std::exit(1);
+ }
+ }
+
+ if (parser.isSet(portOpt))
+ {
+ portControl.selectPort(parser.value(portOpt));
+ }
+
+ if (parser.isSet(baudrateOpt))
+ {
+ portControl.selectBaudrate(parser.value(baudrateOpt));
+ }
+
+ if (parser.isSet(openPortOpt))
+ {
+ portControl.openPort();
+ }
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -46,6 +46,8 @@
#include "plotmenu.h"
#include "updatecheckdialog.h"
#include "samplecounter.h"
+#include "datatextview.h"
+#include "bpslabel.h"
namespace Ui {
class MainWindow;
@@ -61,8 +63,7 @@ public:
PlotViewSettings viewSettings() const;
- void messageHandler(QtMsgType type, const QMessageLogContext &context,
- const QString &msg);
+ void messageHandler(QtMsgType type, const QString &logString, const QString &msg);
private:
Ui::MainWindow *ui;
@@ -89,7 +90,11 @@ private:
RecordPanel recordPanel;
PlotControlPanel plotControlPanel;
PlotMenu plotMenu;
+ DataTextView textView;
UpdateCheckDialog updateCheckDialog;
+ BPSLabel bpsLabel;
+
+ void handleCommandLineOptions(const QCoreApplication &app);
/// Returns true if demo is running
bool isDemoRunning();
diff --git a/src/plot.cpp b/src/plot.cpp
--- a/src/plot.cpp
+++ b/src/plot.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -91,6 +91,11 @@ Plot::~Plot()
if (snapshotOverlay != NULL) delete snapshotOverlay;
}
+void Plot::setDispChannels(QVector channels)
+{
+ zoomer.setDispChannels(channels);
+}
+
void Plot::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax)
{
this->isAutoScaled = autoScaled;
diff --git a/src/plot.h b/src/plot.h
--- a/src/plot.h
+++ b/src/plot.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2018 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -50,6 +50,9 @@ public:
Plot(QWidget* parent = 0);
~Plot();
+ /// Set displayed channels for value tracking (can be null)
+ void setDispChannels(QVector channels);
+
public slots:
void showGrid(bool show = true);
void showMinorGrid(bool show = true);
diff --git a/src/plotcontrolpanel.cpp b/src/plotcontrolpanel.cpp
--- a/src/plotcontrolpanel.cpp
+++ b/src/plotcontrolpanel.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -21,10 +21,10 @@
#include
#include
#include
+#include
#include
-#include "color_selector.hpp"
#include "plotcontrolpanel.h"
#include "ui_plotcontrolpanel.h"
#include "setting_defines.h"
@@ -162,9 +162,9 @@ PlotControlPanel::PlotControlPanel(QWidg
this, SLOT(onRangeSelected()));
// color selector starts disabled until a channel is selected
- ui->colorSelector->setColor(QColor(0,0,0,0));
- ui->colorSelector->setDisplayMode(color_widgets::ColorPreview::AllAlpha);
- ui->colorSelector->setDisabled(true);
+ ui->pbColorSel->setDisabled(true);
+ setSelectorColor(QColor(0,0,0,0));
+ connect(ui->pbColorSel, &QPushButton::clicked, this, &PlotControlPanel::onColorSelect);
// reset buttons
resetAct.setToolTip(tr("Reset channel names and colors"));
@@ -241,6 +241,30 @@ bool PlotControlPanel::askNSConfirmation
return mb.exec() == QMessageBox::Apply;
}
+void PlotControlPanel::setSelectorColor(QColor color)
+{
+ ui->pbColorSel->setStyleSheet(QString("background-color: %1;").arg(color.name()));
+}
+
+void PlotControlPanel::onColorSelect()
+{
+ auto selection = ui->tvChannelInfo->selectionModel()->currentIndex();
+ // no selection
+ if (!selection.isValid()) return;
+
+ // current color
+ auto model = ui->tvChannelInfo->model();
+ QColor color = model->data(selection, Qt::ForegroundRole).value();
+
+ // show dialog
+ color = QColorDialog::getColor(color, this);
+
+ if (color.isValid()) // color is set to invalid if user cancels
+ {
+ ui->tvChannelInfo->model()->setData(selection, color, Qt::ForegroundRole);
+ }
+}
+
void PlotControlPanel::onAutoScaleChecked(bool checked)
{
if (checked)
@@ -373,19 +397,16 @@ void PlotControlPanel::setChannelInfoMod
if (current.isValid())
{
- ui->colorSelector->setEnabled(true);
+ ui->pbColorSel->setEnabled(true);
auto model = ui->tvChannelInfo->model();
color = model->data(current, Qt::ForegroundRole).value();
}
else
{
- ui->colorSelector->setDisabled(true);
+ ui->pbColorSel->setDisabled(true);
}
- // temporarily block signals because `setColor` emits `colorChanged`
- bool wasBlocked = ui->colorSelector->blockSignals(true);
- ui->colorSelector->setColor(color);
- ui->colorSelector->blockSignals(wasBlocked);
+ setSelectorColor(color);
});
connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::selectionChanged,
@@ -393,22 +414,11 @@ void PlotControlPanel::setChannelInfoMod
{
if (!selected.length())
{
- ui->colorSelector->setDisabled(true);
-
- // temporarily block signals because `setColor` emits `colorChanged`
- bool wasBlocked = ui->colorSelector->blockSignals(true);
- ui->colorSelector->setColor(QColor(0,0,0,0));
- ui->colorSelector->blockSignals(wasBlocked);
+ ui->pbColorSel->setDisabled(true);
+ setSelectorColor(QColor(0,0,0,0));
}
});
- connect(ui->colorSelector, &color_widgets::ColorSelector::colorChanged,
- [this](QColor color)
- {
- auto index = ui->tvChannelInfo->selectionModel()->currentIndex();
- ui->tvChannelInfo->model()->setData(index, color, Qt::ForegroundRole);
- });
-
connect(model, &QAbstractItemModel::dataChanged,
[this](const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ())
{
@@ -419,11 +429,7 @@ void PlotControlPanel::setChannelInfoMod
auto mod = ui->tvChannelInfo->model();
QColor color = mod->data(current, Qt::ForegroundRole).value();
-
- // temporarily block signals because `setColor` emits `colorChanged`
- bool wasBlocked = ui->colorSelector->blockSignals(true);
- ui->colorSelector->setColor(color);
- ui->colorSelector->blockSignals(wasBlocked);
+ setSelectorColor(color);
});
// reset actions
diff --git a/src/plotcontrolpanel.h b/src/plotcontrolpanel.h
--- a/src/plotcontrolpanel.h
+++ b/src/plotcontrolpanel.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -79,6 +79,9 @@ private:
/// Show a confirmation dialog before setting #samples to a big value
bool askNSConfirmation(int value);
+ /// Set the color displayed by color selector button
+ void setSelectorColor(QColor color);
+
private slots:
void onNumOfSamples(int value);
void onAutoScaleChecked(bool checked);
@@ -87,6 +90,7 @@ private slots:
void onIndexChecked(bool checked);
void onXScaleChanged();
void onPlotWidthChanged();
+ void onColorSelect();
};
#endif // PLOTCONTROLPANEL_H
diff --git a/src/plotcontrolpanel.ui b/src/plotcontrolpanel.ui
--- a/src/plotcontrolpanel.ui
+++ b/src/plotcontrolpanel.ui
@@ -7,7 +7,7 @@
0
0
704
- 195
+ 220
@@ -69,9 +69,9 @@
QLayout::SetMaximumSize
-
-
+
-
+
0
0
@@ -88,6 +88,15 @@
20
+
+ background-color: rgb(70, 141, 255);
+
+
+
+
+
+ false
+
-
@@ -411,14 +420,6 @@
-
-
- color_widgets::ColorSelector
- QWidget
-
- 1
-
-
diff --git a/src/plotmanager.cpp b/src/plotmanager.cpp
--- a/src/plotmanager.cpp
+++ b/src/plotmanager.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -19,7 +19,6 @@
#include
#include
-#include
#include "qwt_symbol.h"
#include "plot.h"
@@ -31,9 +30,9 @@ PlotManager::PlotManager(QWidget* plotAr
const Stream* stream, QObject* parent) :
QObject(parent)
{
+ _stream = stream;
construct(plotArea, menu);
- _stream = stream;
- if (_stream == NULL) return;
+ if (_stream == nullptr) return;
// connect to ChannelInfoModel
infoModel = _stream->infoModel();
@@ -53,15 +52,15 @@ PlotManager::PlotManager(QWidget* plotAr
// add initial curves if any?
for (unsigned int i = 0; i < stream->numChannels(); i++)
{
- addCurve(stream->channel(i)->name(), stream->channel(i)->yData());
+ addCurve(stream->channel(i)->name(), stream->channel(i)->xData(), stream->channel(i)->yData());
}
-
}
PlotManager::PlotManager(QWidget* plotArea, PlotMenu* menu,
Snapshot* snapshot, QObject *parent) :
QObject(parent)
{
+ _stream = nullptr;
construct(plotArea, menu);
setNumOfSamples(snapshot->numSamples());
@@ -70,11 +69,14 @@ PlotManager::PlotManager(QWidget* plotAr
for (unsigned ci = 0; ci < snapshot->numChannels(); ci++)
{
- addCurve(snapshot->channelName(ci), snapshot->yData[ci]);
+ addCurve(snapshot->channelName(ci), snapshot->xData[ci], snapshot->yData[ci]);
}
connect(infoModel, &QAbstractItemModel::dataChanged,
this, &PlotManager::onChannelInfoChanged);
+
+ // TODO: remove when snapshot view supports multi display
+ checkNoVisChannels();
}
void PlotManager::construct(QWidget* plotArea, PlotMenu* menu)
@@ -94,8 +96,6 @@ void PlotManager::construct(QWidget* plo
// initalize layout and single widget
isMulti = false;
scrollArea = NULL;
- setupLayout(isMulti);
- addPlotWidget();
// connect to menu
connect(menu, &PlotMenu::symbolShowChanged, this, &PlotManager:: setSymbols);
@@ -148,7 +148,7 @@ void PlotManager::onNumChannelsChanged(u
// add new channels
for (unsigned int i = oldNum; i < numOfChannels; i++)
{
- addCurve(_stream->channel(i)->name(), _stream->channel(i)->yData());
+ addCurve(_stream->channel(i)->name(), _stream->channel(i)->xData(), _stream->channel(i)->yData());
}
}
else if(numOfChannels < oldNum)
@@ -217,8 +217,6 @@ void PlotManager::checkNoVisChannels()
void PlotManager::setMulti(bool enabled)
{
- if (enabled == isMulti) return;
-
isMulti = enabled;
// detach all curves
@@ -239,11 +237,14 @@ void PlotManager::setMulti(bool enabled)
if (isMulti)
{
// add new widgets and attach
+ int i = 0;
for (auto curve : curves)
{
auto plot = addPlotWidget();
plot->setVisible(curve->isVisible());
+ plot->setDispChannels(QVector(1, _stream->channel(i)));
curve->attach(plot);
+ i++;
}
}
else
@@ -251,6 +252,11 @@ void PlotManager::setMulti(bool enabled)
// add a single widget
auto plot = addPlotWidget();
+ if (_stream != nullptr)
+ {
+ plot->setDispChannels(_stream->allChannels());
+ }
+
// attach all curves
for (auto curve : curves)
{
@@ -332,11 +338,10 @@ Plot* PlotManager::addPlotWidget()
return plot;
}
-void PlotManager::addCurve(QString title, const FrameBuffer* buffer)
+void PlotManager::addCurve(QString title, const XFrameBuffer* xBuf, const FrameBuffer* yBuf)
{
auto curve = new QwtPlotCurve(title);
- auto series = new FrameBufferSeries(buffer);
- series->setXAxis(_xAxisAsIndex, _xMin, _xMax);
+ auto series = new FrameBufferSeries(xBuf, yBuf);
curve->setSamples(series);
_addCurve(curve);
}
@@ -362,6 +367,20 @@ void PlotManager::_addCurve(QwtPlotCurve
plot = plotWidgets[0];
}
+ if (_stream != nullptr) // not displaying snapshot
+ {
+ QVector dispChannels;
+ if (isMulti)
+ {
+ dispChannels = QVector(1, _stream->channel(index));
+ }
+ else
+ {
+ dispChannels = _stream->allChannels();
+ }
+ plot->setDispChannels(dispChannels);
+ }
+
// show the curve
curve->attach(plot);
plot->replot();
@@ -369,6 +388,16 @@ void PlotManager::_addCurve(QwtPlotCurve
void PlotManager::removeCurves(unsigned number)
{
+ if (_stream != nullptr) // not displaying snapshot
+ {
+ if (! isMulti)
+ {
+ QVector dispChannels;
+ dispChannels = _stream->allChannels();
+ plotWidgets[0]->setDispChannels(dispChannels);
+ }
+ }
+
for (unsigned i = 0; i < number; i++)
{
if (!curves.isEmpty())
@@ -481,10 +510,13 @@ void PlotManager::setXAxis(bool asIndex,
_xAxisAsIndex = asIndex;
_xMin = xMin;
_xMax = xMax;
+
+ int ci = 0;
for (auto curve : curves)
{
FrameBufferSeries* series = static_cast(curve->data());
- series->setXAxis(asIndex, xMin, xMax);
+ series->setX(_stream->channel(ci)->xData());
+ ci++;
}
for (auto plot : plotWidgets)
{
diff --git a/src/plotmanager.h b/src/plotmanager.h
--- a/src/plotmanager.h
+++ b/src/plotmanager.h
@@ -41,7 +41,7 @@ class PlotManager : public QObject
public:
explicit PlotManager(QWidget* plotArea, PlotMenu* menu,
- const Stream* stream = NULL,
+ const Stream* stream = nullptr,
QObject *parent = 0);
explicit PlotManager(QWidget* plotArea, PlotMenu* menu,
Snapshot* snapshot,
@@ -49,7 +49,7 @@ public:
~PlotManager();
/// Add a new curve with title and buffer. A color is
/// automatically chosen for curve.
- void addCurve(QString title, const FrameBuffer* buffer);
+ void addCurve(QString title, const XFrameBuffer* xBuf, const FrameBuffer* yBuf);
/// Removes curves from the end
void removeCurves(unsigned number);
/// Returns current number of curves known by plot manager
@@ -82,7 +82,7 @@ private:
QList curves;
QList plotWidgets;
Plot* emptyPlot; ///< for displaying when all channels are hidden
- const Stream* _stream; ///< attached stream, can be `NULL`
+ const Stream* _stream; ///< attached stream, can be `nullptr`
const ChannelInfoModel* infoModel;
bool isDemoShown;
bool _autoScaled;
diff --git a/src/plotmenu.cpp b/src/plotmenu.cpp
--- a/src/plotmenu.cpp
+++ b/src/plotmenu.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -66,7 +66,7 @@ PlotMenu::PlotMenu(QWidget* parent) :
setSymbolsMenu.addAction(&setSymbolsAutoAct);
setSymbolsAutoAct.setCheckable(true);
setSymbolsAutoAct.setChecked(true);
- connect(&setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ connect(&setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::toggled),
[this](bool checked)
{
if (checked) emit symbolShowChanged(Plot::ShowSymbolsAuto);
@@ -74,18 +74,18 @@ PlotMenu::PlotMenu(QWidget* parent) :
setSymbolsMenu.addAction(&setSymbolsShowAct);
setSymbolsShowAct.setCheckable(true);
- connect(&setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ connect(&setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::toggled),
[this](bool checked)
{
- if (checked) symbolShowChanged(Plot::ShowSymbolsShow);
+ if (checked) emit symbolShowChanged(Plot::ShowSymbolsShow);
});
setSymbolsMenu.addAction(&setSymbolsHideAct);
setSymbolsHideAct.setCheckable(true);
- connect(&setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::triggered),
+ connect(&setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::toggled),
[this](bool checked)
{
- if (checked) symbolShowChanged(Plot::ShowSymbolsHide);
+ if (checked) emit symbolShowChanged(Plot::ShowSymbolsHide);
});
// add symbol actions to same group so that they appear as radio buttons
@@ -202,20 +202,17 @@ void PlotMenu::loadSettings(QSettings* s
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
+ else if (!showSymbolsStr.isEmpty())
{
qCritical() << "Invalid symbol setting:" << showSymbolsStr;
}
diff --git a/src/portcontrol.cpp b/src/portcontrol.cpp
--- a/src/portcontrol.cpp
+++ b/src/portcontrol.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -79,10 +79,10 @@ PortControl::PortControl(QSerialPort* po
this, &PortControl::onTbPortListActivated);
QObject::connect(ui->cbPortList,
SELECT::OVERLOAD_OF(&QComboBox::activated),
- this, &PortControl::selectPort);
+ this, &PortControl::selectListedPort);
QObject::connect(&tbPortList,
SELECT::OVERLOAD_OF(&QComboBox::activated),
- this, &PortControl::selectPort);
+ this, &PortControl::selectListedPort);
// setup buttons
ui->pbOpenPort->setDefaultAction(&openAction);
@@ -91,7 +91,7 @@ PortControl::PortControl(QSerialPort* po
// setup baud rate selection widget
QObject::connect(ui->cbBaudRate,
SELECT::OVERLOAD_OF(&QComboBox::activated),
- this, &PortControl::selectBaudRate);
+ this, &PortControl::_selectBaudRate);
// setup parity selection buttons
parityButtons.addButton(ui->rbNoParity, (int) QSerialPort::NoParity);
@@ -198,7 +198,7 @@ void PortControl::loadBaudRateList()
}
}
-void PortControl::selectBaudRate(QString baudRate)
+void PortControl::_selectBaudRate(QString baudRate)
{
if (serialPort->isOpen())
{
@@ -289,7 +289,7 @@ void PortControl::togglePort()
if (serialPort->open(QIODevice::ReadWrite))
{
// set port settings
- selectBaudRate(ui->cbBaudRate->currentText());
+ _selectBaudRate(ui->cbBaudRate->currentText());
selectParity((QSerialPort::Parity) parityButtons.checkedId());
selectDataBits((QSerialPort::DataBits) dataBitsButtons.checkedId());
selectStopBits((QSerialPort::StopBits) stopBitsButtons.checkedId());
@@ -310,10 +310,17 @@ void PortControl::togglePort()
openAction.setChecked(serialPort->isOpen());
}
-void PortControl::selectPort(QString portName)
+void PortControl::selectListedPort(QString portName)
{
// portName may be coming from combobox
portName = portName.split(" ")[0];
+
+ QSerialPortInfo portInfo(portName);
+ if (portInfo.isNull())
+ {
+ qWarning() << "Device doesn't exists:" << portName;
+ }
+
// has selection actually changed
if (portName != serialPort->portName())
{
@@ -366,6 +373,13 @@ void PortControl::onTbPortListActivated(
void PortControl::onPortError(QSerialPort::SerialPortError error)
{
+#ifdef Q_OS_UNIX
+ // For suppressing "Invalid argument" errors that happens with pseudo terminals
+ auto isPtsInvalidArgErr = [this] () -> bool {
+ return serialPort->portName().contains("pts/") && serialPort->errorString().contains("Invalid argument");
+ };
+#endif
+
switch(error)
{
case QSerialPort::NoError :
@@ -408,12 +422,22 @@ required privileges or device is already
qCritical() << "An error occurred while reading data.";
break;
case QSerialPort::UnsupportedOperationError:
+#ifdef Q_OS_UNIX
+ // Qt 5.5 gives "Invalid argument" with 'UnsupportedOperationError'
+ if (isPtsInvalidArgErr())
+ break;
+#endif
qCritical() << "Operation is not supported.";
break;
case QSerialPort::TimeoutError:
qCritical() << "A timeout error occurred.";
break;
case QSerialPort::UnknownError:
+#ifdef Q_OS_UNIX
+ // Qt 5.2 gives "Invalid argument" with 'UnknownError'
+ if (isPtsInvalidArgErr())
+ break;
+#endif
qCritical() << "Unknown error! Error: " << serialPort->errorString();
break;
default:
@@ -453,6 +477,64 @@ QString PortControl::currentFlowControlT
}
}
+void PortControl::selectPort(QString portName)
+{
+ int portIndex = portList.indexOfName(portName);
+ if (portIndex < 0) // not in list, add to model and update the selections
+ {
+ portList.appendRow(new PortListItem(portName));
+ portIndex = portList.rowCount()-1;
+ }
+
+ ui->cbPortList->setCurrentIndex(portIndex);
+ tbPortList.setCurrentIndex(portIndex);
+
+ selectListedPort(portName);
+}
+
+void PortControl::selectBaudrate(QString baudRate)
+{
+ int baudRateIndex = ui->cbBaudRate->findText(baudRate);
+ if (baudRateIndex < 0)
+ {
+ ui->cbBaudRate->setCurrentText(baudRate);
+ }
+ else
+ {
+ ui->cbBaudRate->setCurrentIndex(baudRateIndex);
+ }
+ _selectBaudRate(baudRate);
+}
+
+void PortControl::openPort()
+{
+ if (!serialPort->isOpen())
+ {
+ openAction.trigger();
+ }
+}
+
+unsigned PortControl::maxBitRate() const
+{
+ float baud = serialPort->baudRate();
+ float dataBits = serialPort->dataBits();
+ float parityBits = serialPort->parity() == QSerialPort::NoParity ? 0 : 1;
+
+ float stopBits;
+ if (serialPort->stopBits() == QSerialPort::OneAndHalfStop)
+ {
+ stopBits = 1.5;
+ }
+ else
+ {
+ stopBits = serialPort->stopBits();
+ }
+
+ float frame_size = 1 /* start bit */ + dataBits + parityBits + stopBits;
+
+ return float(baud) / frame_size;
+}
+
void PortControl::saveSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_Port);
@@ -494,7 +576,7 @@ void PortControl::loadSettings(QSettings
parityButtons.button(paritySetting)->setChecked(true);
// load number of bits
- int dataBits = settings->value(SG_Port_Parity, dataBitsButtons.checkedId()).toInt();
+ int dataBits = settings->value(SG_Port_DataBits, dataBitsButtons.checkedId()).toInt();
if (dataBits >=5 && dataBits <= 8)
{
dataBitsButtons.button((QSerialPort::DataBits) dataBits)->setChecked(true);
diff --git a/src/portcontrol.h b/src/portcontrol.h
--- a/src/portcontrol.h
+++ b/src/portcontrol.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -47,6 +47,12 @@ public:
QSerialPort* serialPort;
QToolBar* toolBar();
+ void selectPort(QString portName);
+ void selectBaudrate(QString baudRate);
+ void openPort();
+ /// Returns maximum bit rate for current baud rate
+ unsigned maxBitRate() const;
+
/// Stores port settings into a `QSettings`
void saveSettings(QSettings* settings);
/// Loads port settings from a `QSettings`. If open serial port is closed.
@@ -76,19 +82,18 @@ private:
/// Returns currently selected flow control as text to be saved in settings
QString currentFlowControlText();
-public slots:
+private slots:
void loadPortList();
void loadBaudRateList();
void togglePort();
- void selectPort(QString portName);
+ void selectListedPort(QString portName);
- void selectBaudRate(QString baudRate);
+ void _selectBaudRate(QString baudRate);
void selectParity(int parity); // parity must be one of QSerialPort::Parity
void selectDataBits(int dataBits); // bits must be one of QSerialPort::DataBits
void selectStopBits(int stopBits); // stopBits must be one of QSerialPort::StopBits
void selectFlowControl(int flowControl); // flowControl must be one of QSerialPort::FlowControl
-private slots:
void openActionTriggered(bool checked);
void onCbPortListActivated(int index);
void onTbPortListActivated(int index);
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 © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 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_TextView[] = "TextView";
const char SettingGroup_UpdateCheck[] = "UpdateCheck";
// mainwindow setting keys
@@ -112,6 +113,10 @@ const char SG_Record_Separator[]
const char SG_Record_DisableBuffering[] = "disableBuffering";
const char SG_Record_Timestamp[] = "timestamp";
+// text view settings keys
+const char SG_TextView_NumLines[] = "numLines";
+const char SG_TextView_Decimals[] = "decimals";
+
// update check settings keys
const char SG_UpdateCheck_Periodic[] = "periodicCheck";
const char SG_UpdateCheck_LastCheck[] = "lastCheck";
diff --git a/src/snapshot.h b/src/snapshot.h
--- a/src/snapshot.h
+++ b/src/snapshot.h
@@ -28,6 +28,7 @@
#include "channelinfomodel.h"
#include "readonlybuffer.h"
+#include "indexbuffer.h"
class SnapshotView;
class MainWindow;
@@ -40,7 +41,8 @@ public:
Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved = false);
~Snapshot();
- // TODO: yData of snapshot shouldn't be public, preferable should be handled in constructor
+ // TODO: yData and xData of snapshot shouldn't be public, preferable should be handled in constructor
+ QVector xData;
QVector yData;
QAction* showAction();
QAction* deleteAction();
diff --git a/src/snapshotmanager.cpp b/src/snapshotmanager.cpp
--- a/src/snapshotmanager.cpp
+++ b/src/snapshotmanager.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2018 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -71,6 +71,7 @@ Snapshot* SnapshotManager::makeSnapshot(
for (unsigned ci = 0; ci < _stream->numChannels(); ci++)
{
+ snapshot->xData.append(new IndexBuffer(_stream->numSamples()));
snapshot->yData.append(new ReadOnlyBuffer(_stream->channel(ci)->yData()));
}
@@ -156,8 +157,16 @@ void SnapshotManager::loadSnapshotFromFi
QTextStream ts(&file);
QString line;
unsigned lineNum = 1;
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
while (ts.readLineInto(&line))
{
+#else
+ while (true)
+ {
+ line = ts.readLine();
+ if (line.isNull()) break;
+#endif
// parse line
auto split = line.split(',');
@@ -194,6 +203,7 @@ void SnapshotManager::loadSnapshotFromFi
for (unsigned ci = 0; ci < numOfChannels; ci++)
{
+ snapshot->xData.append(new IndexBuffer(data[ci].size()));
snapshot->yData.append(new ReadOnlyBuffer(data[ci].data(), data[ci].size()));
}
diff --git a/src/stream.cpp b/src/stream.cpp
--- a/src/stream.cpp
+++ b/src/stream.cpp
@@ -20,6 +20,7 @@
#include "stream.h"
#include "ringbuffer.h"
#include "indexbuffer.h"
+#include "linindexbuffer.h"
Stream::Stream(unsigned nc, bool x, unsigned ns) :
_infoModel(nc)
@@ -27,15 +28,20 @@ Stream::Stream(unsigned nc, bool x, unsi
_numSamples = ns;
_paused = false;
+ xAsIndex = true;
+ xMin = 0;
+ xMax = 1;
+
// create xdata buffer
_hasx = x;
if (x)
{
- xData = new RingBuffer(ns);
+ // TODO: implement XRingBuffer (binary search)
+ Q_ASSERT(false);
}
else
{
- xData = new IndexBuffer(ns);
+ xData = makeXBuffer();
}
// create channels
@@ -81,6 +87,16 @@ StreamChannel* Stream::channel(unsigned
return const_cast(static_cast(*this).channel(index));
}
+QVector Stream::allChannels() const
+{
+ QVector result(numChannels());
+ for (unsigned ci = 0; ci < numChannels(); ci++)
+ {
+ result[ci] = channel(ci);
+ }
+ return result;
+}
+
const ChannelInfoModel* Stream::infoModel() const
{
return &_infoModel;
@@ -118,11 +134,12 @@ void Stream::setNumChannels(unsigned nc,
{
if (x)
{
- xData = new RingBuffer(_numSamples);
+ // TODO: implement XRingBuffer (binary search)
+ Q_ASSERT(false);
}
else
{
- xData = new IndexBuffer(_numSamples);
+ xData = makeXBuffer();
}
for (auto c : channels)
@@ -142,6 +159,18 @@ void Stream::setNumChannels(unsigned nc,
Sink::setNumChannels(nc, x);
}
+XFrameBuffer* Stream::makeXBuffer() const
+{
+ if (xAsIndex)
+ {
+ return new IndexBuffer(_numSamples);
+ }
+ else
+ {
+ return new LinIndexBuffer(_numSamples, xMin, xMax);
+ }
+}
+
const SamplePack* Stream::applyGainOffset(const SamplePack& pack) const
{
Q_ASSERT(infoModel()->gainOrOffsetEn());
@@ -191,7 +220,9 @@ void Stream::feedIn(const SamplePack& pa
unsigned ns = pack.numSamples();
if (_hasx)
{
- static_cast(xData)->addSamples(pack.xData(), ns);
+ // TODO: implement XRingBuffer (binary search)
+ Q_ASSERT(false);
+ // static_cast(xData)->addSamples(pack.xData(), ns);
}
// modified pack that gain and offset is applied to
@@ -237,6 +268,24 @@ void Stream::setNumSamples(unsigned valu
}
}
+void Stream::setXAxis(bool asIndex, double min, double max)
+{
+ xAsIndex = asIndex;
+ xMin = min;
+ xMax = max;
+
+ // Note that x axis scaling is ignored when X is provided from source as data
+ // TODO: assert (UI options for x axis should be disabled)
+ if (!hasX())
+ {
+ xData = makeXBuffer();
+ for (auto c : channels)
+ {
+ c->setX(xData);
+ }
+ }
+}
+
void Stream::saveSettings(QSettings* settings) const
{
_infoModel.saveSettings(settings);
diff --git a/src/stream.h b/src/stream.h
--- a/src/stream.h
+++ b/src/stream.h
@@ -48,16 +48,16 @@ public:
* @param x has X data input
* @param ns number of samples
*/
- Stream(unsigned nc = 0, bool x = false, unsigned ns = 0);
+ Stream(unsigned nc = 1, bool x = false, unsigned ns = 2);
~Stream();
- // implementations for `Source`
- virtual bool hasX() const;
- virtual unsigned numChannels() const;
+ bool hasX() const;
+ unsigned numChannels() const;
unsigned numSamples() const;
const StreamChannel* channel(unsigned index) const;
StreamChannel* channel(unsigned index);
+ QVector allChannels() const;
const ChannelInfoModel* infoModel() const;
ChannelInfoModel* infoModel();
@@ -79,10 +79,13 @@ signals:
void dataAdded(); ///< emitted when data added to channel man.
public slots:
- // TODO: these won't be public
- // void setNumChannels(unsigned number);
+ /// Change number of samples (buffer size)
void setNumSamples(unsigned value);
+ /// Change X axis style
+ /// @note Ignored when X is provided by source (hasX == true)
+ void setXAxis(bool asIndex, double min, double max);
+
/// When paused data feed is ignored
void pause(bool paused);
@@ -94,11 +97,14 @@ private:
bool _paused;
bool _hasx;
- ResizableBuffer* xData;
+ XFrameBuffer* xData;
QList channels;
ChannelInfoModel _infoModel;
+ bool xAsIndex;
+ double xMin, xMax;
+
/**
* Applies gain and offset to given pack.
*
@@ -111,6 +117,9 @@ private:
* @return modified data
*/
const SamplePack* applyGainOffset(const SamplePack& pack) const;
+
+ /// Returns a new virtual X buffer for settings
+ XFrameBuffer* makeXBuffer() const;
};
diff --git a/src/streamchannel.cpp b/src/streamchannel.cpp
--- a/src/streamchannel.cpp
+++ b/src/streamchannel.cpp
@@ -17,9 +17,10 @@
along with serialplot. If not, see .
*/
+#include
#include "streamchannel.h"
-StreamChannel::StreamChannel(unsigned i, const FrameBuffer* x,
+StreamChannel::StreamChannel(unsigned i, const XFrameBuffer* x,
FrameBuffer* y, ChannelInfoModel* info)
{
_index = i;
@@ -37,8 +38,37 @@ unsigned StreamChannel::index() const {r
QString StreamChannel::name() const {return _info->name(_index);};
QColor StreamChannel::color() const {return _info->color(_index);};
bool StreamChannel::visible() const {return _info->isVisible(_index);};
-const FrameBuffer* StreamChannel::xData() const {return _x;}
+const XFrameBuffer* StreamChannel::xData() const {return _x;}
const FrameBuffer* StreamChannel::yData() const {return _y;}
FrameBuffer* StreamChannel::yData() {return _y;}
const ChannelInfoModel* StreamChannel::info() const {return _info;}
-void StreamChannel::setX(const FrameBuffer* x) {_x = x;};
+void StreamChannel::setX(const XFrameBuffer* x) {_x = x;};
+
+double StreamChannel::findValue(double x) const
+{
+ int index = _x->findIndex(x);
+ Q_ASSERT(index < (int) _x->size());
+
+ if (index >= 0)
+ {
+ // can't do estimation for last sample
+ if (index == (int) _x->size() - 1)
+ {
+ return _y->sample(index);
+ }
+ else
+ {
+ // calculate middle of the line
+ double prev_x = _x->sample(index);
+ double next_x = _x->sample(index+1);
+ double ratio = (x - prev_x) / (next_x - prev_x);
+ double prev_y = _y->sample(index);
+ double next_y = _y->sample(index+1);
+ return ratio * (next_y - prev_y) + prev_y;
+ }
+ }
+ else
+ {
+ return std::numeric_limits::quiet_NaN();
+ }
+}
diff --git a/src/streamchannel.h b/src/streamchannel.h
--- a/src/streamchannel.h
+++ b/src/streamchannel.h
@@ -35,7 +35,7 @@ public:
* @param info channel info model
*/
StreamChannel(unsigned i,
- const FrameBuffer* x,
+ const XFrameBuffer* x,
FrameBuffer* y,
ChannelInfoModel* info);
~StreamChannel();
@@ -44,15 +44,23 @@ public:
QString name() const;
QColor color() const;
bool visible() const;
- const FrameBuffer* xData() const;
+ const XFrameBuffer* xData() const;
FrameBuffer* yData();
const FrameBuffer* yData() const;
const ChannelInfoModel* info() const;
- void setX(const FrameBuffer* x);
+ void setX(const XFrameBuffer* x);
+
+ /**
+ * Returns sample value for `x`.
+ *
+ * If `x` is out of range `NaN` is returned. A calculated (linear)
+ * value is returned when `x` is in between two data points.
+ */
+ double findValue(double x) const;
private:
unsigned _index;
- const FrameBuffer* _x;
+ const XFrameBuffer* _x;
FrameBuffer* _y;
ChannelInfoModel* _info;
};
diff --git a/src/updatechecker.cpp b/src/updatechecker.cpp
--- a/src/updatechecker.cpp
+++ b/src/updatechecker.cpp
@@ -118,7 +118,7 @@ bool UpdateChecker::parseData(const QJso
if (!data.isObject()) return false;
- auto values = data.object()["values"];
+ auto values = data.object().value("values");
if (values == QJsonValue::Undefined || !values.isArray()) return false;
for (auto value : values.toArray())
diff --git a/src/zoomer.cpp b/src/zoomer.cpp
--- a/src/zoomer.cpp
+++ b/src/zoomer.cpp
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2020 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -19,9 +19,13 @@
#include "zoomer.h"
#include
-#include
+#include
+#include
+#include
+#include
-#include
+static const int VALUE_POINT_DIAM = 4;
+static const int VALUE_TEXT_MARGIN = VALUE_POINT_DIAM + 2;
Zoomer::Zoomer(QWidget* widget, bool doReplot) :
ScrollZoomer(widget)
@@ -59,6 +63,11 @@ void Zoomer::zoom( const QRectF & rect)
ScrollZoomer::zoom(rect);
}
+void Zoomer::setDispChannels(QVector channels)
+{
+ dispChannels = channels;
+}
+
QwtText Zoomer::trackerTextF(const QPointF& pos) const
{
QwtText b = ScrollZoomer::trackerTextF(pos);
@@ -102,6 +111,225 @@ QRegion Zoomer::rubberBandMask() const
return QRegion(r);
}
+void Zoomer::drawTracker(QPainter* painter) const
+{
+ if (isActive())
+ {
+ QwtPlotZoomer::drawTracker(painter);
+ }
+ else if (dispChannels.length())
+ {
+ drawValues(painter);
+ }
+}
+
+QList Zoomer::visChannels() const
+{
+ QList result;
+
+ for (unsigned ci = 0; ci < (unsigned) dispChannels.length(); ci++)
+ {
+ if (dispChannels[ci]->visible())
+ result.append(dispChannels[ci]);
+ }
+
+ return result;
+}
+
+const double ValueLabelHeight = 12; // TODO: calculate
+
+struct ChannelValue
+{
+ const StreamChannel* ch;
+ double value;
+ double y;
+ double top() const {return y;};
+ double bottom() const {return y + ValueLabelHeight;};
+};
+
+static void layoutValues(QList& values)
+{
+ typedef ChannelValue LayItem;
+ typedef QList LayItemList;
+
+ struct LayGroup
+ {
+ struct VRange {double top, bottom;};
+ LayItemList items;
+ LayGroup(LayItem* initialItem) {items.append(initialItem);}
+ unsigned numItems() const {return items.size();}
+ double top() const {return items.first()->top();}
+ double bottom() const {return items.last()->bottom();}
+ VRange vRange() const {return {top(), bottom()};}
+ double overlap(const LayGroup* otrGroup) const
+ {
+ auto myr = vRange();
+ auto otr = otrGroup->vRange();
+
+ double a = myr.bottom - otr.top;
+ double b = otr.bottom - myr.top;
+ if (a > 0 and b > 0)
+ {
+ return std::min(a, b);
+ }
+ return 0;
+ }
+ void moveBy(double y) {for (auto it : items) it->y += y;}
+ void join(LayGroup* other)
+ {
+ // assumes other group is below this one and they overlap
+ double ovr_h = overlap(other);
+
+ // groups are moved less if they have more items and vice versa
+ double ratio = double(numItems()) / double(numItems() + other->numItems());
+ double self_off = ovr_h * (1. - ratio);
+
+ // make sure we don't go out of screen (above) after the shift
+ double final_top = top() - self_off;
+ if (final_top < 0)
+ self_off += final_top;
+
+ // move groups
+ moveBy(-self_off); // up
+ other->moveBy(ovr_h - self_off); // down
+
+ // finalize the merge by gettin items from other
+ do
+ {
+ items.append(other->items.takeFirst());
+ } while (!other->items.isEmpty());
+ }
+ };
+
+ // create initial groups (1 group per item)
+ QList groups;
+ for (auto& val : values)
+ groups.append(new LayGroup(&val));
+
+ // sort groups according to their items position
+ struct {
+ bool operator()(LayGroup* a, LayGroup* b) const
+ {
+ return a->top() < b->top();
+ }
+ } compTops;
+
+ std::sort(groups.begin(), groups.end(), compTops);
+
+ // do spacing
+ bool somethingOverlaps = true;
+ while (somethingOverlaps and groups.size() > 1)
+ {
+ somethingOverlaps = false;
+ for (int i = 0; i < groups.size() - 1; i++)
+ {
+ auto a = groups[i];
+ auto b = groups[i + 1];
+
+ // make sure nothing is over the top
+ if (a->top() < 0)
+ a->moveBy(-a->top());
+
+ // join if groups overlap
+ if (a->overlap(b))
+ {
+ somethingOverlaps = true;
+ a->join(b);
+ delete groups.takeAt(i + 1);
+ break;
+ }
+ }
+ }
+
+ // cleanup
+ do
+ {
+ delete groups.takeFirst();
+ } while (!groups.isEmpty());
+};
+
+void Zoomer::drawValues(QPainter* painter) const
+{
+ auto tpos = trackerPosition();
+ if (tpos.x() < 0) return; // cursor not on window
+
+ // find Y values for current cursor X position
+ double x = invTransform(tpos).x();
+ auto channels = visChannels();
+ QList values;
+ for (auto ch : channels)
+ {
+ double value = ch->findValue(x);
+ if (!std::isnan(value))
+ {
+ auto point = transform(QPointF(x, value));
+ values.append({ch, value, double(point.y())});
+ }
+ }
+
+ // TODO should keep?
+ if (values.isEmpty())
+ {
+ return;
+ }
+
+ layoutValues(values);
+
+ painter->save();
+
+ // draw vertical line
+ auto linePen = rubberBandPen();
+ linePen.setStyle(Qt::DotLine);
+ painter->setPen(linePen);
+ const QRect pRect = pickArea().boundingRect().toRect();
+ int px = tpos.x();
+ painter->drawLine(px, pRect.top(), px, pRect.bottom());
+
+ // draw sample values
+ for (auto value : values)
+ {
+ double val = value.value;
+ auto ch = value.ch;
+
+ auto point = transform(QPointF(x, val));
+
+ painter->setBrush(ch->color());
+ painter->setPen(Qt::NoPen);
+ painter->drawEllipse(point, VALUE_POINT_DIAM, VALUE_POINT_DIAM);
+
+ painter->setPen(rubberBandPen());
+ // We give a very small (1x1) rectangle but disable clipping
+ painter->drawText(QRectF(point.x() + VALUE_TEXT_MARGIN, value.y, 1, 1),
+ Qt::AlignVCenter | Qt::TextDontClip,
+ QString("%1").arg(val));
+ }
+
+ painter->restore();
+}
+
+QRect Zoomer::trackerRect(const QFont& font) const
+{
+ if (isActive())
+ {
+ return QwtPlotZoomer::trackerRect(font);
+ }
+ else
+ {
+ return valueTrackerRect(font);
+ }
+}
+
+QRect Zoomer::valueTrackerRect(const QFont& font) const
+{
+ // TODO: consider using actual tracker values for width calculation
+ const int textWidth = qCeil(QwtText("-8.8888888").textSize(font).width());
+ const int width = textWidth + VALUE_POINT_DIAM + VALUE_TEXT_MARGIN;
+ const int x = trackerPosition().x() - VALUE_POINT_DIAM;
+ const auto pickRect = pickArea().boundingRect();
+
+ return QRect(x, pickRect.y(), width, pickRect.height());
+}
+
void Zoomer::widgetMousePressEvent(QMouseEvent* mouseEvent)
{
if (mouseEvent->modifiers() & Qt::ControlModifier)
diff --git a/src/zoomer.h b/src/zoomer.h
--- a/src/zoomer.h
+++ b/src/zoomer.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2017 Hasan Yavuz Özderya
+ Copyright © 2019 Hasan Yavuz Özderya
This file is part of serialplot.
@@ -20,25 +20,36 @@
#ifndef ZOOMER_H
#define ZOOMER_H
-#include
+#include
+#include
+#include
+
+#include "scrollzoomer.h"
+#include "streamchannel.h"
class Zoomer : public ScrollZoomer
{
Q_OBJECT
public:
- Zoomer(QWidget *, bool doReplot=true);
+ Zoomer(QWidget*, bool doReplot=true);
void zoom(int up);
- void zoom( const QRectF & );
+ void zoom(const QRectF&);
+ /// Set displayed channels for value tracking (can be null)
+ void setDispChannels(QVector channels);
signals:
void unzoomed();
protected:
/// Re-implemented to display selection size in the tracker text.
- QwtText trackerTextF(const QPointF &pos) const;
+ QwtText trackerTextF(const QPointF &pos) const override;
+ /// Re-implemented for sample value tracker
+ QRect trackerRect(const QFont&) const override;
/// Re-implemented for alpha background
- void drawRubberBand(QPainter* painter) const;
+ void drawRubberBand(QPainter* painter) const override;
+ /// Re-implemented to draw sample values
+ void drawTracker(QPainter* painter) const override;
/// Re-implemented for alpha background (masking is basically disabled)
QRegion rubberBandMask() const;
/// Overloaded for panning
@@ -51,6 +62,15 @@ protected:
private:
bool is_panning;
QPointF pan_point;
+ /// displayed channels for value tracking
+ QVector dispChannels;
+
+ /// Get a list of visible channels
+ QList visChannels() const;
+ /// Draw sample values
+ void drawValues(QPainter* painter) const;
+ /// Returns trackerRect for value tracker
+ QRect valueTrackerRect(const QFont& font) const;
};
#endif // ZOOMER_H
diff --git a/tests/test.cpp b/tests/test.cpp
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -224,6 +224,14 @@ TEST_CASE("LinIndexBuffer", "[memory, bu
REQUIRE(buf.sample(0) == -5.0);
REQUIRE(buf.sample(19) == 5.0);
+
+ buf.resize(10);
+ buf.setLimits({0., 3.});
+ REQUIRE(buf.findIndex(0.01) == 0);
+ REQUIRE(buf.findIndex(1.51) == 4);
+ REQUIRE(buf.findIndex(2.99) == 8);
+ REQUIRE(buf.findIndex(3.01) == XFrameBuffer::OUT_OF_RANGE);
+ REQUIRE(buf.findIndex(-0.01) == XFrameBuffer::OUT_OF_RANGE);
}
TEST_CASE("RingBuffer sizing", "[memory, buffer]")
diff --git a/tests/test_stream.cpp b/tests/test_stream.cpp
--- a/tests/test_stream.cpp
+++ b/tests/test_stream.cpp
@@ -27,17 +27,17 @@ TEST_CASE("construction of stream with d
// default values are an empty stream with no channels
Stream s;
- REQUIRE(s.numChannels() == 0);
+ REQUIRE(s.numChannels() == 1);
REQUIRE(!s.hasX());
- REQUIRE(s.numSamples() == 0);
+ REQUIRE(s.numSamples() == 2);
}
TEST_CASE("construction of stream with parameters", "[memory, stream]")
{
- Stream s(4, true, 100);
+ Stream s(4, false, 100);
REQUIRE(s.numChannels() == 4);
- REQUIRE(s.hasX());
+ REQUIRE(!s.hasX());
REQUIRE(s.numSamples() == 100);
for (unsigned i = 0; i < 4; i++)
@@ -64,6 +64,8 @@ TEST_CASE("changing stream number of cha
REQUIRE(c->index() == i);
}
+// TODO: enable test when `Stream` supports X channel
+#if 0
// increase nc value, add X
so._setNumChannels(5, true);
@@ -76,6 +78,7 @@ TEST_CASE("changing stream number of cha
REQUIRE(c != NULL);
REQUIRE(c->index() == i);
}
+#endif
// reduce nc value, remove X
so._setNumChannels(1, false);
@@ -127,9 +130,11 @@ TEST_CASE("adding data to a stream with
}
}
+// TODO: enable test when `Stream` supports X channel
+#if 0
TEST_CASE("adding data to a stream with X", "[memory, stream, data, sink]")
{
- Stream s(3, true, 10);
+ Stream s(3, false, 10);
// prepare data
SamplePack pack(5, 3, true);
@@ -147,7 +152,7 @@ TEST_CASE("adding data to a stream with
}
TestSource so(3, true);
- so.connectSink(&s);
+ REQUIRE_THROWS(so.connectSink(&s));
// test
so._feed(pack);
@@ -178,6 +183,7 @@ TEST_CASE("adding data to a stream with
REQUIRE(x->sample(i) == (i-5)+10);
}
}
+#endif
TEST_CASE("paused stream shouldn't store data", "[memory, stream, pause]")
{