Changeset - a30d48dab8b8
[Not reviewed]
Merge longmem
4 61 44
Hasan Yavuz Ă–ZDERYA - 7 years ago 2018-09-17 15:25:05
hy@ozderya.net
Merge with default
109 files changed with 6483 insertions and 1713 deletions:
0 comments (0 inline, 0 general)
.hgtags
Show inline comments
 
@@ -2,12 +2,13 @@ cdddeef73834c62a8ba5f3ac4c000cd4cb059e06
 
73524cc41d6c6f4a664b39a74ae202be21b58eaf v0.2
 
1332b87e543fd947b57d45ccf62e9a0886e09cb5 v0.3
 
92b7092ddffa87776c7886dc071113416f772860 v0.4
 
11a1ee6b27d1cd655193cfdc032aab76ad7a743c v0.5
 
878ad8de497561098a63a0d21d36b103cea0719c v0.5.1
 
e545d9bf398aa5728e0ae0236f6c014d9fe55320 v0.6
 
b4d0a38444d31872633e474d89ffc15cd0fe42f0 v0.7
 
27b0354ca2c5ea7b3870156417ce7e04e799bbf7 v0.7.1
 
fd5f1eb480ec372b49df58b497458de05c30057c v0.8.0
 
9c9a11cd15fd094e2b2b65dc51805fd8fd1d2460 v0.8.1
 
4cf9a1ee1f107a38e03dbe17c4f2882c43d827c9 v0.9.0
 
ef003f7af8f37f760c22dae776f5ff8e1b526deb v0.9.1
 
7ce85527f3f2f6d19afc0401d2ac723f6c072481 v0.10.0
CMakeLists.txt
Show inline comments
 
#
 
# Copyright © 2017 Hasan Yavuz Özderya
 
# Copyright © 2018 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
@@ -43,141 +43,176 @@ if (BUILD_QWT)
 
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
 
  src/portcontrol.ui
 
  src/about_dialog.ui
 
  src/snapshotview.ui
 
  src/commandpanel.ui
 
  src/commandwidget.ui
 
  src/dataformatpanel.ui
 
  src/plotcontrolpanel.ui
 
  src/recordpanel.ui
 
  src/numberformatbox.ui
 
  src/endiannessbox.ui
 
  src/binarystreamreadersettings.ui
 
  src/asciireadersettings.ui
 
  src/framedreadersettings.ui
 
  src/demoreadersettings.ui
 
  src/updatecheckdialog.ui
 
  )
 

	
 
if (WIN32)
 
  qt5_add_resources(RES_FILES misc/icons.qrc misc/winicons.qrc)
 
else (WIN32)
 
  qt5_add_resources(RES_FILES misc/icons.qrc)
 
endif (WIN32)
 

	
 
add_executable(${PROGRAM_NAME} WIN32
 
  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/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/ringbuffer.cpp
 
  src/indexbuffer.cpp
 
  src/readonlybuffer.cpp
 
  src/framebufferseries.cpp
 
  src/numberformatbox.cpp
 
  src/endiannessbox.cpp
 
  src/abstractreader.cpp
 
  src/binarystreamreader.cpp
 
  src/binarystreamreadersettings.cpp
 
  src/asciireader.cpp
 
  src/asciireadersettings.cpp
 
  src/demoreader.cpp
 
  src/demoreadersettings.cpp
 
  src/framedreader.cpp
 
  src/framedreadersettings.cpp
 
  src/plotmanager.cpp
 
  src/plotmenu.cpp
 
  src/barplot.cpp
 
  src/barchart.cpp
 
  src/barscaledraw.cpp
 
  src/numberformat.cpp
 
  src/updatechecker.cpp
 
  src/versionnumber.cpp
 
  src/updatecheckdialog.cpp
 
  src/samplepack.cpp
 
  src/source.cpp
 
  src/sink.cpp
 
  src/samplecounter.cpp
 
  misc/windows_icon.rc
 
  ${UI_FILES}
 
  ${RES_FILES}
 
  )
 

	
 
# 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)
 
qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Network)
 

	
 
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")
 

	
 
# Enable C++11 support, fail if not supported
 
include(CheckCXXCompilerFlag)
 
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
 
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
 
if(COMPILER_SUPPORTS_CXX11)
 
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
 
elseif(COMPILER_SUPPORTS_CXX0X)
 
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
 
else()
 
  message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
 
endif()
 

	
 
# default version
 
set(VERSION_STRING "0.9.1")
 
set(VERSION_STRING "0.10.0")
 
set(VERSION_REVISION "0")
 

	
 
# get revision number from mercurial and parse version string
 
include(GetVersion)
 

	
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_STRING=\\\"${VERSION_STRING}\\\" ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MAJOR=${VERSION_MAJOR} ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MINOR=${VERSION_MINOR} ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_PATCH=${VERSION_PATCH} ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_REVISION=\\\"${VERSION_REVISION}\\\" ")
 

	
 
# add make run target
 
add_custom_target(run
 
    COMMAND ${PROGRAM_NAME}
 
    DEPENDS ${PROGRAM_NAME}
 
    WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
 
)
 

	
 
# installing
 
install(TARGETS ${PROGRAM_NAME} DESTINATION bin)
 

	
cmake/modules/BuildLedWidget.cmake
Show inline comments
 
new file 100644
 
#
 
# 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 <http://www.gnu.org/licenses/>.
 
#
 

	
 
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)
cmake/modules/BuildLinuxAppImage.cmake
Show inline comments
 
# Based on: https://github.com/mhoeher/opentodolist
 
#
 
# Note: we extract linuxdeployqt appimage so that it can run in docker, that's
 
# because fuse doesn't work in docker.
 

	
 
set(LINUXDEPLOYQT_URL "https://github.com/probonopd/linuxdeployqt/releases/download/4/linuxdeployqt-4-x86_64.AppImage")
 
set(LINUXDEPLOYQT_TOOL ${CMAKE_CURRENT_BINARY_DIR}/linuxdeployqt-4-x86_64.AppImage)
 
set(LINUXDEPLOYQT_URL "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage")
 
set(LINUXDEPLOYQT_APPIMAGE ${CMAKE_CURRENT_BINARY_DIR}/linuxdeployqt-continuous-x86_64.AppImage)
 
set(LINUXDEPLOYQT_TOOL ${CMAKE_CURRENT_BINARY_DIR}/squashfs-root/AppRun)
 

	
 
set(APPIMAGE_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PROGRAM_NAME}-${VERSION_STRING}-${CMAKE_HOST_SYSTEM_PROCESSOR})
 

	
 
add_custom_command(
 
    OUTPUT
 
        ${LINUXDEPLOYQT_TOOL}
 
    COMMAND
 
        wget ${LINUXDEPLOYQT_URL}
 
    COMMAND
 
        chmod a+x ${LINUXDEPLOYQT_TOOL})
 
        chmod a+x ${LINUXDEPLOYQT_APPIMAGE}
 
    COMMAND
 
        ${LINUXDEPLOYQT_APPIMAGE} --appimage-extract)
 

	
 
add_custom_target(
 
    appimage
 

	
 
    DEPENDS ${LINUXDEPLOYQT_TOOL}
 

	
 
    COMMAND
 
        ${CMAKE_COMMAND} -E remove_directory ${APPIMAGE_DIR}
 
    COMMAND
 
        ${CMAKE_COMMAND} -E make_directory ${APPIMAGE_DIR}
 
    COMMAND
 
        ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PROGRAM_NAME}> ${APPIMAGE_DIR}
cmake/modules/BuildQColorWidgets.cmake
Show inline comments
 
#
 
# Copyright © 2017 Hasan Yavuz Özderya
 
# Copyright © 2018 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
include(ExternalProject)
 

	
 
ExternalProject_Add(QCW
 
  PREFIX qcw
 
  GIT_REPOSITORY https://github.com/mbasaglia/Qt-Color-Widgets
 
  PATCH_COMMAND patch -t -p1 -i ${CMAKE_CURRENT_LIST_DIR}/qt_5_2_moc_creation_namespace_fix.diff
 
  GIT_REPOSITORY https://gitlab.com/mattia.basaglia/Qt-Color-Widgets.git
 
  GIT_TAG 2c49e1bb4e1f591e720e2132cc2aaeef3ba73f14
 
  CMAKE_CACHE_ARGS "-DCMAKE_CXX_FLAGS:string=-D QTCOLORWIDGETS_STATICALLY_LINKED"
 
  UPDATE_COMMAND ""
 
  INSTALL_COMMAND "")
 

	
 
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})
cmake/modules/FindLedWidget.cmake
Show inline comments
 
new file 100644
 
#
 
# 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 <http://www.gnu.org/licenses/>.
 
#
 

	
 
find_library(LEDWIDGET_LIBRARY "ledwidget")
 
find_path(LEDWIDGET_INCLUDE_DIR "ledwidget.h" PATH_SUFFIXES "ledwidget")
 

	
 
mark_as_advanced(LEDWIDGET_LIBRARY LEDWIDGET_INCLUDE_DIR)
 

	
 
find_package_handle_standard_args(LedWidget DEFAULT_MSG LEDWIDGET_LIBRARY LEDWIDGET_INCLUDE_DIR)
cmake/modules/qt_5_2_moc_creation_namespace_fix.diff
Show inline comments
 
deleted file
misc/pseudo_device.py
Show inline comments
 
#!/usr/bin/python3
 
#
 
# This script will create a pseudo terminal, and send dummy data over
 
# it for testing purposes. Note that pseuodo terminal is a unix thing,
 
# this script will not work on Windows.
 
#
 
# Currently this script only outputs ASCII(comma separated) data.
 
#
 
# Copyright © 2015 Hasan Yavuz Özderya
 
# Copyright © 2018 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
import os, pty, time, struct, math
 

	
 
def ascii_test_str(port):
 
    text = """
 
1,2,3
 
2,4,6
 
3,8,11
 
1,2,3
 
-1,-1,-1
 
nana
 
0,0,0
 
1,na,na
 
    """
 
    while True:
 
        for line in text.splitlines():
 
            os.write(port, bytes(line+"\r\n", 'utf8'))
 
            time.sleep(1)
 

	
 
def ascii_test(port):
 
    """Put ASCII test data through pseudo terminal."""
 
    print("\n")
 
    nc = 4 # number of channels
 
    for i in range(0, 1000):
 
        data = []
 
        for ci in range(0, nc):
 
            data.append(i*(ci+1))
 
        data = ",".join([str(num) for num in data])
 
        print("<< " + data, end="\r")
 
        os.write(port, bytes(data + "\r\n", 'ASCII'))
 
        time.sleep(0.1)
 
@@ -100,21 +116,22 @@ def frame_test(port, fixed_size=False, h
 
        time.sleep(0.1)
 

	
 
def run():
 
    # create the pseudo terminal
 
    master, slave = pty.openpty()
 

	
 
    master_name = os.ttyname(master)
 
    slave_name = os.ttyname(slave)
 
    print("Master terminal: {}\nSlave terminal: {}".format(master_name, slave_name))
 

	
 
    try:
 
        # float_sine(master)
 
        frame_test(master)
 
        # frame_test(master)
 
        # ascii_test(master)
 
        ascii_test_str(master)
 
    finally:
 
        # close the pseudo terminal files
 
        os.close(master)
 
        os.close(slave)
 

	
 
if __name__=="__main__":
 
    run()
serialplot.pro
Show inline comments
 
@@ -59,25 +59,28 @@ SOURCES += \
 
    src/numberformatbox.cpp \
 
    src/endiannessbox.cpp \
 
    src/framedreadersettings.cpp \
 
    src/abstractreader.cpp \
 
    src/binarystreamreader.cpp \
 
    src/binarystreamreadersettings.cpp \
 
    src/asciireadersettings.cpp \
 
    src/asciireader.cpp \
 
    src/demoreader.cpp \
 
    src/framedreader.cpp \
 
    src/plotmanager.cpp \
 
    src/numberformat.cpp \
 
    src/recordpanel.cpp
 
    src/recordpanel.cpp \
 
    src/updatechecker.cpp \
 
    src/updatecheckdialog.cpp \
 
    src/demoreadersettings.cpp
 

	
 
HEADERS += \
 
    src/mainwindow.h \
 
    src/utils.h \
 
    src/portcontrol.h \
 
    src/floatswap.h \
 
    src/plot.h \
 
    src/zoomer.h \
 
    src/hidabletabwidget.h \
 
    src/framebuffer.h \
 
    src/scalepicker.h \
 
    src/scalezoomer.h \
 
@@ -99,39 +102,44 @@ HEADERS += \
 
    src/endiannessbox.h \
 
    src/framedreadersettings.h \
 
    src/abstractreader.h \
 
    src/binarystreamreader.h \
 
    src/binarystreamreadersettings.h \
 
    src/asciireadersettings.h \
 
    src/asciireader.h \
 
    src/demoreader.h \
 
    src/framedreader.h \
 
    src/plotmanager.h \
 
    src/setting_defines.h \
 
    src/numberformat.h \
 
    src/recordpanel.h
 
    src/recordpanel.h \
 
    src/updatechecker.h \
 
    src/updatecheckdialog.h \
 
    src/demoreadersettings.h
 

	
 
FORMS += \
 
    src/mainwindow.ui \
 
    src/about_dialog.ui \
 
    src/portcontrol.ui \
 
    src/snapshotview.ui \
 
    src/commandpanel.ui \
 
    src/commandwidget.ui \
 
    src/dataformatpanel.ui \
 
    src/plotcontrolpanel.ui \
 
    src/numberformatbox.ui \
 
    src/endiannessbox.ui \
 
    src/framedreadersettings.ui \
 
    src/binarystreamreadersettings.ui \
 
    src/asciireadersettings.ui \
 
    src/recordpanel.ui
 
    src/recordpanel.ui \
 
    src/updatecheckdialog.ui \
 
    src/demoreadersettings.ui
 

	
 
INCLUDEPATH += qmake/ src/
 

	
 
CONFIG += c++11
 

	
 
RESOURCES += misc/icons.qrc
 

	
 
win32 {
 
    RESOURCES += misc/winicons.qrc
 
}
src/abstractreader.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "abstractreader.h"
 

	
 
AbstractReader::AbstractReader(QIODevice* device, ChannelManager* channelMan,
 
                               DataRecorder* recorder, QObject* parent) :
 
AbstractReader::AbstractReader(QIODevice* device, QObject* parent) :
 
    QObject(parent)
 
{
 
    _device = device;
 
    _channelMan = channelMan;
 
    _recorder = recorder;
 
    recording = false;
 
}
 

	
 
    // initialize sps counter
 
    sampleCount = 0;
 
    samplesPerSecond = 0;
 
    QObject::connect(&spsTimer, &QTimer::timeout,
 
                     this, &AbstractReader::spsTimerTimeout);
 
    // TODO: start sps timer when reader is enabled
 
    spsTimer.start(SPS_UPDATE_TIMEOUT * 1000);
 
void AbstractReader::pause(bool enabled)
 
{
 
    paused = enabled;
 
}
 

	
 
void AbstractReader::spsTimerTimeout()
 
void AbstractReader::enable(bool enabled)
 
{
 
    unsigned currentSps = samplesPerSecond;
 
    samplesPerSecond = (sampleCount/numOfChannels())/SPS_UPDATE_TIMEOUT;
 
    if (currentSps != samplesPerSecond)
 
    if (enabled)
 
    {
 
        emit samplesPerSecondChanged(samplesPerSecond);
 
        QObject::connect(_device, &QIODevice::readyRead,
 
                         this, &AbstractReader::onDataReady);
 
    }
 
    sampleCount = 0;
 
    else
 
    {
 
        QObject::disconnect(_device, 0, this, 0);
 
        disconnectSinks();
 
    }
 
}
 

	
 
void AbstractReader::addData(double* samples, unsigned length)
 
{
 
    _channelMan->addData(samples, length);
 
    if (recording)
 
    {
 
        _recorder->addData(samples, length, numOfChannels());
 
    }
 
    sampleCount += length;
 
}
src/abstractreader.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -16,89 +16,59 @@
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef ABSTRACTREADER_H
 
#define ABSTRACTREADER_H
 

	
 
#include <QObject>
 
#include <QIODevice>
 
#include <QWidget>
 
#include <QTimer>
 

	
 
#include "channelmanager.h"
 
#include "datarecorder.h"
 
#include "source.h"
 

	
 
/**
 
 * All reader classes must inherit this class.
 
 */
 
class AbstractReader : public QObject
 
class AbstractReader : public QObject, public Source
 
{
 
    Q_OBJECT
 
public:
 
    explicit AbstractReader(QIODevice* device, ChannelManager* channelMan,
 
                            DataRecorder* recorder, QObject* parent = 0);
 

	
 
    bool recording;                 /// is recording started
 
    explicit AbstractReader(QIODevice* device, QObject* parent = 0);
 

	
 
    /**
 
     * Returns a widget to be shown in data format panel when reader
 
     * is selected.
 
     */
 
    virtual QWidget* settingsWidget() = 0;
 

	
 
    /**
 
     * Number of channels being read.
 
     *
 
     * This number may be user selected or automatically determined
 
     * from incoming stream.
 
     */
 
    virtual unsigned numOfChannels() = 0;
 

	
 
    /// Reader should only read when enabled. Default state should be
 
    /// 'disabled'.
 
    virtual void enable(bool enabled = true) = 0;
 
    virtual void enable(bool enabled = true);
 

	
 
    /**
 
     * @brief Starts sending data to recorder.
 
     *
 
     * @note recorder must have been started!
 
     */
 
    void startRecording();
 

	
 
    /// Stops recording.
 
    void stopRecording();
 
    /// None of the current readers support X channel at the moment
 
    bool hasX() const final { return false; };
 

	
 
signals:
 
    // TODO: should we keep this?
 
    void numOfChannelsChanged(unsigned);
 
    void samplesPerSecondChanged(unsigned);
 

	
 
public slots:
 
    /**
 
     * Pauses the reading.
 
     *
 
     * Reader should actually continue reading to keep the
 
     * synchronization but shouldn't commit data.
 
     */
 
    virtual void pause(bool) = 0;
 
    void pause(bool enabled);
 

	
 
protected:
 
    QIODevice* _device;
 

	
 
    /// Should be called with read data
 
    void addData(double* samples, unsigned length);
 

	
 
private:
 
    const int SPS_UPDATE_TIMEOUT = 1;  // second
 
    bool paused;
 

	
 
    unsigned sampleCount;
 
    unsigned samplesPerSecond;
 

	
 
    ChannelManager* _channelMan;
 
    DataRecorder* _recorder;
 
    QTimer spsTimer;
 

	
 
private slots:
 
    void spsTimerTimeout();
 
protected slots:
 
    /// all derived readers has to override this function
 
    virtual void onDataReady() = 0;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtDebug>
 

	
 
#include "asciireader.h"
 

	
 
/// If set to this value number of channels is determined from input
 
#define NUMOFCHANNELS_AUTO   (0)
 

	
 
AsciiReader::AsciiReader(QIODevice* device, ChannelManager* channelMan,
 
                         DataRecorder* recorder, QObject* parent) :
 
    AbstractReader(device, channelMan, recorder, parent)
 
AsciiReader::AsciiReader(QIODevice* device, QObject* parent) :
 
    AbstractReader(device, parent)
 
{
 
    paused = false;
 
    discardFirstLine = true;
 

	
 
    _numOfChannels = _settingsWidget.numOfChannels();
 
    autoNumOfChannels = (_numOfChannels == NUMOFCHANNELS_AUTO);
 
    _numChannels = _settingsWidget.numOfChannels();
 
    autoNumOfChannels = (_numChannels == NUMOFCHANNELS_AUTO);
 
    delimiter = _settingsWidget.delimiter();
 

	
 
    connect(&_settingsWidget, &AsciiReaderSettings::numOfChannelsChanged,
 
            [this](unsigned value)
 
            {
 
                _numOfChannels = value;
 
                autoNumOfChannels = (_numOfChannels == NUMOFCHANNELS_AUTO);
 
                _numChannels = value;
 
                updateNumChannels(); // TODO: setting numchannels = 0, should remove all buffers
 
                                     // do we want this?
 
                autoNumOfChannels = (_numChannels == NUMOFCHANNELS_AUTO);
 
                if (!autoNumOfChannels)
 
                {
 
                    emit numOfChannelsChanged(value);
 
                }
 
            });
 

	
 
    connect(device, &QIODevice::aboutToClose, [this](){discardFirstLine=true;});
 
    connect(&_settingsWidget, &AsciiReaderSettings::delimiterChanged,
 
            [this](QChar d)
 
            {
 
                delimiter = d;
 
            });
 
}
 

	
 
QWidget* AsciiReader::settingsWidget()
 
{
 
    return &_settingsWidget;
 
}
 

	
 
unsigned AsciiReader::numOfChannels()
 
unsigned AsciiReader::numChannels() const
 
{
 
    // TODO: an alternative is to never set _numChannels to '0'
 
    // do not allow '0'
 
    if (_numOfChannels == 0)
 
    {
 
        return 1;
 
    }
 
    else
 
    {
 
        return _numOfChannels;
 
    }
 
    return _numChannels == 0 ? 1 : _numChannels;
 
}
 

	
 
// TODO: this could be a part of AbstractReader
 
void AsciiReader::enable(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        discardFirstLine = true;
 
        firstReadAfterEnable = true;
 
        QObject::connect(_device, &QIODevice::readyRead,
 
                         this, &AsciiReader::onDataReady);
 
    }
 
    else
 
    {
 
        QObject::disconnect(_device, 0, this, 0);
 
        disconnectSinks();
 
    }
 
}
 

	
 
void AsciiReader::pause(bool enabled)
 
{
 
    paused = enabled;
 
}
 

	
 
void AsciiReader::onDataReady()
 
{
 
    while(_device->canReadLine())
 
    {
 
        QByteArray line = _device->readLine();
 
        QString line = QString(_device->readLine());
 

	
 
        // discard only once when we just started reading
 
        if (discardFirstLine)
 
        if (firstReadAfterEnable)
 
        {
 
            discardFirstLine = false;
 
            firstReadAfterEnable = false;
 
            continue;
 
        }
 

	
 
        // discard data if paused
 
        if (paused)
 
        {
 
            continue;
 
        }
 

	
 
        // parse data
 
        line = line.trimmed();
 

	
 
        // Note: When data coming from pseudo terminal is buffered by
 
        // system CR is converted to LF for some reason. This causes
 
        // empty lines in the input when the port is just opened.
 
        if (line.isEmpty())
 
        {
 
            continue;
 
        }
 

	
 
        auto separatedValues = line.split(',');
 
        const SamplePack* samples = parseLine(line);
 
        if (samples != nullptr) {
 
            // update number of channels if in auto mode
 
            if (autoNumOfChannels ) {
 
                unsigned nc = samples->numChannels();
 
                if (nc != _numChannels) {
 
                    _numChannels = nc;
 
                    updateNumChannels();
 
                    // TODO: is `numOfChannelsChanged` signal still used?
 
                    emit numOfChannelsChanged(nc);
 
                }
 
            }
 

	
 
        unsigned numReadChannels; // effective number of channels to read
 
        unsigned numComingChannels = separatedValues.length();
 
            Q_ASSERT(samples->numChannels() == _numChannels);
 

	
 
            // commit data
 
            feedOut(*samples);
 
        }
 
    }
 
}
 

	
 
        if (autoNumOfChannels)
 
        {
 
            // did number of channels changed?
 
            if (numComingChannels != _numOfChannels)
 
            {
 
                _numOfChannels = numComingChannels;
 
                emit numOfChannelsChanged(numComingChannels);
 
            }
 
            numReadChannels = numComingChannels;
 
        }
 
        else if (numComingChannels >= _numOfChannels)
 
        {
 
            numReadChannels = _numOfChannels;
 
        }
 
        else // there is missing channel data
 
SamplePack* AsciiReader::parseLine(const QString& line) const
 
{
 
    auto separatedValues = line.split(delimiter, QString::SkipEmptyParts);
 
    unsigned numComingChannels = separatedValues.length();
 

	
 
    // check number of channels (skipped if auto num channels is enabled)
 
    if ((!numComingChannels) || (!autoNumOfChannels && numComingChannels != _numChannels))
 
    {
 
        qWarning() << "Line parsing error: invalid number of channels!";
 
        qWarning() << "Read line: " << line;
 
        return nullptr;
 
    }
 

	
 
    // parse data per channel
 
    auto samples = new SamplePack(1, numComingChannels);
 
    for (unsigned ci = 0; ci < numComingChannels; ci++)
 
    {
 
        bool ok;
 
        samples->data(ci)[0] = separatedValues[ci].toDouble(&ok);
 
        if (!ok)
 
        {
 
            numReadChannels = separatedValues.length();
 
            qWarning() << "Incoming data is missing data for some channels!";
 
            qWarning() << "Data parsing error for channel: " << ci;
 
            qWarning() << "Read line: " << line;
 
        }
 

	
 
        // parse read line
 
        double* channelSamples = new double[_numOfChannels]();
 
        for (unsigned ci = 0; ci < numReadChannels; ci++)
 
        {
 
            bool ok;
 
            channelSamples[ci] = separatedValues[ci].toDouble(&ok);
 
            if (!ok)
 
            {
 
                qWarning() << "Data parsing error for channel: " << ci;
 
                qWarning() << "Read line: " << line;
 
                channelSamples[ci] = 0;
 
            }
 
            delete samples;
 
            return nullptr;
 
        }
 
    }
 

	
 
        // commit data
 
        addData(channelSamples, _numOfChannels);
 

	
 
        delete[] channelSamples;
 
    }
 
    return samples;
 
}
 

	
 
void AsciiReader::saveSettings(QSettings* settings)
 
{
 
    _settingsWidget.saveSettings(settings);
 
}
 

	
 
void AsciiReader::loadSettings(QSettings* settings)
 
{
 
    _settingsWidget.loadSettings(settings);
 
}
src/asciireader.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef ASCIIREADER_H
 
#define ASCIIREADER_H
 

	
 
#include <QSettings>
 
#include <QString>
 

	
 
#include "samplepack.h"
 
#include "abstractreader.h"
 
#include "asciireadersettings.h"
 

	
 
class AsciiReader : public AbstractReader
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit AsciiReader(QIODevice* device, ChannelManager* channelMan,
 
                         DataRecorder* recorder, QObject *parent = 0);
 
    explicit AsciiReader(QIODevice* device, QObject *parent = 0);
 
    QWidget* settingsWidget();
 
    unsigned numOfChannels();
 
    void enable(bool enabled = true);
 
    unsigned numChannels() const;
 
    void enable(bool enabled) override;
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    void pause(bool);
 

	
 
private:
 
    AsciiReaderSettings _settingsWidget;
 
    unsigned _numOfChannels;
 
    unsigned _numChannels;
 
    /// number of channels will be determined from incoming data
 
    unsigned autoNumOfChannels;
 
    bool paused;
 
    QChar delimiter; ///< selected column delimiter
 

	
 
    // We may have (usually true) started reading in the middle of a
 
    // line, so its a better idea to just discard first line.
 
    bool discardFirstLine;
 
    bool firstReadAfterEnable = false;
 

	
 
private slots:
 
    void onDataReady();
 
    void onDataReady() override;
 
    /**
 
     * Parses given line and returns sample pack.
 
     *
 
     * Returns `nullptr` in case of error.
 
     */
 
    SamplePack* parseLine(const QString& line) const;
 
};
 

	
 
#endif // ASCIIREADER_H
src/asciireadersettings.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QRegularExpressionValidator>
 
#include <QRegularExpression>
 

	
 
#include "utils.h"
 
#include "setting_defines.h"
 

	
 
#include "asciireadersettings.h"
 
#include "ui_asciireadersettings.h"
 

	
 
#include <QtDebug>
 

	
 
AsciiReaderSettings::AsciiReaderSettings(QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::AsciiReaderSettings)
 
{
 
    ui->setupUi(this);
 

	
 
    auto validator = new QRegularExpressionValidator(QRegularExpression("[^\\d]?"), this);
 
    ui->leDelimiter->setValidator(validator);
 

	
 
    connect(ui->rbComma, &QAbstractButton::toggled,
 
            this, &AsciiReaderSettings::delimiterToggled);
 
    connect(ui->rbSpace, &QAbstractButton::toggled,
 
            this, &AsciiReaderSettings::delimiterToggled);
 
    connect(ui->rbTab, &QAbstractButton::toggled,
 
            this, &AsciiReaderSettings::delimiterToggled);
 
    connect(ui->rbOtherDelimiter, &QAbstractButton::toggled,
 
            this, &AsciiReaderSettings::delimiterToggled);
 
    connect(ui->leDelimiter, &QLineEdit::textChanged,
 
            this, &AsciiReaderSettings::customDelimiterChanged);
 

	
 
    // Note: if directly connected we get a runtime warning on incompatible signal arguments
 
    connect(ui->spNumOfChannels, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged),
 
            [this](int value)
 
            {
 
                emit numOfChannelsChanged(value);
 
            });
 
}
 

	
 
AsciiReaderSettings::~AsciiReaderSettings()
 
{
 
    delete ui;
 
}
 

	
 
unsigned AsciiReaderSettings::numOfChannels()
 
unsigned AsciiReaderSettings::numOfChannels() const
 
{
 
    return ui->spNumOfChannels->value();
 
}
 

	
 
QChar AsciiReaderSettings::delimiter() const
 
{
 
    if (ui->rbComma->isChecked())
 
    {
 
        return QChar(',');
 
    }
 
    else if (ui->rbSpace->isChecked())
 
    {
 
        return QChar(' ');
 
    }
 
    else if (ui->rbTab->isChecked())
 
    {
 
        return QChar('\t');
 
    }
 
    else                        // rbOther
 
    {
 
        auto t = ui->leDelimiter->text();
 
        return t.isEmpty() ? QChar() : t.at(0);
 
    }
 
}
 

	
 
void AsciiReaderSettings::delimiterToggled(bool checked)
 
{
 
    if (!checked) return;
 

	
 
    auto d = delimiter();
 
    if (!d.isNull())
 
    {
 
        emit delimiterChanged(d);
 
    }
 
}
 

	
 
void AsciiReaderSettings::customDelimiterChanged(const QString text)
 
{
 
    if (ui->rbOtherDelimiter->isChecked())
 
    {
 
        if (!text.isEmpty()) emit delimiterChanged(text.at(0));
 
    }
 
}
 

	
 
void AsciiReaderSettings::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_ASCII);
 

	
 
    // save number of channels setting
 
    QString numOfChannelsSetting = QString::number(numOfChannels());
 
    if (numOfChannelsSetting == "0") numOfChannelsSetting = "auto";
 
    settings->setValue(SG_ASCII_NumOfChannels, numOfChannelsSetting);
 

	
 
    // save delimiter
 
    QString delimiterS;
 
    if (ui->rbOtherDelimiter->isChecked())
 
    {
 
        delimiterS = "other";
 
    }
 
    else if (ui->rbTab->isChecked())
 
    {
 
        // Note: \t is not correctly loaded
 
        delimiterS = "TAB";
 
    }
 
    else
 
    {
 
        delimiterS = delimiter();
 
    }
 

	
 
    settings->setValue(SG_ASCII_Delimiter, delimiterS);
 
    settings->setValue(SG_ASCII_CustomDelimiter, ui->leDelimiter->text());
 

	
 
    settings->endGroup();
 
}
 

	
 
void AsciiReaderSettings::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_ASCII);
 

	
 
    // load number of channels
 
    QString numOfChannelsSetting =
 
        settings->value(SG_ASCII_NumOfChannels, numOfChannels()).toString();
 

	
 
    if (numOfChannelsSetting == "auto")
 
@@ -74,14 +148,35 @@ void AsciiReaderSettings::loadSettings(Q
 
        ui->spNumOfChannels->setValue(0);
 
    }
 
    else
 
    {
 
        bool ok;
 
        int nc = numOfChannelsSetting.toInt(&ok);
 
        if (ok)
 
        {
 
            ui->spNumOfChannels->setValue(nc);
 
        }
 
    }
 

	
 
    // load delimiter
 
    auto delimiterS = settings->value(SG_ASCII_Delimiter, delimiter()).toString();
 
    auto customDelimiter = settings->value(SG_ASCII_CustomDelimiter, delimiter()).toString();
 
    if (!customDelimiter.isEmpty()) ui->leDelimiter->setText(customDelimiter);
 
    if (delimiterS == ",")
 
    {
 
        ui->rbComma->setChecked(true);
 
    }
 
    else if (delimiterS == " ")
 
    {
 
        ui->rbSpace->setChecked(true);
 
    }
 
    else if (delimiterS == "TAB")
 
    {
 
        ui->rbTab->setChecked(true);
 
    }
 
    else
 
    {
 
        ui->rbOtherDelimiter->setChecked(true);
 
    }
 

	
 
    settings->endGroup();
 
}
src/asciireadersettings.h
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef ASCIIREADERSETTINGS_H
 
#define ASCIIREADERSETTINGS_H
 

	
 
#include <QWidget>
 
#include <QSettings>
 
#include <QChar>
 

	
 
namespace Ui {
 
class AsciiReaderSettings;
 
}
 

	
 
class AsciiReaderSettings : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit AsciiReaderSettings(QWidget *parent = 0);
 
    ~AsciiReaderSettings();
 

	
 
    unsigned numOfChannels();
 
    unsigned numOfChannels() const;
 
    QChar delimiter() const;
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
signals:
 
    void numOfChannelsChanged(unsigned);
 
    /// Signaled only with a valid delimiter
 
    void delimiterChanged(QChar);
 

	
 
private:
 
    Ui::AsciiReaderSettings *ui;
 

	
 
private slots:
 
    void delimiterToggled(bool checked);
 
    void customDelimiterChanged(const QString text);
 
};
 

	
 
#endif // ASCIIREADERSETTINGS_H
src/asciireadersettings.ui
Show inline comments
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>AsciiReaderSettings</class>
 
 <widget class="QWidget" name="AsciiReaderSettings">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>414</width>
 
    <width>493</width>
 
    <height>171</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QVBoxLayout" name="verticalLayout">
 
  <layout class="QFormLayout" name="formLayout">
 
   <property name="fieldGrowthPolicy">
 
    <enum>QFormLayout::ExpandingFieldsGrow</enum>
 
   </property>
 
   <property name="leftMargin">
 
    <number>0</number>
 
   </property>
 
   <property name="topMargin">
 
    <number>0</number>
 
   </property>
 
   <property name="rightMargin">
 
    <number>0</number>
 
   </property>
 
   <property name="bottomMargin">
 
    <number>0</number>
 
   </property>
 
   <item>
 
   <item row="0" column="0">
 
    <widget class="QLabel" name="label_4">
 
     <property name="text">
 
      <string>Number Of Channels:</string>
 
     </property>
 
    </widget>
 
   </item>
 
   <item row="0" column="1">
 
    <widget class="QSpinBox" name="spNumOfChannels">
 
     <property name="minimumSize">
 
      <size>
 
       <width>60</width>
 
       <height>0</height>
 
      </size>
 
     </property>
 
     <property name="toolTip">
 
      <string>Select number of channels or set to 0 for Auto (determined from incoming data)</string>
 
     </property>
 
     <property name="specialValueText">
 
      <string>Auto</string>
 
     </property>
 
     <property name="keyboardTracking">
 
      <bool>false</bool>
 
     </property>
 
     <property name="minimum">
 
      <number>0</number>
 
     </property>
 
     <property name="maximum">
 
      <number>32</number>
 
     </property>
 
    </widget>
 
   </item>
 
   <item row="2" column="0">
 
    <widget class="QLabel" name="label">
 
     <property name="text">
 
      <string>Column Delimiter:</string>
 
     </property>
 
    </widget>
 
   </item>
 
   <item row="2" column="1">
 
    <layout class="QHBoxLayout" name="horizontalLayout">
 
     <item>
 
      <widget class="QLabel" name="label_4">
 
      <widget class="QRadioButton" name="rbComma">
 
       <property name="text">
 
        <string>Number Of Channels:</string>
 
        <string>comma</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QSpinBox" name="spNumOfChannels">
 
       <property name="minimumSize">
 
        <size>
 
         <width>60</width>
 
         <height>0</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>Select number of channels or set to 0 for Auto (determined from incoming data)</string>
 
      <widget class="QRadioButton" name="rbSpace">
 
       <property name="text">
 
        <string>space</string>
 
       </property>
 
       <property name="specialValueText">
 
        <string>Auto</string>
 
       </property>
 
       <property name="keyboardTracking">
 
        <bool>false</bool>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QRadioButton" name="rbTab">
 
       <property name="text">
 
        <string>tab</string>
 
       </property>
 
       <property name="minimum">
 
        <number>0</number>
 
       </property>
 
       <property name="maximum">
 
        <number>32</number>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QRadioButton" name="rbOtherDelimiter">
 
       <property name="text">
 
        <string>other:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <spacer name="horizontalSpacer">
 
       <property name="orientation">
 
        <enum>Qt::Horizontal</enum>
 
      <widget class="QLineEdit" name="leDelimiter">
 
       <property name="sizePolicy">
 
        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
 
         <horstretch>0</horstretch>
 
         <verstretch>0</verstretch>
 
        </sizepolicy>
 
       </property>
 
       <property name="sizeHint" stdset="0">
 
       <property name="maximumSize">
 
        <size>
 
         <width>1</width>
 
         <height>20</height>
 
         <width>30</width>
 
         <height>16777215</height>
 
        </size>
 
       </property>
 
      </spacer>
 
       <property name="toolTip">
 
        <string>Enter a custom delimiter character</string>
 
       </property>
 
       <property name="inputMask">
 
        <string/>
 
       </property>
 
       <property name="text">
 
        <string>|</string>
 
       </property>
 
      </widget>
 
     </item>
 
    </layout>
 
   </item>
 
   <item>
 
    <spacer name="verticalSpacer">
 
     <property name="orientation">
 
      <enum>Qt::Vertical</enum>
 
     </property>
 
     <property name="sizeHint" stdset="0">
 
      <size>
 
       <width>20</width>
 
       <height>1</height>
 
      </size>
 
     </property>
 
    </spacer>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections/>
 
</ui>
src/barchart.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QPalette>
 
#include <qwt_scale_map.h>
 

	
 
#include "barchart.h"
 

	
 
BarChart::BarChart(const Stream* stream)
 
{
 
    _stream = stream;
 
    setSpacing(0);
 
}
 

	
 
void BarChart::resample()
 
{
 
    setSamples(chartData());
 
}
 

	
 
QVector<double> BarChart::chartData() const
 
{
 
    unsigned numChannels = _stream->numChannels();
 
    unsigned numSamples = _stream->numSamples();
 
    QVector<double> data(numChannels);
 
    for (unsigned i = 0; i < numChannels; i++)
 
    {
 
        data[i] = _stream->channel(i)->yData()->sample(numSamples-1);
 
    }
 
    return data;
 
}
 

	
 
QwtColumnSymbol* BarChart::specialSymbol(int sampleIndex, const QPointF& sample) const
 
{
 
    unsigned numChannels = _stream->numChannels();
 
    if (sampleIndex < 0 || sampleIndex > (int) numChannels)
 
    {
 
        return NULL;
 
    }
 

	
 
    auto color = _stream->channel(sampleIndex)->color();
 

	
 
    QwtColumnSymbol* symbol = new QwtColumnSymbol(QwtColumnSymbol::Box);
 
    symbol->setLineWidth(1);
 
    symbol->setFrameStyle(QwtColumnSymbol::Plain);
 
    symbol->setPalette(QPalette(color));
 

	
 
    return symbol;
 
}
 

	
 
void BarChart::drawSample(
 
        QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap,
 
        const QRectF &canvasRect, const QwtInterval &boundingInterval,
 
        int index, const QPointF &sample ) const
 
{
 
    QwtPlotBarChart::drawSample(painter, xMap, yMap, canvasRect, boundingInterval, index, sample);
 

	
 
    // calculate bar size
 
    const double barWidth = sampleWidth(xMap, canvasRect.width(),
 
                                        boundingInterval.width(), sample.y());
 
    const double x = xMap.transform( sample.x() );
 
    const double x1 = x - 0.5 * barWidth;
 
    // const double x2 = x + 0.5 * barWidth;
 

	
 
    const double y1 = yMap.transform(baseline());
 
    const double y2 = yMap.transform(sample.y());
 
    const double barHeight = fabs(y2 - y1);
 

	
 
    // create and calculate text size
 
    const auto valueStr = QString::number(sample.y());
 
    auto valueText = QwtText(valueStr, QwtText::PlainText);
 
    const auto textSize = valueText.textSize();
 
    auto r = QRectF(x1, y2, barWidth, textSize.height());
 
    if (y2 > y1) r.moveBottom(y2);
 

	
 
    // display text if there is enough space
 
    if (barHeight >= textSize.height() && barWidth >= textSize.width())
 
    {
 
        valueText.draw(painter, r);
 
    }
 
}
src/barchart.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BARCHART_H
 
#define BARCHART_H
 

	
 
#include <qwt_plot_barchart.h>
 
#include <qwt_column_symbol.h>
 
#include "stream.h"
 

	
 
class BarChart : public QwtPlotBarChart
 
{
 
public:
 
    explicit BarChart(const Stream* stream);
 

	
 
    void resample();
 
    QwtColumnSymbol* specialSymbol(int sampleIndex, const QPointF&) const;
 

	
 
    void drawSample(
 
        QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap,
 
        const QRectF &canvasRect, const QwtInterval &boundingInterval,
 
        int index, const QPointF &sample ) const;
 

	
 
private:
 
    const Stream* _stream;
 

	
 
    QVector<double> chartData() const;
 
};
 

	
 

	
 
#endif // BARCHART_H
src/barplot.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "barplot.h"
 
#include "barscaledraw.h"
 
#include "utils.h"
 

	
 
BarPlot::BarPlot(Stream* stream, PlotMenu* menu, QWidget* parent) :
 
    QwtPlot(parent), _menu(menu), barChart(stream)
 
{
 
    _stream = stream;
 
    barChart.attach(this);
 
    setAxisMaxMinor(QwtPlot::xBottom, 0);
 
    setAxisScaleDraw(QwtPlot::xBottom, new BarScaleDraw(stream));
 

	
 
    update();
 
    connect(_stream, &Stream::dataAdded, this, &BarPlot::update);
 
    connect(_stream, &Stream::numChannelsChanged, this, &BarPlot::update);
 

	
 
    // connect to menu
 
    connect(&menu->darkBackgroundAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &BarPlot::darkBackground);
 
    darkBackground(menu->darkBackgroundAction.isChecked());
 
}
 

	
 
void BarPlot::update()
 
{
 
    // Note: -0.99 is used instead of -1 to handle the case of `numOfChannels==1`
 
    setAxisScale(QwtPlot::xBottom, 0, _stream->numChannels()-0.99, 1);
 
    barChart.resample();
 
    replot();
 
}
 

	
 
void BarPlot::setYAxis(bool autoScaled, double yMin, double yMax)
 
{
 
    if (autoScaled)
 
    {
 
        setAxisAutoScale(QwtPlot::yLeft);
 
    }
 
    else
 
    {
 
        setAxisScale(QwtPlot::yLeft, yMin, yMax);
 
    }
 
}
 

	
 

	
 
void BarPlot::darkBackground(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        setCanvasBackground(QBrush(Qt::black));
 
    }
 
    else
 
    {
 
        setCanvasBackground(QBrush(Qt::white));
 
    }
 
    replot();
 
}
src/barplot.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BARPLOT_H
 
#define BARPLOT_H
 

	
 
#include <qwt_plot.h>
 

	
 
#include "stream.h"
 
#include "plotmenu.h"
 
#include "barchart.h"
 

	
 
class BarPlot : public QwtPlot
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit BarPlot(Stream* stream,
 
                     PlotMenu* menu,
 
                     QWidget* parent = 0);
 

	
 
public slots:
 
    /// Set the Y axis
 
    void setYAxis(bool autoScaled, double yMin = 0, double yMax = 1);
 
    /// Enable/disable dark background
 
    void darkBackground(bool enabled);
 

	
 
private:
 
    Stream* _stream;
 
    PlotMenu* _menu;
 
    BarChart barChart;
 

	
 
    QVector<double> chartData() const;
 

	
 
private slots:
 
    void update();
 
};
 

	
 
#endif // BARPLOT_H
src/barscaledraw.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "barscaledraw.h"
 

	
 
#include <QtDebug>
 

	
 
BarScaleDraw::BarScaleDraw(const Stream* stream)
 
{
 
    _stream = stream;
 
    enableComponent(Backbone, false);
 
    setLabelRotation(-90);
 
    setLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter);
 

	
 
    QObject::connect(_stream, &Stream::channelNameChanged,
 
            [this]()
 
            {
 
                invalidateCache();
 
            });
 
}
 

	
 
QwtText BarScaleDraw::label(double value) const
 
{
 
    int index = value;
 
    unsigned numChannels = _stream->numChannels();
 

	
 
    if (index >=0 && index < (int) numChannels)
 
    {
 
        return _stream->channel(index)->name();
 
    }
 
    else
 
    {
 
        return QString("");
 
    }
 
}
src/barscaledraw.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BARSCALEDRAW_H
 
#define BARSCALEDRAW_H
 

	
 
#include <QStringList>
 
#include <qwt_scale_draw.h>
 
#include <qwt_text.h>
 

	
 
#include "stream.h"
 

	
 
class BarScaleDraw : public QwtScaleDraw
 
{
 
public:
 
    explicit BarScaleDraw(const Stream* stream);
 
    QwtText label(double value) const;
 

	
 
private:
 
    const Stream* _stream;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtEndian>
 
#include <QtDebug>
 

	
 
#include "binarystreamreader.h"
 
#include "floatswap.h"
 

	
 
BinaryStreamReader::BinaryStreamReader(QIODevice* device, ChannelManager* channelMan,
 
                                       DataRecorder* recorder, QObject* parent) :
 
    AbstractReader(device, channelMan, recorder, parent)
 
BinaryStreamReader::BinaryStreamReader(QIODevice* device, QObject* parent) :
 
    AbstractReader(device, parent)
 
{
 
    paused = false;
 
    skipByteRequested = false;
 
    skipSampleRequested = false;
 

	
 
    _numOfChannels = _settingsWidget.numOfChannels();
 
    _numChannels = _settingsWidget.numOfChannels();
 
    connect(&_settingsWidget, &BinaryStreamReaderSettings::numOfChannelsChanged,
 
            this, &BinaryStreamReader::numOfChannelsChanged);
 
    connect(&_settingsWidget, &BinaryStreamReaderSettings::numOfChannelsChanged,
 
            this, &BinaryStreamReader::onNumOfChannelsChanged);
 
                     this, &BinaryStreamReader::onNumOfChannelsChanged);
 

	
 
    // initial number format selection
 
    onNumberFormatChanged(_settingsWidget.numberFormat());
 
    connect(&_settingsWidget, &BinaryStreamReaderSettings::numberFormatChanged,
 
            this, &BinaryStreamReader::onNumberFormatChanged);
 

	
 
    // enable skip byte and sample buttons
 
    connect(&_settingsWidget, &BinaryStreamReaderSettings::skipByteRequested,
 
            [this]()
 
            {
 
                skipByteRequested = true;
 
            });
 
    connect(&_settingsWidget, &BinaryStreamReaderSettings::skipSampleRequested,
 
            [this]()
 
            {
 
                skipSampleRequested = true;
 
            });
 
}
 

	
 
QWidget* BinaryStreamReader::settingsWidget()
 
{
 
    return &_settingsWidget;
 
}
 

	
 
unsigned BinaryStreamReader::numOfChannels()
 
{
 
    return _numOfChannels;
 
}
 

	
 
void BinaryStreamReader::enable(bool enabled)
 
unsigned BinaryStreamReader::numChannels() const
 
{
 
    if (enabled)
 
    {
 
        QObject::connect(_device, &QIODevice::readyRead,
 
                         this, &BinaryStreamReader::onDataReady);
 
    }
 
    else
 
    {
 
        QObject::disconnect(_device, 0, this, 0);
 
    }
 
}
 

	
 
void BinaryStreamReader::pause(bool enabled)
 
{
 
    paused = enabled;
 
    return _numChannels;
 
}
 

	
 
void BinaryStreamReader::onNumberFormatChanged(NumberFormat numberFormat)
 
{
 
    switch(numberFormat)
 
    {
 
        case NumberFormat_uint8:
 
            sampleSize = 1;
 
            readSample = &BinaryStreamReader::readSampleAs<quint8>;
 
            break;
 
        case NumberFormat_int8:
 
            sampleSize = 1;
 
@@ -114,75 +93,73 @@ void BinaryStreamReader::onNumberFormatC
 
        case NumberFormat_float:
 
            sampleSize = 4;
 
            readSample = &BinaryStreamReader::readSampleAs<float>;
 
            break;
 
        case NumberFormat_INVALID:
 
            Q_ASSERT(1); // never
 
            break;
 
    }
 
}
 

	
 
void BinaryStreamReader::onNumOfChannelsChanged(unsigned value)
 
{
 
    _numOfChannels = value;
 
    _numChannels = value;
 
    updateNumChannels();
 
    emit numOfChannelsChanged(value);
 
}
 

	
 
void BinaryStreamReader::onDataReady()
 
{
 
    // a package is a set of channel data like {CHAN0_SAMPLE, CHAN1_SAMPLE...}
 
    int packageSize = sampleSize * _numOfChannels;
 
    int packageSize = sampleSize * _numChannels;
 
    int bytesAvailable = _device->bytesAvailable();
 

	
 
    // skip 1 byte if requested
 
    if (bytesAvailable > 0 && skipByteRequested)
 
    if (skipByteRequested && bytesAvailable > 0)
 
    {
 
        _device->read(1);
 
        skipByteRequested = false;
 
        bytesAvailable--;
 
    }
 

	
 
    // skip 1 sample (channel) if requested
 
    if (bytesAvailable >= (int) sampleSize && skipSampleRequested)
 
    if (skipSampleRequested && bytesAvailable >= (int) sampleSize)
 
    {
 
        _device->read(sampleSize);
 
        skipSampleRequested = false;
 
        bytesAvailable--;
 
        bytesAvailable -= sampleSize;
 
    }
 

	
 
    if (bytesAvailable < packageSize) return;
 

	
 
    int numOfPackagesToRead =
 
        (bytesAvailable - (bytesAvailable % packageSize)) / packageSize;
 

	
 
    if (paused)
 
    {
 
        // read and discard data
 
        _device->read(numOfPackagesToRead*packageSize);
 
        return;
 
    }
 

	
 
    double* channelSamples = new double[numOfPackagesToRead*_numOfChannels];
 

	
 
    // actual reading
 
    SamplePack samples(numOfPackagesToRead, _numChannels);
 
    for (int i = 0; i < numOfPackagesToRead; i++)
 
    {
 
        for (unsigned int ci = 0; ci < _numOfChannels; ci++)
 
        for (unsigned int ci = 0; ci < _numChannels; ci++)
 
        {
 
            // channelSamples[ci].replace(i, (this->*readSample)());
 
            channelSamples[ci*numOfPackagesToRead+i] = (this->*readSample)();
 
            samples.data(ci)[i] = (this->*readSample)();
 
        }
 
    }
 

	
 
    addData(channelSamples, numOfPackagesToRead*_numOfChannels);
 

	
 
    delete[] channelSamples;
 
    feedOut(samples);
 
}
 

	
 
template<typename T> double BinaryStreamReader::readSampleAs()
 
{
 
    T data;
 

	
 
    _device->read((char*) &data, sizeof(data));
 

	
 
    if (_settingsWidget.endianness() == LittleEndian)
 
    {
 
        data = qFromLittleEndian(data);
 
    }
src/binarystreamreader.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -25,51 +25,45 @@
 
#include "abstractreader.h"
 
#include "binarystreamreadersettings.h"
 

	
 
/**
 
 * Reads a simple stream of samples in binary form from the
 
 * device. There is no means of synchronization other than a button
 
 * that should be manually triggered by user.
 
 */
 
class BinaryStreamReader : public AbstractReader
 
{
 
    Q_OBJECT
 
public:
 
    explicit BinaryStreamReader(QIODevice* device, ChannelManager* channelMan,
 
                                DataRecorder* recorder, QObject *parent = 0);
 
    explicit BinaryStreamReader(QIODevice* device, QObject *parent = 0);
 
    QWidget* settingsWidget();
 
    unsigned numOfChannels();
 
    void enable(bool enabled = true);
 
    unsigned numChannels() const;
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    void pause(bool);
 

	
 
private:
 
    BinaryStreamReaderSettings _settingsWidget;
 
    unsigned _numOfChannels;
 
    unsigned _numChannels;
 
    unsigned sampleSize;
 
    bool paused;
 
    bool skipByteRequested;
 
    bool skipSampleRequested;
 

	
 
    /// points to the readSampleAs function for currently selected number format
 
    double (BinaryStreamReader::*readSample)();
 

	
 
    /**
 
     * Reads 1 sample from the device in given format.
 
     *
 
     * @note Device should already have enough bytes present before
 
     * calling this function.
 
     */
 
    template<typename T> double readSampleAs();
 

	
 
private slots:
 
    void onNumberFormatChanged(NumberFormat numberFormat);
 
    void onNumOfChannelsChanged(unsigned value);
 
    void onDataReady();
 
    void onDataReady() override;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -59,69 +59,109 @@ const QColor colors[NUMOF_COLORS] =
 
};
 

	
 
ChannelInfoModel::ChannelInfoModel(unsigned numberOfChannels, QObject* parent) :
 
    QAbstractTableModel(parent)
 
{
 
    _numOfChannels = 0;
 
    setNumOfChannels(numberOfChannels);
 
}
 

	
 
ChannelInfoModel::ChannelInfoModel(const ChannelInfoModel& other) :
 
    ChannelInfoModel(other.rowCount(), other.parent())
 
{
 
    // TODO: why not set (copy) info list directly instead?
 
    for (int i = 0; i < other.rowCount(); i++)
 
    {
 
        setData(index(i, COLUMN_NAME),
 
                other.data(other.index(i, COLUMN_NAME), Qt::EditRole),
 
                Qt::EditRole);
 
        setData(index(i, COLUMN_NAME),
 
                other.data(other.index(i, COLUMN_NAME), Qt::ForegroundRole),
 
                Qt::ForegroundRole);
 

	
 
        setData(index(i, COLUMN_VISIBILITY),
 
                other.data(other.index(i, COLUMN_VISIBILITY), Qt::CheckStateRole),
 
                Qt::CheckStateRole);
 

	
 
        setData(index(i, COLUMN_GAIN),
 
                other.data(other.index(i, COLUMN_GAIN), Qt::CheckStateRole),
 
                Qt::CheckStateRole);
 
        setData(index(i, COLUMN_GAIN),
 
                other.data(other.index(i, COLUMN_GAIN), Qt::EditRole),
 
                Qt::EditRole);
 

	
 
        setData(index(i, COLUMN_OFFSET),
 
                other.data(other.index(i, COLUMN_OFFSET), Qt::CheckStateRole),
 
                Qt::CheckStateRole);
 
        setData(index(i, COLUMN_OFFSET),
 
                other.data(other.index(i, COLUMN_OFFSET), Qt::EditRole),
 
                Qt::EditRole);
 
    }
 
}
 

	
 
ChannelInfoModel::ChannelInfoModel(const QStringList& channelNames) :
 
    ChannelInfoModel(channelNames.length(), NULL)
 
{
 
    for (int i = 0; i < channelNames.length(); i++)
 
    {
 
        setData(index(i, COLUMN_NAME), channelNames[i], Qt::EditRole);
 
    }
 
}
 

	
 
ChannelInfoModel::ChannelInfo::ChannelInfo(unsigned index)
 
{
 
    name = tr("Channel %1").arg(index + 1);
 
    visibility = true;
 
    color = colors[index % NUMOF_COLORS];
 
    gain = 1.0;
 
    offset = 0.0;
 
    gainEn = false;
 
    offsetEn = false;
 
}
 

	
 
QString ChannelInfoModel::name(unsigned i) const
 
{
 
    return infos[i].name;
 
}
 

	
 
QColor ChannelInfoModel::color(unsigned i) const
 
{
 
    return infos[i].color;
 
}
 

	
 
bool ChannelInfoModel::isVisible(unsigned i) const
 
{
 
    return infos[i].visibility;
 
}
 

	
 
bool ChannelInfoModel::gainEn (unsigned i) const
 
{
 
    return infos[i].gainEn;
 
}
 

	
 
double ChannelInfoModel::gain (unsigned i) const
 
{
 
    return infos[i].gain;
 
}
 

	
 
bool ChannelInfoModel::offsetEn (unsigned i) const
 
{
 
    return infos[i].offsetEn;
 
}
 

	
 
double ChannelInfoModel::offset (unsigned i) const
 
{
 
    return infos[i].offset;
 
}
 

	
 
QStringList ChannelInfoModel::channelNames() const
 
{
 
    QStringList r;
 
    for (unsigned ci = 0; ci < _numOfChannels; ci++)
 
    {
 
        r << name(ci);
 
    }
 
    return r;
 
}
 

	
 
int ChannelInfoModel::rowCount(const QModelIndex &parent) const
 
{
 
@@ -134,128 +174,199 @@ int ChannelInfoModel::columnCount(const 
 
}
 

	
 
Qt::ItemFlags ChannelInfoModel::flags(const QModelIndex &index) const
 
{
 
    if (index.column() == COLUMN_NAME)
 
    {
 
        return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
 
    }
 
    else if (index.column() == COLUMN_VISIBILITY)
 
    {
 
        return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
 
    }
 
    else if (index.column() == COLUMN_GAIN || index.column() == COLUMN_OFFSET)
 
    {
 
        return Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
 
    }
 

	
 
    return Qt::NoItemFlags;
 
}
 

	
 
QVariant ChannelInfoModel::data(const QModelIndex &index, int role) const
 
{
 
    // check index
 
    if (index.row() >= (int) _numOfChannels || index.row() < 0)
 
    {
 
        return QVariant();
 
    }
 

	
 
    auto &info = infos[index.row()];
 

	
 
    // get color
 
    if (role == Qt::ForegroundRole)
 
    {
 
        return infos[index.row()].color;
 
        return info.color;
 
    }
 

	
 
    // get name
 
    // name
 
    if (index.column() == COLUMN_NAME)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            return QVariant(infos[index.row()].name);
 
            return QVariant(info.name);
 
        }
 
    } // get visibility
 
    } // visibility
 
    else if (index.column() == COLUMN_VISIBILITY)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            bool visible = infos[index.row()].visibility;
 
            bool visible = info.visibility;
 
            return visible ? Qt::Checked : Qt::Unchecked;
 
        }
 
    } // gain
 
    else if (index.column() == COLUMN_GAIN)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            return info.gainEn ? Qt::Checked : Qt::Unchecked;
 
        }
 
        else if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            return QVariant(info.gain);
 
        }
 
    } // offset
 
    else if (index.column() == COLUMN_OFFSET)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            return info.offsetEn ? Qt::Checked : Qt::Unchecked;
 
        }
 
        else if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            return QVariant(info.offset);
 
        }
 
    }
 

	
 
    return QVariant();
 
}
 

	
 
QVariant ChannelInfoModel::headerData(int section, Qt::Orientation orientation, int role) const
 
{
 
    if (orientation == Qt::Horizontal)
 
    {
 
        if (role == Qt::DisplayRole)
 
        {
 
            if (section == COLUMN_NAME)
 
            {
 
                return tr("Channel");
 
            }
 
            else if (section == COLUMN_VISIBILITY)
 
            {
 
                return tr("Visible");
 
            }
 
            else if (section == COLUMN_GAIN)
 
            {
 
                return tr("Gain");
 
            }
 
            else if (section == COLUMN_OFFSET)
 
            {
 
                return tr("Offset");
 
            }
 
        }
 
    }
 
    else                        // vertical
 
    {
 
        if (section < (int) _numOfChannels && role == Qt::DisplayRole)
 
        {
 
            return QString::number(section + 1);
 
        }
 
    }
 

	
 
    return QVariant();
 
}
 

	
 
bool ChannelInfoModel::setData(const QModelIndex &index, const QVariant &value, int role)
 
{
 
    // check index
 
    if (index.row() >= (int) _numOfChannels || index.row() < 0)
 
    {
 
        return false;
 
    }
 

	
 
    auto &info = infos[index.row()];
 

	
 
    // set color
 
    if (role == Qt::ForegroundRole)
 
    {
 
        infos[index.row()].color = value.value<QColor>();
 
        info.color = value.value<QColor>();
 
        emit dataChanged(index, index, QVector<int>({Qt::ForegroundRole}));
 
        return true;
 
    }
 

	
 
    // set name
 
    bool r = false;
 
    if (index.column() == COLUMN_NAME)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            infos[index.row()].name = value.toString();
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            info.name = value.toString();
 
            r = true;
 
        }
 
    } // set visibility
 
    else if (index.column() == COLUMN_VISIBILITY)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            bool checked = value.toInt() == Qt::Checked;
 
            infos[index.row()].visibility = checked;
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            info.visibility = checked;
 
            r = true;
 
        }
 
    }
 
    else if (index.column() == COLUMN_GAIN)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            info.gain = value.toDouble();
 
            r = true;
 
        }
 
        else if (role == Qt::CheckStateRole)
 
        {
 
            bool checked = value.toInt() == Qt::Checked;
 
            info.gainEn = checked;
 
            if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
 
            r = true;
 
        }
 
    }
 
    else if (index.column() == COLUMN_OFFSET)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            info.offset = value.toDouble();
 
            r = true;
 
        }
 
        else if (role == Qt::CheckStateRole)
 
        {
 
            bool checked = value.toInt() == Qt::Checked;
 
            info.offsetEn = checked;
 
            if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
 
            r = true;
 
        }
 
    }
 

	
 
    // invalid index/role
 
    return false;
 
    if (r)
 
    {
 
        emit dataChanged(index, index, QVector<int>({role}));
 
    }
 

	
 
    return r;
 
}
 

	
 
void ChannelInfoModel::setNumOfChannels(unsigned number)
 
{
 
    if (number == _numOfChannels) return;
 

	
 
    bool isInserting = number > _numOfChannels;
 
    if (isInserting)
 
    {
 
        beginInsertRows(QModelIndex(), _numOfChannels, number-1);
 
    }
 
    else
 
@@ -276,115 +387,168 @@ void ChannelInfoModel::setNumOfChannels(
 
    // make sure newly available channels are visible, we don't
 
    // remember visibility option intentionally so that user doesn't
 
    // get confused
 
    if (number > _numOfChannels)
 
    {
 
        for (unsigned ci = _numOfChannels; ci < number; ci++)
 
        {
 
            infos[ci].visibility = true;
 
        }
 
    }
 

	
 
    _numOfChannels = number;
 
    updateGainOrOffsetEn();
 

	
 
    if (isInserting)
 
    {
 
        endInsertRows();
 
    }
 
    else
 
    {
 
        endRemoveRows();
 
    }
 
}
 

	
 
void ChannelInfoModel::resetInfos()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci] = ChannelInfo(ci);
 
    }
 
    endResetModel();
 
}
 

	
 
// TODO: fix repetitive code, ChannelInfoModel::reset* functions
 
void ChannelInfoModel::resetNames()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        // TODO: do not create a full object every time (applies to other reset methods as well)
 
        infos[ci].name = ChannelInfo(ci).name;
 
    }
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::resetColors()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].color = ChannelInfo(ci).color;
 
    }
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::resetVisibility()
 
void ChannelInfoModel::resetVisibility(bool visible)
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].visibility = visible;
 
    }
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::resetGains()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].visibility = true;
 
        infos[ci].gain = ChannelInfo(ci).gain;
 
        infos[ci].gainEn = ChannelInfo(ci).gainEn;
 
    }
 
    updateGainOrOffsetEn();
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::saveSettings(QSettings* settings)
 
void ChannelInfoModel::resetOffsets()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].offset = ChannelInfo(ci).offset;
 
        infos[ci].offsetEn = ChannelInfo(ci).offsetEn;
 
    }
 
    updateGainOrOffsetEn();
 
    endResetModel();
 
}
 

	
 
bool ChannelInfoModel::gainOrOffsetEn() const
 
{
 
    return _gainOrOffsetEn;
 
}
 

	
 
void ChannelInfoModel::updateGainOrOffsetEn()
 
{
 
    _gainOrOffsetEn = false;
 
    for (unsigned ci = 0; ci < _numOfChannels; ci++)
 
    {
 
        auto& info = infos[ci];
 
        _gainOrOffsetEn |= (info.gainEn || info.offsetEn);
 
    }
 
}
 

	
 
void ChannelInfoModel::saveSettings(QSettings* settings) const
 
{
 
    settings->beginGroup(SettingGroup_Channels);
 
    settings->beginWriteArray(SG_Channels_Channel);
 

	
 
    // save all channel information regardless of current number of channels
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        settings->setArrayIndex(ci);
 
        settings->setValue(SG_Channels_Name, infos[ci].name);
 
        settings->setValue(SG_Channels_Color, infos[ci].color);
 
        settings->setValue(SG_Channels_Visible, infos[ci].visibility);
 
        auto& info = infos[ci];
 
        settings->setValue(SG_Channels_Name, info.name);
 
        settings->setValue(SG_Channels_Color, info.color);
 
        settings->setValue(SG_Channels_Visible, info.visibility);
 
        settings->setValue(SG_Channels_Gain, info.gain);
 
        settings->setValue(SG_Channels_GainEn, info.gainEn);
 
        settings->setValue(SG_Channels_Offset, info.offset);
 
        settings->setValue(SG_Channels_OffsetEn, info.offsetEn);
 
    }
 

	
 
    settings->endArray();
 
    settings->endGroup();
 
}
 

	
 
void ChannelInfoModel::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Channels);
 
    unsigned size = settings->beginReadArray(SG_Channels_Channel);
 

	
 
    for (unsigned ci = 0; ci < size; ci++)
 
    {
 
        settings->setArrayIndex(ci);
 

	
 
        ChannelInfo chanInfo(ci);
 
        chanInfo.name       = settings->value(SG_Channels_Name, chanInfo.name).toString();
 
        chanInfo.color      = settings->value(SG_Channels_Color, chanInfo.color).value<QColor>();
 
        chanInfo.visibility = settings->value(SG_Channels_Visible, true).toBool();
 
        chanInfo.name       = settings->value(SG_Channels_Name     , chanInfo.name).toString();
 
        chanInfo.color      = settings->value(SG_Channels_Color    , chanInfo.color).value<QColor>();
 
        chanInfo.visibility = settings->value(SG_Channels_Visible  , chanInfo.visibility).toBool();
 
        chanInfo.gain       = settings->value(SG_Channels_Gain     , chanInfo.gain).toDouble();
 
        chanInfo.gainEn     = settings->value(SG_Channels_GainEn   , chanInfo.gainEn).toBool();
 
        chanInfo.offset     = settings->value(SG_Channels_Offset   , chanInfo.offset).toDouble();
 
        chanInfo.offsetEn   = settings->value(SG_Channels_OffsetEn , chanInfo.offsetEn).toBool();
 

	
 
        if ((int) ci < infos.size())
 
        {
 
            infos[ci] = chanInfo;
 

	
 
            if (ci < _numOfChannels)
 
            {
 
                auto roles = QVector<int>({
 
                    Qt::DisplayRole, Qt::EditRole, Qt::ForegroundRole, Qt::CheckStateRole});
 
                emit dataChanged(index(ci, 0), index(ci, COLUMN_COUNT-1), roles);
 
            }
 
        }
 
        else
 
        {
 
            infos.append(chanInfo);
 
        }
 
    }
 

	
 
    updateGainOrOffsetEn();
 

	
 
    settings->endArray();
 
    settings->endGroup();
 
}
src/channelinfomodel.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -25,69 +25,93 @@
 
#include <QSettings>
 
#include <QStringList>
 

	
 
class ChannelInfoModel : public QAbstractTableModel
 
{
 
    Q_OBJECT
 

	
 
public:
 
    enum ChannelInfoColumn
 
    {
 
        COLUMN_NAME = 0,
 
        COLUMN_VISIBILITY,
 
        COLUMN_COUNT
 
        COLUMN_GAIN,
 
        COLUMN_OFFSET,
 
        COLUMN_COUNT            // MUST be last
 
    };
 

	
 
    explicit ChannelInfoModel(unsigned numberOfChannels, QObject *parent = 0);
 
    ChannelInfoModel(const ChannelInfoModel& other);
 
    explicit ChannelInfoModel(const QStringList& channelNames);
 

	
 
    QString name     (unsigned i) const;
 
    QColor  color    (unsigned i) const;
 
    bool    isVisible(unsigned i) const;
 
    bool    gainEn   (unsigned i) const;
 
    double  gain     (unsigned i) const;
 
    bool    offsetEn (unsigned i) const;
 
    double  offset   (unsigned i) const;
 
    /// Returns true if any of the channels have gain or offset enabled
 
    bool gainOrOffsetEn() const;
 
    /// Returns a list of channel names
 
    QStringList channelNames() const;
 

	
 
    // implemented from QAbstractItemModel
 
    int           rowCount(const QModelIndex &parent = QModelIndex()) const;
 
    int           columnCount(const QModelIndex &parent = QModelIndex()) const;
 
    QVariant      data(const QModelIndex &index, int role = Qt::DisplayRole) const;
 
    bool          setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
 
    Qt::ItemFlags flags(const QModelIndex &index) const;
 
    QVariant      headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
 

	
 
    void setNumOfChannels(unsigned number);
 
    /// Stores all channel info into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    void saveSettings(QSettings* settings) const;
 
    /// Loads all channel info from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    /// reset all channel info (names, color etc.)
 
    void resetInfos();
 
    /// reset all channel names
 
    void resetNames();
 
    /// reset all channel colors
 
    void resetColors();
 
    /// reset all channel gain values and disables gains
 
    void resetGains();
 
    /// reset all channel offset values and disables offsets
 
    void resetOffsets();
 
    /// reset visibility
 
    void resetVisibility();
 
    void resetVisibility(bool visible);
 

	
 
private:
 
    struct ChannelInfo
 
    {
 
        explicit ChannelInfo(unsigned index);
 

	
 
        QString name;
 
        bool visibility;
 
        QColor color;
 
        double gain, offset;
 
        bool gainEn, offsetEn;
 
    };
 

	
 
    unsigned _numOfChannels;     ///< @note this is not necessarily the length of `infos`
 

	
 
    /**
 
     * Channel info is added here but never removed so that we can
 
     * remember user entered info (names, colors etc.).
 
     */
 
    QList<ChannelInfo> infos;
 

	
 
    /**
 
     * Cache for gain and offset enabled variables of channels. If gain and/or
 
     * offset is not enabled for *any* of the channels this is false otherwise
 
     * true.
 
     */
 
    bool _gainOrOffsetEn;
 

	
 
    /// Updates `_gainOrOffsetEn` by scanning all channel infos.
 
    void updateGainOrOffsetEn();
 
};
 

	
 
#endif // CHANNELINFOMODEL_H
src/channelmanager.cpp
Show inline comments
 
deleted file
src/channelmanager.h
Show inline comments
 
deleted file
src/commandedit.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  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.
 
@@ -53,25 +53,25 @@ QValidator::State HexCommandValidator::v
 
        input.replace(QRegExp("([0-9A-F]{2}(?!$))"), "\\1 ");
 
        if (pos == input.size()-1) pos = input.size();
 
        r = QRegExpValidator::validate(input, pos);
 
    }
 

	
 
    return r;
 
}
 

	
 
CommandEdit::CommandEdit(QWidget *parent) :
 
    QLineEdit(parent)
 
{
 
    hexValidator = new HexCommandValidator(this);
 
    asciiValidator = new QRegExpValidator(QRegExp("[\\x0000-\\x007F]+"));
 
    asciiValidator = new QRegExpValidator(QRegExp("[\\x0000-\\x007F]+"), this);
 
    ascii_mode = true;
 
    setValidator(asciiValidator);
 
}
 

	
 
CommandEdit::~CommandEdit()
 
{
 
    delete hexValidator;
 
}
 

	
 
static QString unEscape(QString str);
 
static QString escape(QString str);
 

	
src/commandwidget.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  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.
 
@@ -94,25 +94,32 @@ void CommandWidget::onSendClicked()
 
void CommandWidget::onASCIIToggled(bool checked)
 
{
 
    ui->leCommand->setMode(checked);
 
}
 

	
 
bool CommandWidget::isASCIIMode()
 
{
 
    return ui->pbASCII->isChecked();
 
}
 

	
 
void CommandWidget::setASCIIMode(bool enabled)
 
{
 
    ui->pbASCII->setChecked(enabled);
 
    if (enabled)
 
    {
 
        ui->pbASCII->setChecked(true);
 
    }
 
    else
 
    {
 
        ui->pbHEX->setChecked(true);
 
    }
 
}
 

	
 
void CommandWidget::setName(QString name)
 
{
 
    ui->leName->setText(name);
 
}
 

	
 
QString CommandWidget::name()
 
{
 
    return ui->leName->text();
 
}
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "dataformatpanel.h"
 
#include "ui_dataformatpanel.h"
 

	
 
#include <QRadioButton>
 
#include <QtEndian>
 
#include <QMap>
 
#include <QtDebug>
 

	
 
#include "utils.h"
 
#include "setting_defines.h"
 
#include "floatswap.h"
 

	
 
DataFormatPanel::DataFormatPanel(QSerialPort* port,
 
                                 ChannelManager* channelMan,
 
                                 DataRecorder* recorder,
 
                                 QWidget *parent) :
 
DataFormatPanel::DataFormatPanel(QSerialPort* port, QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::DataFormatPanel),
 
    bsReader(port, channelMan, recorder, this),
 
    asciiReader(port, channelMan, recorder, this),
 
    framedReader(port, channelMan, recorder, this),
 
    demoReader(port, channelMan, recorder, this)
 
    bsReader(port, this),
 
    asciiReader(port, this),
 
    framedReader(port, this),
 
    demoReader(port, this)
 
{
 
    ui->setupUi(this);
 

	
 
    serialPort = port;
 
    _channelMan = channelMan;
 
    paused = false;
 
    demoEnabled = false;
 
    readerBeforeDemo = nullptr;
 

	
 
    // initalize default reader
 
    currentReader = &bsReader;
 
    bsReader.enable();
 
    ui->rbBinary->setChecked(true);
 
    ui->horizontalLayout->addWidget(bsReader.settingsWidget(), 1);
 
    connect(&bsReader, SIGNAL(numOfChannelsChanged(unsigned)),
 
            this, SIGNAL(numOfChannelsChanged(unsigned)));
 
    connect(&bsReader, SIGNAL(samplesPerSecondChanged(unsigned)),
 
            this, SIGNAL(samplesPerSecondChanged(unsigned)));
 

	
 
    // initalize reader selection buttons
 
    connect(ui->rbBinary, &QRadioButton::toggled, [this](bool checked)
 
            {
 
                if (checked) selectReader(&bsReader);
 
            });
 

	
 
    connect(ui->rbAscii, &QRadioButton::toggled, [this](bool checked)
 
            {
 
                if (checked) selectReader(&asciiReader);
 
            });
 

	
 
    connect(ui->rbFramed, &QRadioButton::toggled, [this](bool checked)
 
            {
 
                if (checked) selectReader(&framedReader);
 
            });
 

	
 
    // re-purpose numofchannels settings from actual reader settings to demo reader
 
    connect(this, &DataFormatPanel::numOfChannelsChanged,
 
            &demoReader, &DemoReader::setNumOfChannels);
 
}
 

	
 
DataFormatPanel::~DataFormatPanel()
 
{
 
    delete ui;
 
}
 

	
 
unsigned DataFormatPanel::numOfChannels()
 
unsigned DataFormatPanel::numChannels() const
 
{
 
    return currentReader->numOfChannels();
 
    return currentReader->numChannels();
 
}
 

	
 
Source* DataFormatPanel::activeSource()
 
{
 
    return currentReader;
 
}
 

	
 
void DataFormatPanel::pause(bool enabled)
 
{
 
    paused = enabled;
 
    currentReader->pause(enabled);
 
    demoReader.pause(enabled);
 
}
 

	
 
void DataFormatPanel::enableDemo(bool enabled)
 
void DataFormatPanel::enableDemo(bool demoEnabled)
 
{
 
    if (enabled)
 
    if (demoEnabled)
 
    {
 
        demoReader.enable();
 
        demoReader.recording = currentReader->recording;
 
        connect(&demoReader, &DemoReader::samplesPerSecondChanged,
 
                this, &DataFormatPanel::samplesPerSecondChanged);
 
        readerBeforeDemo = currentReader;
 
        demoReader.setNumChannels(readerBeforeDemo->numChannels());
 
        selectReader(&demoReader);
 
    }
 
    else
 
    {
 
        demoReader.enable(false);
 
        disconnect(&demoReader, 0, this, 0);
 
        Q_ASSERT(readerBeforeDemo != nullptr);
 
        selectReader(readerBeforeDemo);
 
    }
 
    demoEnabled = enabled;
 

	
 
    // disable/enable reader selection buttons during/after demo
 
    ui->rbAscii->setDisabled(demoEnabled);
 
    ui->rbBinary->setDisabled(demoEnabled);
 
    ui->rbFramed->setDisabled(demoEnabled);
 
}
 

	
 
void DataFormatPanel::startRecording()
 
bool DataFormatPanel::isDemoEnabled() const
 
{
 
    currentReader->recording = true;
 
    if (demoEnabled) demoReader.recording = true;
 
}
 

	
 
void DataFormatPanel::stopRecording()
 
{
 
    currentReader->recording = false;
 
    if (demoEnabled) demoReader.recording = false;
 
    return currentReader == &demoReader;
 
}
 

	
 
void DataFormatPanel::selectReader(AbstractReader* reader)
 
{
 
    currentReader->enable(false);
 
    reader->enable();
 

	
 
    // re-connect signals
 
    disconnect(currentReader, 0, this, 0);
 
    connect(reader, SIGNAL(numOfChannelsChanged(unsigned)),
 
            this, SIGNAL(numOfChannelsChanged(unsigned)));
 
    connect(reader, SIGNAL(samplesPerSecondChanged(unsigned)),
 
            this, SIGNAL(samplesPerSecondChanged(unsigned)));
 

	
 
    // switch the settings widget
 
    ui->horizontalLayout->removeWidget(currentReader->settingsWidget());
 
    currentReader->settingsWidget()->hide();
 
    ui->horizontalLayout->addWidget(reader->settingsWidget(), 1);
 
    reader->settingsWidget()->show();
 

	
 
    // notify if number of channels is different
 
    if (currentReader->numOfChannels() != reader->numOfChannels())
 
    {
 
        emit numOfChannelsChanged(reader->numOfChannels());
 
    }
 

	
 
    reader->pause(paused);
 
    reader->recording = currentReader->recording;
 

	
 
    currentReader = reader;
 
    emit sourceChanged(currentReader);
 
}
 

	
 
void DataFormatPanel::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_DataFormat);
 

	
 
    // save selected format
 
    // save selected data format (current reader)
 
    QString format;
 
    if (currentReader == &bsReader)
 
    AbstractReader* selectedReader = isDemoEnabled() ? readerBeforeDemo : currentReader;
 
    if (selectedReader == &bsReader)
 
    {
 
        format = "binary";
 
    }
 
    else if (currentReader == &asciiReader)
 
    else if (selectedReader == &asciiReader)
 
    {
 
        format = "ascii";
 
    }
 
    else // framed reader
 
    {
 
        format = "custom";
 
    }
 
    settings->setValue(SG_DataFormat_Format, format);
 

	
 
    settings->endGroup();
 

	
 
    // save reader settings
src/dataformatpanel.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

	
 
#ifndef DATAFORMATPANEL_H
 
#define DATAFORMATPANEL_H
 

	
 
#include <QWidget>
 
#include <QButtonGroup>
 
#include <QTimer>
 
#include <QSerialPort>
 
#include <QList>
 
#include <QSettings>
 
#include <QtGlobal>
 

	
 
#include "framebuffer.h"
 
#include "channelmanager.h"
 
#include "binarystreamreader.h"
 
#include "asciireader.h"
 
#include "demoreader.h"
 
#include "framedreader.h"
 
#include "datarecorder.h"
 

	
 
namespace Ui {
 
class DataFormatPanel;
 
}
 

	
 
class DataFormatPanel : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit DataFormatPanel(QSerialPort* port,
 
                             ChannelManager* channelMan,
 
                             DataRecorder* recorder,
 
                             QWidget* parent = 0);
 
    explicit DataFormatPanel(QSerialPort* port, QWidget* parent = 0);
 
    ~DataFormatPanel();
 

	
 
    /// Returns currently selected number of channels
 
    unsigned numOfChannels();
 
    unsigned numChannels() const;
 
    /// Returns active source (reader)
 
    Source* activeSource();
 
    /// Stores data format panel settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads data format panel settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    void pause(bool);
 
    void enableDemo(bool); // demo shouldn't be enabled when port is open
 

	
 
    /**
 
     * @brief Starts sending data to recorder.
 
     *
 
     * @note recorder must have been started!
 
     */
 
    void startRecording();
 

	
 
    /// Stops recording.
 
    void stopRecording();
 

	
 
signals:
 
    void numOfChannelsChanged(unsigned);
 
    void samplesPerSecondChanged(unsigned);
 
    /// Active (selected) reader has changed.
 
    void sourceChanged(Source* source);
 

	
 
private:
 
    Ui::DataFormatPanel *ui;
 

	
 
    QSerialPort* serialPort;
 
    ChannelManager* _channelMan;
 

	
 
    BinaryStreamReader bsReader;
 
    AsciiReader asciiReader;
 
    FramedReader framedReader;
 
    /// Currently selected reader
 
    AbstractReader* currentReader;
 
    /// Disable current reader and enable a another one
 
    void selectReader(AbstractReader* reader);
 

	
 
    bool paused;
 

	
 
    bool demoEnabled;
 
    DemoReader demoReader;
 
    AbstractReader* readerBeforeDemo;
 

	
 
    bool isDemoEnabled() const;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "datarecorder.h"
 

	
 
#include <QFileInfo>
 
#include <QDir>
 
#include <QDateTime>
 
#include <QtDebug>
 

	
 
DataRecorder::DataRecorder(QObject *parent) :
 
    QObject(parent),
 
    fileStream(&file)
 
{
 
    lastNumChannels = 0;
 
    disableBuffering = false;
 
    windowsLE = false;
 
    timestampEn = false;
 
}
 

	
 
bool DataRecorder::startRecording(QString fileName, QString separator, QStringList channelNames)
 
bool DataRecorder::startRecording(QString fileName, QString separator,
 
                                  QStringList channelNames, bool insertTime)
 
{
 
    Q_ASSERT(!file.isOpen());
 
    _sep =  separator;
 
    timestampEn = insertTime;
 

	
 
    // create directory if it doesn't exist
 
    {
 
        QFileInfo fi(fileName);
 
        if (!fi.dir().mkpath("."))
 
        {
 
            qCritical() << "Failed to create directory for: " << fileName;
 
            return false;
 
        }
 
    }
 

	
 
    // open file
 
    file.setFileName(fileName);
 
    if (!file.open(QIODevice::WriteOnly))
 
    {
 
        qCritical() << "Opening file " << fileName
 
                    << " for recording failed with error: " << file.error();
 
        return false;
 
    }
 

	
 
    // write header line
 
    if (!channelNames.isEmpty())
 
    {
 
        if (timestampEn)
 
        {
 
            fileStream << tr("timestamp") << _sep;
 
        }
 
        fileStream << channelNames.join(_sep);
 
        fileStream << le();
 
        lastNumChannels = channelNames.length();
 
    }
 
    return true;
 
}
 

	
 
void DataRecorder::addData(double* data, unsigned length, unsigned numOfChannels)
 
void DataRecorder::feedIn(const SamplePack& data)
 
{
 
    Q_ASSERT(length > 0);
 
    Q_ASSERT(length % numOfChannels == 0);
 
    Q_ASSERT(file.isOpen());    // recorder should be disconnected before stopping recording
 
    Q_ASSERT(!data.hasX());     // NYI
 

	
 
    if (lastNumChannels != 0 && numOfChannels != lastNumChannels)
 
    // check if number of channels has changed during recording and warn
 
    unsigned numChannels = data.numChannels();
 
    if (lastNumChannels != 0 && numChannels != lastNumChannels)
 
    {
 
        qWarning() << "Number of channels changed from " << lastNumChannels
 
                   << " to " << numOfChannels <<
 
                   << " to " << numChannels <<
 
            " during recording, CSV file is corrupted but no data will be lost.";
 
    }
 
    lastNumChannels = numOfChannels;
 
    lastNumChannels = numChannels;
 

	
 
    unsigned numOfSamples = length / numOfChannels; // per channel
 
    for (unsigned int i = 0; i < numOfSamples; i++)
 
    // write data
 
    qint64 timestamp;
 
    if (timestampEn) timestamp = QDateTime::currentMSecsSinceEpoch();
 
    unsigned numSamples = data.numSamples();
 
    for (unsigned int i = 0; i < numSamples; i++)
 
    {
 
        for (unsigned ci = 0; ci < numOfChannels; ci++)
 
        if (timestampEn)
 
        {
 
            fileStream << data[ci * numOfSamples + i];
 
            if (ci != numOfChannels-1) fileStream << _sep;
 
            fileStream << timestamp << _sep;
 
        }
 
        for (unsigned ci = 0; ci < numChannels; ci++)
 
        {
 
            fileStream << data.data(ci)[i];
 
            if (ci != numChannels-1) fileStream << _sep;
 
        }
 
        fileStream << le();
 
    }
 

	
 
    if (disableBuffering) fileStream.flush();
 
}
 

	
 
void DataRecorder::stopRecording()
 
{
 
    Q_ASSERT(file.isOpen());
 

	
 
    file.close();
src/datarecorder.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DATARECORDER_H
 
#define DATARECORDER_H
 

	
 
#include <QObject>
 
#include <QFile>
 
#include <QTextStream>
 

	
 
class DataRecorder : public QObject
 
#include "sink.h"
 

	
 
/**
 
 * Implemented as a `Sink` that writes incoming data to a file. Before
 
 * connecting a `Source` recording must be started with the `startRecording`
 
 * method. Also before calling `stopRecording`, recorder should be disconnected
 
 * from source.
 
 */
 
class DataRecorder : public QObject, public Sink
 
{
 
    Q_OBJECT
 
public:
 
    explicit DataRecorder(QObject *parent = 0);
 

	
 
    /// Disables file buffering
 
    bool disableBuffering;
 

	
 
    /**
 
     * Use CR+LF as line ending. `false` by default.
 
     *
 
     * @note Toggling this variable during a recording will result in
 
     * a corrupted file. Care must be taken at higher (UI) levels.
 
     */
 
    bool windowsLE;
 

	
 
    /**
 
     * @brief Starts recording data to a file in CSV format.
 
     *
 
     * File is opened and header line (names of channels) is written.
 
     * File is opened and header line (names of channels) is written. After
 
     * calling this function recorder should be connected to a `Source`.
 
     *
 
     * @param fileName name of the recording file
 
     * @param separator column separator
 
     * @param channelNames names of the channels for header line, if empty no header line is written
 
     * @param insertTime enable inserting timestamp
 
     * @return false if file operation fails (read only etc.)
 
     */
 
    bool startRecording(QString fileName, QString separator, QStringList channelNames);
 
    bool startRecording(QString fileName, QString separator,
 
                        QStringList channelNames, bool insertTime);
 

	
 
    /**
 
     * @brief Adds data to a channel.
 
     *
 
     * Multiple rows of data can be added at a time. Each channels
 
     * data should be ordered consecutively in the `data` array:
 
     *
 
     * [CH0_SMP0, CH0_SMP1 ... CH0_SMPN, CH1_SMP0, CH1_SMP1, ... , CHN_SMPN]
 
     *
 
     * If `numOfChannels` changes during recording, no data will be
 
     * lost (ie. it will be written to the file) but this will produce
 
     * an invalid CSV file. An error message will be written to the
 
     * console.
 
     *
 
     * @param data samples array
 
     * @param length number of samples in `data`, must be multiple of `numOfChannels`
 
     * @param numOfChannels how many channels samples this data carries
 
     */
 
    void addData(double* data, unsigned length, unsigned numOfChannels);
 

	
 
    /// Stops recording, closes file.
 
    void stopRecording();
 

	
 
protected:
 
    virtual void feedIn(const SamplePack& data);
 

	
 
private:
 
    unsigned lastNumChannels;   ///< used for error message only
 
    QFile file;
 
    QTextStream fileStream;
 
    QString _sep;
 
    bool timestampEn;
 

	
 
    /// Returns the selected line ending.
 
    const char* le() const;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -16,74 +16,82 @@
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <math.h>
 

	
 
#include "demoreader.h"
 

	
 
#ifndef M_PI
 
#define M_PI 3.14159265358979323846
 
#endif
 

	
 
DemoReader::DemoReader(QIODevice* device, ChannelManager* channelMan,
 
                       DataRecorder* recorder, QObject* parent) :
 
    AbstractReader(device, channelMan, recorder, parent)
 
DemoReader::DemoReader(QIODevice* device, QObject* parent) :
 
    AbstractReader(device, parent)
 
{
 
    paused = false;
 
    _numOfChannels = 1;
 
    _numChannels = _settingsWidget.numChannels();
 
    connect(&_settingsWidget, &DemoReaderSettings::numChannelsChanged,
 
            this, &DemoReader::onNumChannelsChanged);
 

	
 
    count = 0;
 
    timer.setInterval(100);
 
    QObject::connect(&timer, &QTimer::timeout,
 
                     this, &DemoReader::demoTimerTimeout);
 
    connect(&timer, &QTimer::timeout,
 
            this, &DemoReader::demoTimerTimeout);
 
}
 

	
 
QWidget* DemoReader::settingsWidget()
 
{
 
    return NULL;
 
    return &_settingsWidget;
 
}
 

	
 
void DemoReader::enable(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        timer.start();
 
    }
 
    else
 
    {
 
        timer.stop();
 
        disconnectSinks();
 
    }
 
}
 

	
 
unsigned DemoReader::numOfChannels()
 
unsigned DemoReader::numChannels() const
 
{
 
    return _numOfChannels;
 
    return _numChannels;
 
}
 

	
 
void DemoReader::setNumOfChannels(unsigned value)
 
void DemoReader::setNumChannels(unsigned value)
 
{
 
    _numOfChannels = value;
 
}
 

	
 
void DemoReader::pause(bool enabled)
 
{
 
    paused = enabled;
 
    _settingsWidget.setNumChannels(value);
 
}
 

	
 
void DemoReader::demoTimerTimeout()
 
{
 
    const double period = 100;
 
    count++;
 
    if (count >= 100) count = 0;
 

	
 
    if (!paused)
 
    {
 
        double* samples = new double[_numOfChannels];
 
        for (unsigned ci = 0; ci < _numOfChannels; ci++)
 
        SamplePack samples(1, _numChannels);
 
        for (unsigned ci = 0; ci < _numChannels; ci++)
 
        {
 
            // we are calculating the fourier components of square wave
 
            samples[ci] = 4*sin(2*M_PI*double((ci+1)*count)/period)/((2*(ci+1))*M_PI);
 
            samples.data(ci)[0] = 4*sin(2*M_PI*double((ci+1)*count)/period)/((2*(ci+1))*M_PI);
 
        }
 
        addData(samples, _numOfChannels);
 
        delete[] samples;
 
        feedOut(samples);
 
    }
 
}
 

	
 
void DemoReader::onNumChannelsChanged(unsigned value)
 
{
 
    _numChannels = value;
 
    updateNumChannels();
 
}
 

	
 
void DemoReader::onDataReady()
 
{
 
    // intentionally empty, required by AbstractReader
 
}
src/demoreader.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DEMOREADER_H
 
#define DEMOREADER_H
 

	
 
#include <QTimer>
 

	
 
#include "abstractreader.h"
 
#include "demoreadersettings.h"
 

	
 
/**
 
 * This is a special case of reader implementation and should be used
 
 * with care.
 
 *
 
 * There is no settings widget. Number of channels should be set from
 
 * currently selected actual readers settings widget.
 
 *
 
 * This reader should not be enabled when port is open!
 
 */
 
class DemoReader : public AbstractReader
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit DemoReader(QIODevice* device, ChannelManager* channelMan,
 
                        DataRecorder* recorder, QObject* parent = 0);
 
    explicit DemoReader(QIODevice* device, QObject* parent = 0);
 

	
 
    /// Demo reader is an exception so this function returns NULL
 
    QWidget* settingsWidget();
 

	
 
    unsigned numOfChannels();
 

	
 
    void enable(bool enabled = true);
 
    unsigned numChannels() const;
 
    void enable(bool enabled = true) override;
 

	
 
public slots:
 
    void pause(bool);
 

	
 
    /// Sets the number of channels, this doesn't trigger a `numOfChannelsChanged` signal.
 
    void setNumOfChannels(unsigned value);
 
    void setNumChannels(unsigned value);
 

	
 
private:
 
    bool paused;
 
    unsigned _numOfChannels;
 
    DemoReaderSettings _settingsWidget;
 

	
 
    unsigned _numChannels;
 
    QTimer timer;
 
    int count;
 

	
 
private slots:
 
    void demoTimerTimeout();
 
    void onNumChannelsChanged(unsigned value);
 
    void onDataReady() override;
 
};
 

	
 
#endif // DEMOREADER_H
src/demoreadersettings.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "demoreadersettings.h"
 
#include "ui_demoreadersettings.h"
 

	
 
#include "utils.h"
 

	
 
DemoReaderSettings::DemoReaderSettings(QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::DemoReaderSettings)
 
{
 
    ui->setupUi(this);
 

	
 
    connect(ui->spNumChannels, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged),
 
            [this](int value)
 
            {
 
                emit numChannelsChanged(value);
 
            });
 
}
 

	
 
DemoReaderSettings::~DemoReaderSettings()
 
{
 
    delete ui;
 
}
 

	
 
unsigned DemoReaderSettings::numChannels() const
 
{
 
    return ui->spNumChannels->value();
 
}
 

	
 
void DemoReaderSettings::setNumChannels(unsigned value)
 
{
 
    ui->spNumChannels->setValue(value);
 
}
src/demoreadersettings.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DEMOREADERSETTINGS_H
 
#define DEMOREADERSETTINGS_H
 

	
 
#include <QWidget>
 

	
 
namespace Ui {
 
class DemoReaderSettings;
 
}
 

	
 
class DemoReaderSettings : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit DemoReaderSettings(QWidget *parent = 0);
 
    ~DemoReaderSettings();
 

	
 
    unsigned numChannels() const;
 
    /// Doesn't signal `numChannelsChanged`.
 
    void setNumChannels(unsigned value);
 

	
 
private:
 
    Ui::DemoReaderSettings *ui;
 

	
 
signals:
 
    void numChannelsChanged(unsigned);
 
};
 

	
 
#endif // DEMOREADERSETTINGS_H
src/demoreadersettings.ui
Show inline comments
 
new file 100644
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>DemoReaderSettings</class>
 
 <widget class="QWidget" name="DemoReaderSettings">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>444</width>
 
    <height>141</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QVBoxLayout" name="verticalLayout">
 
   <item>
 
    <layout class="QFormLayout" name="formLayout">
 
     <property name="fieldGrowthPolicy">
 
      <enum>QFormLayout::ExpandingFieldsGrow</enum>
 
     </property>
 
     <item row="0" column="0">
 
      <widget class="QLabel" name="label_4">
 
       <property name="text">
 
        <string>Number Of Channels:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="0" column="1">
 
      <widget class="QSpinBox" name="spNumChannels">
 
       <property name="minimumSize">
 
        <size>
 
         <width>60</width>
 
         <height>0</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>Select number of channels or set to 0 for Auto (determined from incoming data)</string>
 
       </property>
 
       <property name="specialValueText">
 
        <string/>
 
       </property>
 
       <property name="keyboardTracking">
 
        <bool>false</bool>
 
       </property>
 
       <property name="minimum">
 
        <number>1</number>
 
       </property>
 
       <property name="maximum">
 
        <number>32</number>
 
       </property>
 
       <property name="value">
 
        <number>1</number>
 
       </property>
 
      </widget>
 
     </item>
 
    </layout>
 
   </item>
 
   <item>
 
    <spacer name="verticalSpacer">
 
     <property name="orientation">
 
      <enum>Qt::Vertical</enum>
 
     </property>
 
     <property name="sizeHint" stdset="0">
 
      <size>
 
       <width>20</width>
 
       <height>40</height>
 
      </size>
 
     </property>
 
    </spacer>
 
   </item>
 
   <item>
 
    <widget class="QLabel" name="label">
 
     <property name="text">
 
      <string>Demo is enabled, exit demo for reader settings.</string>
 
     </property>
 
    </widget>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections/>
 
</ui>
src/framebuffer.cpp
Show inline comments
 
deleted file
src/framebuffer.h
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
// IMPORTANT NOTE: this file will be renamed to "framebuffer.h" when
 
// stream work is complete
 

	
 
#ifndef FRAMEBUFFER_H
 
#define FRAMEBUFFER_H
 

	
 
#include <QPointF>
 
#include <QRectF>
 
struct Range
 
{
 
    double start, end;
 
};
 

	
 
/// Abstract base class for all frame buffers.
 
class FrameBuffer
 
{
 
public:
 
    FrameBuffer(size_t size);
 
    ~FrameBuffer();
 

	
 
    void resize(size_t size);
 
    void addSamples(double* samples, size_t size);
 
    void clear(); // fill 0
 
    /// Placeholder virtual destructor
 
    virtual ~FrameBuffer() {};
 
    /// Returns size of the buffer.
 
    virtual unsigned size() const = 0;
 
    /// Returns a sample from given index.
 
    virtual double sample(unsigned i) const = 0;
 
    /// Returns minimum and maximum of the buffer values.
 
    virtual Range limits() const = 0;
 
};
 

	
 
    // QwtSeriesData related implementations
 
    size_t size() const;
 
    QRectF boundingRect() const;
 
    double sample(size_t i) const;
 
/// Common base class for index and writable frame buffers
 
class ResizableBuffer : public FrameBuffer
 
{
 
public:
 
    /// Resize the buffer.
 
    ///
 
    /// @important Resizing to same value is an error.
 
    virtual void resize(unsigned n) = 0;
 
};
 

	
 
private:
 
    size_t _size; // size of `data`
 
    double* data;
 
    size_t headIndex; // indicates the actual `0` index of the ring buffer
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
FrameBufferSeries::FrameBufferSeries(FrameBuffer* buffer)
 
FrameBufferSeries::FrameBufferSeries(const FrameBuffer* buffer)
 
{
 
    xAsIndex = true;
 
    _xmin = 0;
 
    _xmax = 1;
 
    _buffer = buffer;
 
    int_index_start = 0;
 
    int_index_end = _buffer->size();
 
}
 

	
 
void FrameBufferSeries::setXAxis(bool asIndex, double xmin, double xmax)
 
{
 
    xAsIndex = asIndex;
 
    _xmin = xmin;
 
    _xmax = xmax;
 
}
 

	
 
size_t FrameBufferSeries::size() const
 
{
 
    return _buffer->size();
 
    return int_index_end - int_index_start;
 
}
 

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

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

	
 
void FrameBufferSeries::setRectOfInterest(const QRectF& rect)
 
{
 
    if (xAsIndex)
 
    {
 
        int_index_start = floor(rect.left())-1;
 
        int_index_end = ceil(rect.right())+1;
 
    }
 
    else
 
    {
 
        double xsize = _xmax - _xmin;
 
        size_t bsize = _buffer->size();
 
        int_index_start =  floor(bsize * (rect.left()-_xmin) / xsize)-1;
 
        int_index_end = ceil(bsize * (rect.right()-_xmin) / xsize)+1;
 
    }
 

	
 
    int_index_start = std::max(int_index_start, 0);
 
    int_index_end = std::min((int) _buffer->size(), int_index_end);
 
}
src/framebufferseries.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

	
 
#include "framebuffer.h"
 

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

	
 
    /// Behavior of X axis
 
    void setXAxis(bool asIndex, double xmin, double xmax);
 

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtDebug>
 
#include <QtEndian>
 
#include "floatswap.h"
 

	
 
#include "framedreader.h"
 

	
 
FramedReader::FramedReader(QIODevice* device, ChannelManager* channelMan,
 
                           DataRecorder* recorder, QObject* parent) :
 
    AbstractReader(device, channelMan, recorder, parent)
 
FramedReader::FramedReader(QIODevice* device, QObject* parent) :
 
    AbstractReader(device, parent)
 
{
 
    paused = false;
 

	
 
    // initial settings
 
    settingsInvalid = 0;
 
    _numOfChannels = _settingsWidget.numOfChannels();
 
    _numChannels = _settingsWidget.numOfChannels();
 
    hasSizeByte = _settingsWidget.frameSize() == 0;
 
    frameSize = _settingsWidget.frameSize();
 
    syncWord = _settingsWidget.syncWord();
 
    checksumEnabled = _settingsWidget.isChecksumEnabled();
 
    onNumberFormatChanged(_settingsWidget.numberFormat());
 
    debugModeEnabled = _settingsWidget.isDebugModeEnabled();
 
    checkSettings();
 

	
 
    // init setting connections
 
    connect(&_settingsWidget, &FramedReaderSettings::numberFormatChanged,
 
            this, &FramedReader::onNumberFormatChanged);
 

	
 
@@ -54,50 +53,32 @@ FramedReader::FramedReader(QIODevice* de
 
            this, &FramedReader::onFrameSizeChanged);
 

	
 
    connect(&_settingsWidget, &FramedReaderSettings::checksumChanged,
 
            [this](bool enabled){checksumEnabled = enabled; reset();});
 

	
 
    connect(&_settingsWidget, &FramedReaderSettings::debugModeChanged,
 
            [this](bool enabled){debugModeEnabled = enabled;});
 

	
 
    // init reader state
 
    reset();
 
}
 

	
 
void FramedReader::enable(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        connect(_device, &QIODevice::readyRead,
 
                this, &FramedReader::onDataReady);
 
    }
 
    else
 
    {
 
        QObject::disconnect(_device, 0, this, 0);
 
    }
 
}
 

	
 
QWidget* FramedReader::settingsWidget()
 
{
 
    return &_settingsWidget;
 
}
 

	
 
unsigned FramedReader::numOfChannels()
 
unsigned FramedReader::numChannels() const
 
{
 
    return _numOfChannels;
 
}
 

	
 
void FramedReader::pause(bool enabled)
 
{
 
    paused = enabled;
 
    return _numChannels;
 
}
 

	
 
void FramedReader::onNumberFormatChanged(NumberFormat numberFormat)
 
{
 
    switch(numberFormat)
 
    {
 
        case NumberFormat_uint8:
 
            sampleSize = 1;
 
            readSample = &FramedReader::readSampleAs<quint8>;
 
            break;
 
        case NumberFormat_int8:
 
            sampleSize = 1;
 
@@ -136,55 +117,55 @@ void FramedReader::checkSettings()
 
{
 
    // sync word is invalid (empty or missing a nibble at the end)
 
    if (!syncWord.size())
 
    {
 
        settingsInvalid |= SYNCWORD_INVALID;
 
    }
 
    else // sync word is valid
 
    {
 
        settingsInvalid &= ~SYNCWORD_INVALID;
 
    }
 

	
 
    // check if fixed frame size is multiple of a sample set size
 
    if (!hasSizeByte && frameSize % (_numOfChannels * sampleSize) != 0)
 
    if (!hasSizeByte && frameSize % (_numChannels * sampleSize) != 0)
 
    {
 
        settingsInvalid |= FRAMESIZE_INVALID;
 
    }
 
    else
 
    {
 
        settingsInvalid &= ~FRAMESIZE_INVALID;
 
    }
 

	
 
    // show an error message
 
    if (settingsInvalid & SYNCWORD_INVALID)
 
    {
 
        _settingsWidget.showMessage("Sync word is invalid!", true);
 
    }
 
    else if (settingsInvalid & FRAMESIZE_INVALID)
 
    {
 
        QString errorMessage =
 
            QString("Frame size must be multiple of %1 (#channels * sample size)!")\
 
            .arg(_numOfChannels * sampleSize);
 
            .arg(_numChannels * sampleSize);
 

	
 
        _settingsWidget.showMessage(errorMessage, true);
 
    }
 
    else
 
    {
 
        _settingsWidget.showMessage("All is well!");
 
    }
 
}
 

	
 
void FramedReader::onNumOfChannelsChanged(unsigned value)
 
{
 
    _numOfChannels = value;
 
    _numChannels = value;
 
    checkSettings();
 
    reset();
 
    emit numOfChannelsChanged(value);
 
}
 

	
 
void FramedReader::onSyncWordChanged(QByteArray word)
 
{
 
    syncWord = word;
 
    checkSettings();
 
    reset();
 
}
 

	
 
@@ -229,29 +210,29 @@ void FramedReader::onDataReady()
 
            }
 
        }
 
        else if (hasSizeByte && !gotSize) // skipped if fixed frame size
 
        {
 
            frameSize = 0;
 
            _device->getChar((char*) &frameSize);
 

	
 
            if (frameSize == 0) // check size
 
            {
 
                qCritical() << "Frame size is 0!";
 
                reset();
 
            }
 
            else if (frameSize % (_numOfChannels * sampleSize) != 0)
 
            else if (frameSize % (_numChannels * sampleSize) != 0)
 
            {
 
                qCritical() <<
 
                    QString("Frame size is not multiple of %1 (#channels * sample size)!") \
 
                    .arg(_numOfChannels * sampleSize);
 
                    .arg(_numChannels * sampleSize);
 
                reset();
 
            }
 
            else
 
            {
 
                if (debugModeEnabled) qDebug() << "Frame size:" << frameSize;
 
                gotSize = true;
 
            }
 
        }
 
        else // read data bytes
 
        {
 
            // have enough data bytes? (+1 for checksum)
 
            if (bytesAvailable < (checksumEnabled ? frameSize+1 : frameSize))
 
@@ -278,56 +259,53 @@ void FramedReader::reset()
 

	
 
// Important: this function assumes device has enough bytes to read a full frames data and checksum
 
void FramedReader::readFrameDataAndCheck()
 
{
 
    // if paused just read and waste data
 
    if (paused)
 
    {
 
        _device->read((checksumEnabled ? frameSize+1 : frameSize));
 
        return;
 
    }
 

	
 
    // a package is 1 set of samples for all channels
 
    unsigned numOfPackagesToRead = frameSize / (_numOfChannels * sampleSize);
 
    double* channelSamples = new double[numOfPackagesToRead * _numOfChannels];
 

	
 
    unsigned numOfPackagesToRead = frameSize / (_numChannels * sampleSize);
 
    SamplePack samples(numOfPackagesToRead, _numChannels);
 
    for (unsigned i = 0; i < numOfPackagesToRead; i++)
 
    {
 
        for (unsigned int ci = 0; ci < _numOfChannels; ci++)
 
        for (unsigned int ci = 0; ci < _numChannels; ci++)
 
        {
 
            channelSamples[ci*numOfPackagesToRead+i] = (this->*readSample)();
 
            samples.data(ci)[i] = (this->*readSample)();
 
        }
 
    }
 

	
 
    // read checksum
 
    unsigned rChecksum = 0;
 
    bool checksumPassed = false;
 
    if (checksumEnabled)
 
    {
 
        _device->read((char*) &rChecksum, 1);
 
        calcChecksum &= 0xFF;
 
        checksumPassed = (calcChecksum == rChecksum);
 
    }
 

	
 
    if (!checksumEnabled || checksumPassed)
 
    {
 
        // commit data
 
        addData(channelSamples, numOfPackagesToRead*_numOfChannels);
 
        feedOut(samples);
 
    }
 
    else
 
    {
 
        qCritical() << "Checksum failed! Received:" << rChecksum << "Calculated:" << calcChecksum;
 
    }
 

	
 
    delete[] channelSamples;
 
}
 

	
 
template<typename T> double FramedReader::readSampleAs()
 
{
 
    T data;
 

	
 
    _device->read((char*) &data, sizeof(data));
 

	
 
    if (checksumEnabled)
 
    {
 
        for (unsigned i = 0; i < sizeof(data); i++)
 
        {
src/framedreader.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

	
 
#include "abstractreader.h"
 
#include "framedreadersettings.h"
 

	
 
/**
 
 * Reads data in a customizable framed format.
 
 */
 
class FramedReader : public AbstractReader
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit FramedReader(QIODevice* device, ChannelManager* channelMan,
 
                          DataRecorder* recorder, QObject *parent = 0);
 
    explicit FramedReader(QIODevice* device, QObject *parent = 0);
 
    QWidget* settingsWidget();
 
    unsigned numOfChannels();
 
    void enable(bool enabled = true);
 
    unsigned numChannels() const;
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    void pause(bool);
 

	
 
private:
 
    /// bit wise fields for `settingsValid` member
 
    enum SettingInvalidFlag
 
    {
 
        SYNCWORD_INVALID = 1,
 
        FRAMESIZE_INVALID = 2
 
    };
 

	
 
    // settings related members
 
    FramedReaderSettings _settingsWidget;
 
    unsigned _numOfChannels;
 
    unsigned _numChannels;
 
    unsigned sampleSize;
 
    bool paused;
 
    unsigned settingsInvalid;   /// settings are all valid if this is 0, if not no reading is done
 
    QByteArray syncWord;
 
    bool checksumEnabled;
 
    bool hasSizeByte;
 
    unsigned frameSize;
 
    bool debugModeEnabled;
 

	
 
    /// Checks the validity of syncWord and frameSize then shows an
 
    /// error message. Also updates `settingsInvalid`. If settings are
 
    /// valid `settingsInvalid` should be `0`.
 
    void checkSettings();
 

	
 
@@ -77,21 +71,21 @@ private:
 
    bool gotSize;    /// indicates if size is captured, ignored if size byte is disabled (fixed size)
 
    unsigned calcChecksum;
 

	
 
    void reset();    /// Resets the reading state. Used in case of error or setting change.
 
    /// points to the readSampleAs function for currently selected number format
 
    double (FramedReader::*readSample)();
 
    template<typename T> double readSampleAs();
 
    /// reads payload portion of the frame, calculates checksum and commits data
 
    /// @note should be called only if there are enough bytes on device
 
    void readFrameDataAndCheck();
 

	
 
private slots:
 
    void onDataReady();
 
    void onDataReady() override;
 

	
 
    void onNumberFormatChanged(NumberFormat numberFormat);
 
    void onNumOfChannelsChanged(unsigned value);
 
    void onSyncWordChanged(QByteArray);
 
    void onFrameSizeChanged(unsigned);
 
};
 

	
 
#endif // FRAMEDREADER_H
src/indexbuffer.cpp
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 

	
 
#include "indexbuffer.h"
 

	
 
IndexBuffer::IndexBuffer(unsigned n)
 
{
 
    _size = n;
 
}
 

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

	
 
void IndexBuffer::resize(unsigned n)
 
{
 
    _size = n;
 
}
 

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

	
 
    return i;
 
}
 

	
 
Range IndexBuffer::limits() const
 
{
 
    return Range{0, _size-1.};
 
}
src/indexbuffer.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef INDEXBUFFER_H
 
#define INDEXBUFFER_H
 

	
 
#include "framebuffer.h"
 

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

	
 
    unsigned size() const;
 
    double sample(unsigned i) const;
 
    Range limits() const;
 
    void resize(unsigned n);
 

	
 
private:
 
    unsigned _size;
 
};
 

	
 
#endif
src/linindexbuffer.cpp
Show inline comments
 
new file 100644
 
 /*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 

	
 
#include "linindexbuffer.h"
 

	
 
LinIndexBuffer::LinIndexBuffer(unsigned n, Range lim)
 
{
 
    Q_ASSERT(n > 0);
 

	
 
    _size = n;
 
    setLimits(lim);
 
}
 

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

	
 
double LinIndexBuffer::sample(unsigned i) const
 
{
 
    return _limits.start + i * _step;
 
}
 

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

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

	
 
void LinIndexBuffer::setLimits(Range lim)
 
{
 
    _limits = lim;
 
    _step = (lim.end - lim.start) / (_size-1);
 
}
src/linindexbuffer.h
Show inline comments
 
new file 100644
 
 /*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef LININDEXBUFFER_H
 
#define LININDEXBUFFER_H
 

	
 
#include "framebuffer.h"
 

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

	
 
    unsigned size() const;
 
    double sample(unsigned i) const;
 
    Range limits() const;
 
    void resize(unsigned n);
 
    /// Sets minimum and maximum sample values of the buffer.
 
    void setLimits(Range lim);
 

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

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

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -26,101 +26,125 @@
 
#include <QFile>
 
#include <QTextStream>
 
#include <QMenu>
 
#include <QDesktopServices>
 
#include <QMap>
 
#include <QtDebug>
 
#include <qwt_plot.h>
 
#include <limits.h>
 
#include <cmath>
 
#include <iostream>
 

	
 
#include <plot.h>
 
#include <barplot.h>
 

	
 
#include "framebufferseries.h"
 
#include "utils.h"
 
#include "defines.h"
 
#include "version.h"
 
#include "setting_defines.h"
 

	
 
#if defined(Q_OS_WIN) && defined(QT_STATIC)
 
#include <QtPlugin>
 
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
 
#endif
 

	
 
// TODO: depends on tab insertion order, a better solution would be to use object names
 
const QMap<int, QString> panelSettingMap({
 
        {0, "Port"},
 
        {1, "DataFormat"},
 
        {2, "Plot"},
 
        {3, "Commands"}
 
        {3, "Commands"},
 
        {4, "Record"},
 
        {5, "Log"}
 
    });
 

	
 
MainWindow::MainWindow(QWidget *parent) :
 
    QMainWindow(parent),
 
    ui(new Ui::MainWindow),
 
    aboutDialog(this),
 
    portControl(&serialPort),
 
    channelMan(1, 1, this),
 
    snapshotMan(this, &channelMan),
 
    secondaryPlot(NULL),
 
    snapshotMan(this, &stream),
 
    commandPanel(&serialPort),
 
    dataFormatPanel(&serialPort, &channelMan, &recorder),
 
    recordPanel(&recorder, &channelMan)
 
    dataFormatPanel(&serialPort),
 
    recordPanel(&stream),
 
    updateCheckDialog(this)
 
{
 
    ui->setupUi(this);
 

	
 
    plotMan = new PlotManager(ui->plotArea, channelMan.infoModel());
 
    plotMan = new PlotManager(ui->plotArea, &plotMenu, &stream);
 

	
 
    ui->tabWidget->insertTab(0, &portControl, "Port");
 
    ui->tabWidget->insertTab(1, &dataFormatPanel, "Data Format");
 
    ui->tabWidget->insertTab(2, &plotControlPanel, "Plot");
 
    ui->tabWidget->insertTab(3, &commandPanel, "Commands");
 
    ui->tabWidget->insertTab(4, &recordPanel, "Record");
 
    ui->tabWidget->setCurrentIndex(0);
 
    auto tbPortControl = portControl.toolBar();
 
    addToolBar(tbPortControl);
 
    addToolBar(recordPanel.toolbar());
 

	
 
    ui->plotToolBar->addAction(snapshotMan.takeSnapshotAction());
 
    ui->menuBar->insertMenu(ui->menuHelp->menuAction(), snapshotMan.menu());
 
    ui->menuBar->insertMenu(ui->menuHelp->menuAction(), commandPanel.menu());
 
    menuBar()->insertMenu(ui->menuHelp->menuAction(), snapshotMan.menu());
 
    menuBar()->insertMenu(ui->menuHelp->menuAction(), commandPanel.menu());
 

	
 
    connect(&commandPanel, &CommandPanel::focusRequested, [this]()
 
            {
 
                this->ui->tabWidget->setCurrentWidget(&commandPanel);
 
                this->ui->tabWidget->showTabs();
 
            });
 

	
 
    tbPortControl->setObjectName("tbPortControl");
 
    ui->plotToolBar->setObjectName("tbPlot");
 

	
 
    setupAboutDialog();
 

	
 
    // init view menu
 
    for (auto a : plotMan->menuActions())
 
    {
 
        ui->menuView->addAction(a);
 
    }
 

	
 
    ui->menuView->addSeparator();
 

	
 
    QMenu* tbMenu = ui->menuView->addMenu("Toolbars");
 
    ui->menuBar->insertMenu(ui->menuSecondary->menuAction(), &plotMenu);
 
    plotMenu.addSeparator();
 
    QMenu* tbMenu = plotMenu.addMenu("Toolbars");
 
    tbMenu->addAction(ui->plotToolBar->toggleViewAction());
 
    tbMenu->addAction(portControl.toolBar()->toggleViewAction());
 

	
 
    // init secondary plot menu
 
    auto group = new QActionGroup(this);
 
    group->addAction(ui->actionVertical);
 
    group->addAction(ui->actionHorizontal);
 

	
 
    // init UI signals
 

	
 
    // Secondary plot menu signals
 
    connect(ui->actionBarPlot, &QAction::triggered,
 
            this, &MainWindow::showBarPlot);
 

	
 
    connect(ui->actionVertical, &QAction::triggered,
 
            [this](bool checked)
 
            {
 
                if (checked) ui->splitter->setOrientation(Qt::Vertical);
 
            });
 

	
 
    connect(ui->actionHorizontal, &QAction::triggered,
 
            [this](bool checked)
 
            {
 
                if (checked) ui->splitter->setOrientation(Qt::Horizontal);
 
            });
 

	
 
    // Help menu signals
 
    QObject::connect(ui->actionHelpAbout, &QAction::triggered,
 
              &aboutDialog, &QWidget::show);
 

	
 
    QObject::connect(ui->actionCheckUpdate, &QAction::triggered,
 
              &updateCheckDialog, &QWidget::show);
 

	
 
    QObject::connect(ui->actionReportBug, &QAction::triggered,
 
                     [](){QDesktopServices::openUrl(QUrl(BUG_REPORT_URL));});
 

	
 
    // File menu signals
 
    QObject::connect(ui->actionExportCsv, &QAction::triggered,
 
                     this, &MainWindow::onExportCsv);
 

	
 
    QObject::connect(ui->actionSaveSettings, &QAction::triggered,
 
                     this, &MainWindow::onSaveSettings);
 

	
 
    QObject::connect(ui->actionLoadSettings, &QAction::triggered,
 
                     this, &MainWindow::onLoadSettings);
 
@@ -138,46 +162,36 @@ MainWindow::MainWindow(QWidget *parent) 
 
    connect(&plotControlPanel, &PlotControlPanel::numOfSamplesChanged,
 
            this, &MainWindow::onNumOfSamplesChanged);
 

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

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

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

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

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

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

	
 
    // init port signals
 
    QObject::connect(&(this->serialPort), SIGNAL(error(QSerialPort::SerialPortError)),
 
                     this, SLOT(onPortError(QSerialPort::SerialPortError)));
 

	
 
    // init data format and reader
 
    QObject::connect(&channelMan, &ChannelManager::dataAdded,
 
                     plotMan, &PlotManager::replot);
 

	
 
    QObject::connect(ui->actionPause, &QAction::triggered,
 
                     &channelMan, &ChannelManager::pause);
 

	
 
    QObject::connect(&recordPanel, &RecordPanel::recordStarted,
 
                     &dataFormatPanel, &DataFormatPanel::startRecording);
 

	
 
    QObject::connect(&recordPanel, &RecordPanel::recordStopped,
 
                     &dataFormatPanel, &DataFormatPanel::stopRecording);
 
                     &stream, &Stream::pause);
 

	
 
    QObject::connect(ui->actionPause, &QAction::triggered,
 
                     [this](bool enabled)
 
                     {
 
                         if (enabled && !recordPanel.recordPaused())
 
                         {
 
                             dataFormatPanel.pause(true);
 
                         }
 
                         else
 
                         {
 
                             dataFormatPanel.pause(false);
 
                         }
 
@@ -186,67 +200,56 @@ MainWindow::MainWindow(QWidget *parent) 
 
    QObject::connect(&recordPanel, &RecordPanel::recordPausedChanged,
 
                     [this](bool enabled)
 
                     {
 
                         if (ui->actionPause->isChecked() && enabled)
 
                         {
 
                             dataFormatPanel.pause(false);
 
                         }
 
                     });
 

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

	
 
    // init data arrays and plot
 
    // init plot
 
    numOfSamples = plotControlPanel.numOfSamples();
 
    unsigned numOfChannels = dataFormatPanel.numOfChannels();
 

	
 
    channelMan.setNumOfSamples(numOfSamples);
 
    channelMan.setNumOfChannels(dataFormatPanel.numOfChannels());
 

	
 
    connect(&dataFormatPanel, &DataFormatPanel::numOfChannelsChanged,
 
            &channelMan, &ChannelManager::setNumOfChannels);
 

	
 
    connect(&channelMan, &ChannelManager::numOfChannelsChanged,
 
            this, &MainWindow::onNumOfChannelsChanged);
 

	
 
    plotControlPanel.setChannelInfoModel(channelMan.infoModel());
 

	
 
    // init curve list
 
    for (unsigned int i = 0; i < numOfChannels; i++)
 
    {
 
        plotMan->addCurve(channelMan.channelName(i), channelMan.channelBuffer(i));
 
    }
 
    stream.setNumSamples(numOfSamples);
 
    plotControlPanel.setChannelInfoModel(stream.infoModel());
 

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

	
 
    // Init sps (sample per second) counter
 
    spsLabel.setText("0sps");
 
    spsLabel.setToolTip("samples per second (per channel)");
 
    ui->statusBar->addPermanentWidget(&spsLabel);
 
    QObject::connect(&dataFormatPanel,
 
                     &DataFormatPanel::samplesPerSecondChanged,
 
                     this, &MainWindow::onSpsChanged);
 
    connect(&sampleCounter, &SampleCounter::spsChanged,
 
            this, &MainWindow::onSpsChanged);
 

	
 
    // init demo
 
    QObject::connect(ui->actionDemoMode, &QAction::toggled,
 
                     this, &MainWindow::enableDemo);
 

	
 
    QObject::connect(ui->actionDemoMode, &QAction::toggled,
 
                     plotMan, &PlotManager::showDemoIndicator);
 

	
 
    // init stream connections
 
    connect(&dataFormatPanel, &DataFormatPanel::sourceChanged,
 
            this, &MainWindow::onSourceChanged);
 
    onSourceChanged(dataFormatPanel.activeSource());
 

	
 
    // load default settings
 
    QSettings settings("serialplot", "serialplot");
 
    loadAllSettings(&settings);
 

	
 
    // ensure command panel has 1 command if none loaded
 
    if (!commandPanel.numOfCommands())
 
    {
 
        commandPanel.newCommandAction()->trigger();
 
    }
 

	
 
    // Important: This should be after newCommandAction is triggered
 
    // (above) we don't want user to be greeted with command panel on
 
@@ -270,26 +273,25 @@ MainWindow::~MainWindow()
 
    delete ui;
 
    ui = NULL; // we check if ui is deleted in messageHandler
 
}
 

	
 
void MainWindow::closeEvent(QCloseEvent * event)
 
{
 
    // save snapshots
 
    if (!snapshotMan.isAllSaved())
 
    {
 
        auto clickedButton = QMessageBox::warning(
 
            this, "Closing SerialPlot",
 
            "There are un-saved snapshots. If you close you will loose the data.",
 
            QMessageBox::Discard | QMessageBox::Discard,
 
            QMessageBox::Cancel);
 
            QMessageBox::Discard, QMessageBox::Cancel);
 
        if (clickedButton == QMessageBox::Cancel)
 
        {
 
            event->ignore();
 
            return;
 
        }
 
    }
 

	
 
    // save settings
 
    QSettings settings("serialplot", "serialplot");
 
    saveAllSettings(&settings);
 
    settings.sync();
 

	
 
@@ -337,122 +339,47 @@ void MainWindow::setupAboutDialog()
 
    aboutText.replace("$VERSION_STRING$", VERSION_STRING);
 
    aboutText.replace("$VERSION_REVISION$", VERSION_REVISION);
 
    uiAboutDialog.lbAbout->setText(aboutText);
 
}
 

	
 
void MainWindow::onPortToggled(bool open)
 
{
 
    // make sure demo mode is disabled
 
    if (open && isDemoRunning()) enableDemo(false);
 
    ui->actionDemoMode->setEnabled(!open);
 
}
 

	
 
void MainWindow::onPortError(QSerialPort::SerialPortError error)
 
void MainWindow::onSourceChanged(Source* source)
 
{
 
    switch(error)
 
    {
 
        case QSerialPort::NoError :
 
            break;
 
        case QSerialPort::ResourceError :
 
            qWarning() << "Port error: resource unavaliable; most likely device removed.";
 
            if (serialPort.isOpen())
 
            {
 
                qWarning() << "Closing port on resource error: " << serialPort.portName();
 
                portControl.togglePort();
 
            }
 
            portControl.loadPortList();
 
            break;
 
        case QSerialPort::DeviceNotFoundError:
 
            qCritical() << "Device doesn't exists: " << serialPort.portName();
 
            break;
 
        case QSerialPort::PermissionError:
 
            qCritical() << "Permission denied. Either you don't have \
 
required privileges or device is already opened by another process.";
 
            break;
 
        case QSerialPort::OpenError:
 
            qWarning() << "Device is already opened!";
 
            break;
 
        case QSerialPort::NotOpenError:
 
            qCritical() << "Device is not open!";
 
            break;
 
        case QSerialPort::ParityError:
 
            qCritical() << "Parity error detected.";
 
            break;
 
        case QSerialPort::FramingError:
 
            qCritical() << "Framing error detected.";
 
            break;
 
        case QSerialPort::BreakConditionError:
 
            qCritical() << "Break condition is detected.";
 
            break;
 
        case QSerialPort::WriteError:
 
            qCritical() << "An error occurred while writing data.";
 
            break;
 
        case QSerialPort::ReadError:
 
            qCritical() << "An error occurred while reading data.";
 
            break;
 
        case QSerialPort::UnsupportedOperationError:
 
            qCritical() << "Operation is not supported.";
 
            break;
 
        case QSerialPort::TimeoutError:
 
            qCritical() << "A timeout error occurred.";
 
            break;
 
        case QSerialPort::UnknownError:
 
            qCritical() << "Unknown error! Error: " << serialPort.errorString();
 
            break;
 
        default:
 
            qCritical() << "Unhandled port error: " << error;
 
            break;
 
    }
 
    source->connectSink(&stream);
 
    source->connectSink(&sampleCounter);
 
}
 

	
 
void MainWindow::clearPlot()
 
{
 
    for (unsigned ci = 0; ci < channelMan.numOfChannels(); ci++)
 
    {
 
        channelMan.channelBuffer(ci)->clear();
 
    }
 
    stream.clear();
 
    plotMan->replot();
 
}
 

	
 
void MainWindow::onNumOfSamplesChanged(int value)
 
{
 
    numOfSamples = value;
 
    channelMan.setNumOfSamples(value);
 
    stream.setNumSamples(value);
 
    plotMan->replot();
 
}
 

	
 
void MainWindow::onNumOfChannelsChanged(unsigned value)
 
void MainWindow::onSpsChanged(float sps)
 
{
 
    unsigned int oldNum = plotMan->numOfCurves();
 
    unsigned numOfChannels = value;
 

	
 
    if (numOfChannels > oldNum)
 
    {
 
        // add new channels
 
        for (unsigned int i = oldNum; i < numOfChannels; i++)
 
        {
 
            plotMan->addCurve(channelMan.channelName(i), channelMan.channelBuffer(i));
 
        }
 
    }
 
    else if(numOfChannels < oldNum)
 
    {
 
        plotMan->removeCurves(oldNum - numOfChannels);
 
    }
 

	
 
    plotMan->replot();
 
}
 

	
 
void MainWindow::onSpsChanged(unsigned sps)
 
{
 
    spsLabel.setText(QString::number(sps) + "sps");
 
    int precision = sps < 1. ? 3 : 0;
 
    spsLabel.setText(QString::number(sps, 'f', precision) + "sps");
 
}
 

	
 
bool MainWindow::isDemoRunning()
 
{
 
    return ui->actionDemoMode->isChecked();
 
}
 

	
 
void MainWindow::enableDemo(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        if (!serialPort.isOpen())
 
@@ -462,46 +389,88 @@ void MainWindow::enableDemo(bool enabled
 
        else
 
        {
 
            ui->actionDemoMode->setChecked(false);
 
        }
 
    }
 
    else
 
    {
 
        dataFormatPanel.enableDemo(false);
 
        ui->actionDemoMode->setChecked(false);
 
    }
 
}
 

	
 
void MainWindow::showSecondary(QWidget* wid)
 
{
 
    if (secondaryPlot != NULL)
 
    {
 
        secondaryPlot->deleteLater();
 
    }
 

	
 
    secondaryPlot = wid;
 
    ui->splitter->addWidget(wid);
 
    ui->splitter->setStretchFactor(0, 1);
 
    ui->splitter->setStretchFactor(1, 0);
 
}
 

	
 
void MainWindow::hideSecondary()
 
{
 
    if (secondaryPlot == NULL)
 
    {
 
        qFatal("Secondary plot doesn't exist!");
 
    }
 

	
 
    secondaryPlot->deleteLater();
 
    secondaryPlot = NULL;
 
}
 

	
 
void MainWindow::showBarPlot(bool show)
 
{
 
    if (show)
 
    {
 
        auto plot = new BarPlot(&stream, &plotMenu);
 
        plot->setYAxis(plotControlPanel.autoScale(),
 
                       plotControlPanel.yMin(),
 
                       plotControlPanel.yMax());
 
        connect(&plotControlPanel, &PlotControlPanel::yScaleChanged,
 
                plot, &BarPlot::setYAxis);
 
        showSecondary(plot);
 
    }
 
    else
 
    {
 
        hideSecondary();
 
    }
 
}
 

	
 
void MainWindow::onExportCsv()
 
{
 
    bool wasPaused = ui->actionPause->isChecked();
 
    ui->actionPause->setChecked(true); // pause plotting
 

	
 
    QString fileName = QFileDialog::getSaveFileName(this, tr("Export CSV File"));
 

	
 
    if (fileName.isNull())  // user canceled export
 
    {
 
        ui->actionPause->setChecked(wasPaused);
 
    }
 
    else
 
    {
 
        Snapshot* snapshot = snapshotMan.makeSnapshot();
 
        snapshot->save(fileName);
 
        delete snapshot;
 
    }
 
}
 

	
 
PlotViewSettings MainWindow::viewSettings() const
 
{
 
    return plotMan->viewSettings();
 
    return plotMenu.viewSettings();
 
}
 

	
 
void MainWindow::messageHandler(QtMsgType type,
 
                                const QMessageLogContext &context,
 
                                const QString &msg)
 
{
 
    QString logString;
 

	
 
    switch (type)
 
    {
 
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
 
        case QtInfoMsg:
 
@@ -532,41 +501,43 @@ void MainWindow::messageHandler(QtMsgTyp
 

	
 
    if (type == QtFatalMsg)
 
    {
 
        __builtin_trap();
 
    }
 
}
 

	
 
void MainWindow::saveAllSettings(QSettings* settings)
 
{
 
    saveMWSettings(settings);
 
    portControl.saveSettings(settings);
 
    dataFormatPanel.saveSettings(settings);
 
    channelMan.saveSettings(settings);
 
    stream.saveSettings(settings);
 
    plotControlPanel.saveSettings(settings);
 
    plotMan->saveSettings(settings);
 
    plotMenu.saveSettings(settings);
 
    commandPanel.saveSettings(settings);
 
    recordPanel.saveSettings(settings);
 
    updateCheckDialog.saveSettings(settings);
 
}
 

	
 
void MainWindow::loadAllSettings(QSettings* settings)
 
{
 
    loadMWSettings(settings);
 
    portControl.loadSettings(settings);
 
    dataFormatPanel.loadSettings(settings);
 
    channelMan.loadSettings(settings);
 
    stream.loadSettings(settings);
 
    plotControlPanel.loadSettings(settings);
 
    plotMan->loadSettings(settings);
 
    plotMenu.loadSettings(settings);
 
    commandPanel.loadSettings(settings);
 
    recordPanel.loadSettings(settings);
 
    updateCheckDialog.loadSettings(settings);
 
}
 

	
 
void MainWindow::saveMWSettings(QSettings* settings)
 
{
 
    // save window geometry
 
    settings->beginGroup(SettingGroup_MainWindow);
 
    settings->setValue(SG_MainWindow_Size, size());
 
    settings->setValue(SG_MainWindow_Pos, pos());
 
    // save active panel
 
    settings->setValue(SG_MainWindow_ActivePanel,
 
                       panelSettingMap.value(ui->tabWidget->currentIndex()));
 
    // save panel minimization
src/mainwindow.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -31,29 +31,30 @@
 
#include <QTimer>
 
#include <QColor>
 
#include <QtGlobal>
 
#include <QSettings>
 
#include <qwt_plot_curve.h>
 

	
 
#include "portcontrol.h"
 
#include "commandpanel.h"
 
#include "dataformatpanel.h"
 
#include "plotcontrolpanel.h"
 
#include "recordpanel.h"
 
#include "ui_about_dialog.h"
 
#include "framebuffer.h"
 
#include "channelmanager.h"
 
#include "stream.h"
 
#include "snapshotmanager.h"
 
#include "plotmanager.h"
 
#include "datarecorder.h"
 
#include "plotmenu.h"
 
#include "updatecheckdialog.h"
 
#include "samplecounter.h"
 

	
 
namespace Ui {
 
class MainWindow;
 
}
 

	
 
class MainWindow : public QMainWindow
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit MainWindow(QWidget *parent = 0);
 
    ~MainWindow();
 
@@ -66,52 +67,62 @@ public:
 
private:
 
    Ui::MainWindow *ui;
 

	
 
    QDialog aboutDialog;
 
    void setupAboutDialog();
 

	
 
    QSerialPort serialPort;
 
    PortControl portControl;
 

	
 
    unsigned int numOfSamples;
 

	
 
    QList<QwtPlotCurve*> curves;
 
    ChannelManager channelMan;
 
    // ChannelManager channelMan;
 
    Stream stream;
 
    PlotManager* plotMan;
 
    QWidget* secondaryPlot;
 
    SnapshotManager snapshotMan;
 
    DataRecorder recorder;       // operated by `recordPanel`
 
    SampleCounter sampleCounter;
 

	
 
    QLabel spsLabel;
 
    CommandPanel commandPanel;
 
    DataFormatPanel dataFormatPanel;
 
    RecordPanel recordPanel;
 
    PlotControlPanel plotControlPanel;
 
    PlotMenu plotMenu;
 
    UpdateCheckDialog updateCheckDialog;
 

	
 
    /// Returns true if demo is running
 
    bool isDemoRunning();
 
    /// Display a secondary plot in the splitter, removing and
 
    /// deleting previous one if it exists
 
    void showSecondary(QWidget* wid);
 
    /// Hide secondary plot
 
    void hideSecondary();
 
    /// Stores settings for all modules
 
    void saveAllSettings(QSettings* settings);
 
    /// Load settings for all modules
 
    void loadAllSettings(QSettings* settings);
 
    /// Stores main window settings into a `QSettings`
 
    void saveMWSettings(QSettings* settings);
 
    /// Loads main window settings from a `QSettings`
 
    void loadMWSettings(QSettings* settings);
 

	
 
    /// `QWidget::closeEvent` handler
 
    void closeEvent(QCloseEvent * event);
 

	
 
private slots:
 
    void onPortToggled(bool open);
 
    void onPortError(QSerialPort::SerialPortError error);
 

	
 
    void onSourceChanged(Source* source);
 
    void onNumOfSamplesChanged(int value);
 
    void onNumOfChannelsChanged(unsigned value);
 

	
 
    void clearPlot();
 
    void onSpsChanged(unsigned sps);
 
    void onSpsChanged(float sps);
 
    void enableDemo(bool enabled);
 
    void showBarPlot(bool show);
 

	
 
    void onExportCsv();
 
    void onSaveSettings();
 
    void onLoadSettings();
 
};
 

	
 
#endif // MAINWINDOW_H
src/mainwindow.ui
Show inline comments
 
@@ -5,33 +5,44 @@
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>653</width>
 
    <height>650</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>SerialPlot</string>
 
  </property>
 
  <widget class="QWidget" name="centralWidget">
 
   <layout class="QVBoxLayout" name="verticalLayout_2">
 
   <layout class="QVBoxLayout" name="verticalLayout">
 
    <item>
 
     <widget class="QWidget" name="plotArea" native="true">
 
     <widget class="QSplitter" name="splitter">
 
      <property name="sizePolicy">
 
       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
 
       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
 
        <horstretch>0</horstretch>
 
        <verstretch>0</verstretch>
 
       </sizepolicy>
 
      </property>
 
      <property name="orientation">
 
       <enum>Qt::Horizontal</enum>
 
      </property>
 
      <widget class="QWidget" name="plotArea" native="true">
 
       <property name="sizePolicy">
 
        <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
 
         <horstretch>0</horstretch>
 
         <verstretch>0</verstretch>
 
        </sizepolicy>
 
       </property>
 
      </widget>
 
     </widget>
 
    </item>
 
    <item>
 
     <widget class="HidableTabWidget" name="tabWidget">
 
      <property name="sizePolicy">
 
       <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
 
        <horstretch>0</horstretch>
 
        <verstretch>0</verstretch>
 
       </sizepolicy>
 
      </property>
 
      <property name="minimumSize">
 
       <size>
 
@@ -81,91 +92,98 @@
 
       </layout>
 
      </widget>
 
     </widget>
 
    </item>
 
   </layout>
 
  </widget>
 
  <widget class="QMenuBar" name="menuBar">
 
   <property name="geometry">
 
    <rect>
 
     <x>0</x>
 
     <y>0</y>
 
     <width>653</width>
 
     <height>27</height>
 
     <height>24</height>
 
    </rect>
 
   </property>
 
   <widget class="QMenu" name="menuHelp">
 
    <property name="title">
 
     <string>&amp;Help</string>
 
    </property>
 
    <addaction name="actionDemoMode"/>
 
    <addaction name="actionReportBug"/>
 
    <addaction name="actionCheckUpdate"/>
 
    <addaction name="actionHelpAbout"/>
 
   </widget>
 
   <widget class="QMenu" name="menuFile">
 
    <property name="title">
 
     <string>&amp;File</string>
 
    </property>
 
    <addaction name="actionSaveSettings"/>
 
    <addaction name="actionLoadSettings"/>
 
    <addaction name="actionExportCsv"/>
 
    <addaction name="separator"/>
 
    <addaction name="actionQuit"/>
 
   </widget>
 
   <widget class="QMenu" name="menuView">
 
   <widget class="QMenu" name="menuSecondary">
 
    <property name="title">
 
     <string>&amp;View</string>
 
     <string>Secondary</string>
 
    </property>
 
    <addaction name="actionBarPlot"/>
 
    <addaction name="separator"/>
 
    <addaction name="actionHorizontal"/>
 
    <addaction name="actionVertical"/>
 
   </widget>
 
   <addaction name="menuFile"/>
 
   <addaction name="menuView"/>
 
   <addaction name="menuSecondary"/>
 
   <addaction name="menuHelp"/>
 
  </widget>
 
  <widget class="QToolBar" name="plotToolBar">
 
   <property name="windowTitle">
 
    <string>Plot Toolbar</string>
 
   </property>
 
   <attribute name="toolBarArea">
 
    <enum>TopToolBarArea</enum>
 
   </attribute>
 
   <attribute name="toolBarBreak">
 
    <bool>false</bool>
 
   </attribute>
 
   <addaction name="actionPause"/>
 
   <addaction name="actionClear"/>
 
  </widget>
 
  <widget class="QStatusBar" name="statusBar"/>
 
  <action name="actionPause">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="checked">
 
    <bool>false</bool>
 
   </property>
 
   <property name="icon">
 
    <iconset theme="player_pause"/>
 
    <iconset theme="player_pause">
 
     <normaloff>.</normaloff>.</iconset>
 
   </property>
 
   <property name="text">
 
    <string>Pause</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Pause Plotting</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>P</string>
 
   </property>
 
  </action>
 
  <action name="actionClear">
 
   <property name="icon">
 
    <iconset theme="editclear"/>
 
    <iconset theme="editclear">
 
     <normaloff>.</normaloff>.</iconset>
 
   </property>
 
   <property name="text">
 
    <string>Clear</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>Ctrl+K</string>
 
   </property>
 
  </action>
 
  <action name="actionHelpAbout">
 
   <property name="text">
 
    <string>&amp;About</string>
 
   </property>
 
@@ -212,24 +230,56 @@
 
   <property name="toolTip">
 
    <string>Save Settings to a File</string>
 
   </property>
 
  </action>
 
  <action name="actionLoadSettings">
 
   <property name="text">
 
    <string>&amp;Load Settings</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Load Settings from a File</string>
 
   </property>
 
  </action>
 
  <action name="actionCheckUpdate">
 
   <property name="text">
 
    <string>&amp;Check Update</string>
 
   </property>
 
  </action>
 
  <action name="actionBarPlot">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="text">
 
    <string>Bar Plot</string>
 
   </property>
 
  </action>
 
  <action name="actionVertical">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="text">
 
    <string>Vertical</string>
 
   </property>
 
  </action>
 
  <action name="actionHorizontal">
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="checked">
 
    <bool>true</bool>
 
   </property>
 
   <property name="text">
 
    <string>Horizontal</string>
 
   </property>
 
  </action>
 
 </widget>
 
 <layoutdefault spacing="6" margin="11"/>
 
 <customwidgets>
 
  <customwidget>
 
   <class>HidableTabWidget</class>
 
   <extends>QTabWidget</extends>
 
   <header>hidabletabwidget.h</header>
 
   <container>1</container>
 
  </customwidget>
 
 </customwidgets>
 
 <resources/>
 
 <connections/>
src/plot.cpp
Show inline comments
 
@@ -30,24 +30,25 @@
 

	
 
static const int SYMBOL_SHOW_AT_WIDTH = 5;
 
static const int SYMBOL_SIZE_MAX = 7;
 

	
 
Plot::Plot(QWidget* parent) :
 
    QwtPlot(parent),
 
    zoomer(this->canvas(), false),
 
    sZoomer(this, &zoomer)
 
{
 
    isAutoScaled = true;
 
    symbolSize = 0;
 
    numOfSamples = 1;
 
    plotWidth = 1;
 
    showSymbols = Plot::ShowSymbolsAuto;
 

	
 
    QObject::connect(&zoomer, &Zoomer::unzoomed, this, &Plot::unzoomed);
 

	
 
    zoomer.setZoomBase();
 
    grid.attach(this);
 
    legend.attach(this);
 

	
 
    showGrid(false);
 
    darkBackground(false);
 

	
 
    snapshotOverlay = NULL;
 
@@ -64,24 +65,34 @@ Plot::Plot(QWidget* parent) :
 
                if (symbolSize) updateSymbols();
 
            });
 

	
 
    // init demo indicator
 
    QwtText demoText(" DEMO RUNNING ");  // looks better with spaces
 
    demoText.setColor(QColor("white"));
 
    demoText.setBackgroundBrush(Qt::darkRed);
 
    demoText.setBorderRadius(4);
 
    demoText.setRenderFlags(Qt::AlignLeft | Qt::AlignBottom);
 
    demoIndicator.setText(demoText);
 
    demoIndicator.hide();
 
    demoIndicator.attach(this);
 

	
 
    // init no channels are visible indicator
 
    QwtText noChannelText(" No Visible Channels ");
 
    noChannelText.setColor(QColor("white"));
 
    noChannelText.setBackgroundBrush(Qt::darkBlue);
 
    noChannelText.setBorderRadius(4);
 
    noChannelText.setRenderFlags(Qt::AlignHCenter | Qt::AlignVCenter);
 
    noChannelIndicator.setText(noChannelText);
 
    noChannelIndicator.hide();
 
    noChannelIndicator.attach(this);
 
}
 

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

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

	
 
    if (!autoScaled)
 
@@ -90,35 +101,36 @@ void Plot::setYAxis(bool autoScaled, dou
 
        yMax = yAxisMax;
 
    }
 

	
 
    zoomer.zoom(0);
 
    resetAxes();
 
}
 

	
 
void Plot::setXAxis(double xMin, double xMax)
 
{
 
    _xMin = xMin;
 
    _xMax = xMax;
 

	
 
    zoomer.setXLimits(xMin, xMax);
 
    zoomer.zoom(0); // unzoom
 

	
 
    // set axis
 
    setAxisScale(QwtPlot::xBottom, xMin, xMax);
 
    // setAxisScale(QwtPlot::xBottom, xMin, xMax);
 
    replot(); // Note: if we don't replot here scale at startup isn't set correctly
 

	
 
    // reset zoom base
 
    auto base = zoomer.zoomBase();
 
    base.setLeft(xMin);
 
    base.setRight(xMax);
 
    zoomer.setZoomBase(base);
 
    // auto base = zoomer.zoomBase();
 
    // base.setLeft(xMin);
 
    // base.setRight(xMax);
 
    // zoomer.setZoomBase(base);
 

	
 
    onXScaleChanged();
 
}
 

	
 
void Plot::resetAxes()
 
{
 
    // reset y axis
 
    if (isAutoScaled)
 
    {
 
        setAxisAutoScale(QwtPlot::yLeft);
 
    }
 
    else
 
@@ -154,24 +166,30 @@ void Plot::showMinorGrid(bool show)
 
void Plot::showLegend(bool show)
 
{
 
    legend.setVisible(show);
 
    replot();
 
}
 

	
 
void Plot::showDemoIndicator(bool show)
 
{
 
    demoIndicator.setVisible(show);
 
    replot();
 
}
 

	
 
void Plot::showNoChannel(bool show)
 
{
 
    noChannelIndicator.setVisible(show);
 
    replot();
 
}
 

	
 
void Plot::unzoom()
 
{
 
    zoomer.zoom(0);
 
}
 

	
 
void Plot::darkBackground(bool enabled)
 
{
 
    QColor gridColor;
 
    if (enabled)
 
    {
 
        setCanvasBackground(QBrush(Qt::black));
 
        gridColor.setHsvF(0, 0, 0.25);
 
@@ -186,54 +204,24 @@ void Plot::darkBackground(bool enabled)
 
        setCanvasBackground(QBrush(Qt::white));
 
        gridColor.setHsvF(0, 0, 0.80);
 
        grid.setPen(gridColor);
 
        zoomer.setRubberBandPen(QPen(Qt::black));
 
        zoomer.setTrackerPen(QPen(Qt::black));
 
        sZoomer.setPickerPen(QPen(Qt::black));
 
        legend.setTextPen(QPen(Qt::black));
 
    }
 
    updateSymbols();
 
    replot();
 
}
 

	
 
/*
 
  Below crude drawing demostrates how color selection occurs for
 
  given channel index
 

	
 
  0°                     <--Hue Value-->                           360°
 
  |* . o . + . o . * . o . + . o . * . o . + . o . * . o . + . o . |
 

	
 
  * -> 0-3
 
  + -> 4-7
 
  o -> 8-15
 
  . -> 16-31
 

	
 
 */
 
QColor Plot::makeColor(unsigned int channelIndex)
 
{
 
    auto i = channelIndex;
 

	
 
    if (i < 4)
 
    {
 
        return QColor::fromHsv(360*i/4, 255, 230);
 
    }
 
    else
 
    {
 
        double p = floor(log2(i));
 
        double n = pow(2, p);
 
        i = i - n;
 
        return QColor::fromHsv(360*i/n + 360/pow(2,p+1), 255, 230);
 
    }
 
}
 

	
 
void Plot::flashSnapshotOverlay(bool light)
 
{
 
    if (snapshotOverlay != NULL) delete snapshotOverlay;
 

	
 
    QColor color;
 
    if(light)
 
    {
 
        color = QColor(Qt::white);
 
    }
 
    else
 
    {
 
        color = QColor(Qt::black);
 
@@ -276,25 +264,26 @@ void Plot::onXScaleChanged()
 
        calcSymbolSize();
 
        updateSymbols();
 
    }
 
}
 

	
 
void Plot::calcSymbolSize()
 
{
 
    auto sw = axisWidget(QwtPlot::xBottom);
 
    auto paintDist = sw->scaleDraw()->scaleMap().pDist();
 
    auto scaleDist = sw->scaleDraw()->scaleMap().sDist();
 
    auto fullScaleDist = zoomer.zoomBase().width();
 
    auto zoomRate = fullScaleDist / scaleDist;
 
    float samplesInView = numOfSamples / zoomRate;
 
    float plotWidthNumSamp = abs(numOfSamples * plotWidth / (_xMax - _xMin));
 
    float samplesInView = plotWidthNumSamp / zoomRate;
 
    int symDisPx = round(paintDist / samplesInView);
 

	
 
    if (symDisPx < SYMBOL_SHOW_AT_WIDTH)
 
    {
 
        symbolSize = 0;
 
    }
 
    else
 
    {
 
        symbolSize = std::min(SYMBOL_SIZE_MAX, symDisPx-SYMBOL_SHOW_AT_WIDTH+1);
 
    }
 
}
 

	
 
@@ -322,12 +311,18 @@ void Plot::updateSymbols()
 

	
 
void Plot::resizeEvent(QResizeEvent * event)
 
{
 
    QwtPlot::resizeEvent(event);
 
    onXScaleChanged();
 
}
 

	
 
void Plot::setNumOfSamples(unsigned value)
 
{
 
    numOfSamples = value;
 
    onXScaleChanged();
 
}
 

	
 
void Plot::setPlotWidth(double width)
 
{
 
    plotWidth = width;
 
    zoomer.setHViewSize(width);
 
}
src/plot.h
Show inline comments
 
@@ -41,62 +41,65 @@ class Plot : public QwtPlot
 

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

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

	
 
    static QColor makeColor(unsigned int channelIndex);
 

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

	
 
    /**
 
     * Displays an animation for snapshot.
 
     *
 
     * @param light show a light colored (white) animation or the opposite
 
     */
 
    void flashSnapshotOverlay(bool light);
 

	
 
    void setNumOfSamples(unsigned value);
 

	
 
    void setPlotWidth(double width);
 

	
 
protected:
 
    /// update the display of symbols depending on `symbolSize`
 
    void updateSymbols();
 

	
 
private:
 
    bool isAutoScaled;
 
    double yMin, yMax;
 
    double _xMin, _xMax;
 
    unsigned numOfSamples;
 
    double plotWidth;
 
    int symbolSize;
 
    Zoomer zoomer;
 
    ScaleZoomer sZoomer;
 
    QwtPlotGrid grid;
 
    PlotSnapshotOverlay* snapshotOverlay;
 
    QwtPlotLegendItem legend;
 
    QwtPlotTextLabel demoIndicator;
 
    QwtPlotTextLabel noChannelIndicator;
 
    ShowSymbols showSymbols;
 

	
 
    void resetAxes();
 
    void resizeEvent(QResizeEvent * event);
 
    void calcSymbolSize();
 

	
 
private slots:
 
    void unzoomed();
 
    void onXScaleChanged();
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QVariant>
 
#include <QMessageBox>
 
#include <QCheckBox>
 
#include <QStyledItemDelegate>
 

	
 
#include <math.h>
 

	
 
#include "color_selector.hpp"
 
#include "plotcontrolpanel.h"
 
#include "ui_plotcontrolpanel.h"
 
#include "setting_defines.h"
 

	
 
/// Confirm if #samples is being set to a value greater than this
 
const int NUMSAMPLES_CONFIRM_AT = 10000;
 
const int NUMSAMPLES_CONFIRM_AT = 1000000;
 
/// Precision used for channel info table numbers
 
const int DOUBLESP_PRECISION = 6;
 

	
 
/// Used for scale range selection combobox
 
struct Range
 
{
 
    double rmin;
 
    double rmax;
 
};
 

	
 
Q_DECLARE_METATYPE(Range);
 

	
 
/// Used for customizing double precision in tables
 
class SpinBoxDelegate : public QStyledItemDelegate
 
{
 
public:
 
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
 
                          const QModelIndex &index) const Q_DECL_OVERRIDE
 
        {
 
            auto w = QStyledItemDelegate::createEditor(
 
                parent, option, index);
 

	
 
            auto sp = qobject_cast<QDoubleSpinBox*>(w);
 
            if (sp)
 
            {
 
                sp->setDecimals(DOUBLESP_PRECISION);
 
            }
 
            return w;
 
        }
 
};
 

	
 
PlotControlPanel::PlotControlPanel(QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::PlotControlPanel),
 
    resetAct(tr("Reset"), this),
 
    resetNamesAct(tr("Reset Names"), this),
 
    resetColorsAct(tr("Reset Colors"), this),
 
    showAllAct(tr("Show All"), this),
 
    hideAllAct(tr("Hide All"), this),
 
    resetGainsAct(tr("Reset All Gain"), this),
 
    resetOffsetsAct(tr("Reset All Offset"), this),
 
    resetMenu(tr("Reset Menu"), this)
 
{
 
    ui->setupUi(this);
 

	
 
    delegate = new SpinBoxDelegate();
 
    ui->tvChannelInfo->setItemDelegate(delegate);
 

	
 
    warnNumOfSamples = true;    // TODO: load from settings
 
    _numOfSamples = ui->spNumOfSamples->value();
 

	
 
    // set limits for axis limit boxes
 
    ui->spYmin->setRange((-1) * std::numeric_limits<double>::max(),
 
                         std::numeric_limits<double>::max());
 

	
 
    ui->spYmax->setRange((-1) * std::numeric_limits<double>::max(),
 
                         std::numeric_limits<double>::max());
 

	
 
    ui->spXmin->setRange((-1) * std::numeric_limits<double>::max(),
 
                         std::numeric_limits<double>::max());
 
@@ -77,27 +105,46 @@ PlotControlPanel::PlotControlPanel(QWidg
 
    connect(ui->spYmax, SIGNAL(valueChanged(double)),
 
            this, SLOT(onYScaleChanged()));
 

	
 
    connect(ui->spYmin, SIGNAL(valueChanged(double)),
 
            this, SLOT(onYScaleChanged()));
 

	
 
    connect(ui->cbIndex, &QCheckBox::toggled,
 
            this, &PlotControlPanel::onIndexChecked);
 

	
 
    connect(ui->spXmax, SIGNAL(valueChanged(double)),
 
            this, SLOT(onXScaleChanged()));
 

	
 
    connect(ui->spXmax, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
 
            [this](double v)
 
            {
 
                // set limit just a little below
 
                double step = pow(10, -1 * ui->spXmin->decimals());
 
                ui->spXmin->setMaximum(v - step);
 
            });
 

	
 
    connect(ui->spXmin, SIGNAL(valueChanged(double)),
 
            this, SLOT(onXScaleChanged()));
 

	
 
    connect(ui->spXmin, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
 
            [this](double v)
 
            {
 
                // set limit just a little above
 
                double step = pow(10, -1 * ui->spXmax->decimals());
 
                ui->spXmax->setMinimum(v + step);
 
            });
 

	
 
    connect(ui->spPlotWidth, SIGNAL(valueChanged(int)),
 
            this, SLOT(onPlotWidthChanged()));
 

	
 
    // init scale range preset list
 
    for (int nbits = 8; nbits <= 24; nbits++) // signed binary formats
 
    {
 
        int rmax = pow(2, nbits-1)-1;
 
        int rmin = -rmax-1;
 
        Range r = {double(rmin),  double(rmax)};
 
        ui->cbRangePresets->addItem(
 
            QString().sprintf("Signed %d bits %d to +%d", nbits, rmin, rmax),
 
            QVariant::fromValue(r));
 
    }
 
    for (int nbits = 8; nbits <= 24; nbits++) // unsigned binary formats
 
    {
 
@@ -110,30 +157,37 @@ PlotControlPanel::PlotControlPanel(QWidg
 
    ui->cbRangePresets->addItem("0 to +1", QVariant::fromValue(Range{0, +1}));
 
    ui->cbRangePresets->addItem("-100 to +100", QVariant::fromValue(Range{-100, +100}));
 
    ui->cbRangePresets->addItem("0 to +100", QVariant::fromValue(Range{0, +100}));
 

	
 
    QObject::connect(ui->cbRangePresets, SIGNAL(activated(int)),
 
                     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);
 

	
 
    // reset button
 
    // reset buttons
 
    resetAct.setToolTip(tr("Reset channel names and colors"));
 
    resetMenu.addAction(&resetNamesAct);
 
    resetMenu.addAction(&resetColorsAct);
 
    resetMenu.addAction(&showAllAct);
 
    resetMenu.addAction(&resetGainsAct);
 
    resetMenu.addAction(&resetOffsetsAct);
 
    resetAct.setMenu(&resetMenu);
 
    ui->tbReset->setDefaultAction(&resetAct);
 

	
 
    showAllAct.setToolTip(tr("Show all channels"));
 
    hideAllAct.setToolTip(tr("Hide all channels"));
 
    ui->tbShowAll->setDefaultAction(&showAllAct);
 
    ui->tbHideAll->setDefaultAction(&hideAllAct);
 
}
 

	
 
PlotControlPanel::~PlotControlPanel()
 
{
 
    delete ui;
 
}
 

	
 
unsigned PlotControlPanel::numOfSamples()
 
{
 
    return ui->spNumOfSamples->value();
 
}
 

	
 
@@ -266,34 +320,55 @@ void PlotControlPanel::onIndexChecked(bo
 

	
 
        emit xScaleChanged(true); // use index
 
    }
 
    else
 
    {
 
        ui->lXmin->setEnabled(true);
 
        ui->lXmax->setEnabled(true);
 
        ui->spXmin->setEnabled(true);
 
        ui->spXmax->setEnabled(true);
 

	
 
        emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
 
    }
 
    emit plotWidthChanged(plotWidth());
 
}
 

	
 
void PlotControlPanel::onXScaleChanged()
 
{
 
    if (!xAxisAsIndex())
 
    {
 
        emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
 
        emit plotWidthChanged(plotWidth());
 
    }
 
}
 

	
 
double PlotControlPanel::plotWidth() const
 
{
 
    double value = ui->spPlotWidth->value();
 
    if (!xAxisAsIndex())
 
    {
 
        // scale by xmin and xmax
 
        auto xmax = ui->spXmax->value();
 
        auto xmin = ui->spXmin->value();
 
        double scale = (xmax - xmin) / _numOfSamples;
 
        value *= scale;
 
    }
 
    return value;
 
}
 

	
 
void PlotControlPanel::onPlotWidthChanged()
 
{
 
    emit plotWidthChanged(plotWidth());
 
}
 

	
 
void PlotControlPanel::setChannelInfoModel(ChannelInfoModel* model)
 
{
 
    ui->tvChannelInfo->setModel(model);
 

	
 
    // channel color selector
 
    connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::currentRowChanged,
 
            [this](const QModelIndex &current, const QModelIndex &previous)
 
            {
 
                // TODO: duplicate with below lambda
 
                QColor color(0,0,0,0); // transparent
 

	
 
                if (current.isValid())
 
@@ -346,43 +421,49 @@ void PlotControlPanel::setChannelInfoMod
 
                QColor color = mod->data(current, Qt::ForegroundRole).value<QColor>();
 

	
 
                // temporarily block signals because `setColor` emits `colorChanged`
 
                bool wasBlocked = ui->colorSelector->blockSignals(true);
 
                ui->colorSelector->setColor(color);
 
                ui->colorSelector->blockSignals(wasBlocked);
 
            });
 

	
 
    // reset actions
 
    connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos);
 
    connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames);
 
    connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors);
 
    connect(&showAllAct, &QAction::triggered, model, &ChannelInfoModel::resetVisibility);
 
    connect(&resetGainsAct, &QAction::triggered, model, &ChannelInfoModel::resetGains);
 
    connect(&resetOffsetsAct, &QAction::triggered, model, &ChannelInfoModel::resetOffsets);
 
    connect(&showAllAct, &QAction::triggered, [model]{model->resetVisibility(true);});
 
    connect(&hideAllAct, &QAction::triggered, [model]{model->resetVisibility(false);});
 
}
 

	
 
void PlotControlPanel::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    settings->setValue(SG_Plot_NumOfSamples, numOfSamples());
 
    settings->setValue(SG_Plot_PlotWidth, ui->spPlotWidth->value());
 
    settings->setValue(SG_Plot_IndexAsX, xAxisAsIndex());
 
    settings->setValue(SG_Plot_XMax, xMax());
 
    settings->setValue(SG_Plot_XMin, xMin());
 
    settings->setValue(SG_Plot_AutoScale, autoScale());
 
    settings->setValue(SG_Plot_YMax, yMax());
 
    settings->setValue(SG_Plot_YMin, yMin());
 
    settings->endGroup();
 
}
 

	
 
void PlotControlPanel::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    ui->spNumOfSamples->setValue(
 
        settings->value(SG_Plot_NumOfSamples, numOfSamples()).toInt());
 
    ui->spPlotWidth->setValue(
 
        settings->value(SG_Plot_PlotWidth, ui->spPlotWidth->value()).toInt());
 
    ui->cbIndex->setChecked(
 
        settings->value(SG_Plot_IndexAsX, xAxisAsIndex()).toBool());
 
    ui->spXmax->setValue(settings->value(SG_Plot_XMax, xMax()).toDouble());
 
    ui->spXmin->setValue(settings->value(SG_Plot_XMin, xMin()).toDouble());
 
    ui->cbAutoScale->setChecked(
 
        settings->value(SG_Plot_AutoScale, autoScale()).toBool());
 
    ui->spYmax->setValue(settings->value(SG_Plot_YMax, yMax()).toDouble());
 
    ui->spYmin->setValue(settings->value(SG_Plot_YMin, yMin()).toDouble());
 
    settings->endGroup();
 
}
src/plotcontrolpanel.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef PLOTCONTROLPANEL_H
 
#define PLOTCONTROLPANEL_H
 

	
 
#include <QWidget>
 
#include <QSettings>
 
#include <QAction>
 
#include <QMenu>
 
#include <QStyledItemDelegate>
 

	
 
#include "channelinfomodel.h"
 

	
 
namespace Ui {
 
class PlotControlPanel;
 
}
 

	
 
class PlotControlPanel : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit PlotControlPanel(QWidget *parent = 0);
 
    ~PlotControlPanel();
 

	
 
    unsigned numOfSamples();
 
    bool   autoScale() const;
 
    double yMax() const;
 
    double yMin() const;
 
    bool   xAxisAsIndex() const;
 
    double xMax() const;
 
    double xMin() const;
 
    /// Returns the plot width adjusted for x axis scaling.
 
    double plotWidth() const;
 

	
 
    void setChannelInfoModel(ChannelInfoModel* model);
 

	
 
    /// Stores plot settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads plot settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
signals:
 
    void numOfSamplesChanged(int value);
 
    void yScaleChanged(bool autoScaled, double yMin = 0, double yMax = 1);
 
    void xScaleChanged(bool asIndex, double xMin = 0, double xMax = 1);
 
    void plotWidthChanged(double width);
 

	
 
private:
 
    Ui::PlotControlPanel *ui;
 

	
 
    /// Holds 'number of samples' after the confirmation
 
    unsigned _numOfSamples;
 
    /// User can disable this setting in the checkbox
 
    bool warnNumOfSamples;
 

	
 
    QAction resetAct, resetNamesAct, resetColorsAct, showAllAct;
 
    QAction resetAct, resetNamesAct, resetColorsAct, showAllAct,
 
        hideAllAct, resetGainsAct, resetOffsetsAct;
 
    QMenu resetMenu;
 
    QStyledItemDelegate* delegate;
 

	
 
    /// Show a confirmation dialog before setting #samples to a big value
 
    bool askNSConfirmation(int value);
 

	
 
private slots:
 
    void onNumOfSamples(int value);
 
    void onAutoScaleChecked(bool checked);
 
    void onYScaleChanged();
 
    void onRangeSelected();
 
    void onIndexChecked(bool checked);
 
    void onXScaleChanged();
 
    void onPlotWidthChanged();
 
};
 

	
 
#endif // PLOTCONTROLPANEL_H
src/plotcontrolpanel.ui
Show inline comments
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>PlotControlPanel</class>
 
 <widget class="QWidget" name="PlotControlPanel">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>706</width>
 
    <height>187</height>
 
    <width>704</width>
 
    <height>195</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QHBoxLayout" name="horizontalLayout">
 
   <item>
 
    <widget class="QWidget" name="widget" native="true">
 
     <property name="sizePolicy">
 
      <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
 
      <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
 
       <horstretch>0</horstretch>
 
       <verstretch>0</verstretch>
 
      </sizepolicy>
 
     </property>
 
     <layout class="QVBoxLayout" name="verticalLayout_2">
 
      <property name="spacing">
 
       <number>3</number>
 
      </property>
 
      <property name="leftMargin">
 
       <number>0</number>
 
      </property>
 
      <property name="topMargin">
 
       <number>0</number>
 
      </property>
 
      <property name="rightMargin">
 
       <number>0</number>
 
      </property>
 
      <property name="bottomMargin">
 
       <number>0</number>
 
      </property>
 
      <item>
 
       <widget class="QTableView" name="tvChannelInfo">
 
        <property name="sizePolicy">
 
         <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
 
         <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
 
          <horstretch>0</horstretch>
 
          <verstretch>0</verstretch>
 
         </sizepolicy>
 
        </property>
 
        <property name="maximumSize">
 
         <size>
 
          <width>300</width>
 
          <width>30000</width>
 
          <height>170</height>
 
         </size>
 
        </property>
 
        <property name="selectionMode">
 
         <enum>QAbstractItemView::SingleSelection</enum>
 
        </property>
 
        <property name="selectionBehavior">
 
         <enum>QAbstractItemView::SelectRows</enum>
 
        </property>
 
       </widget>
 
      </item>
 
      <item>
 
       <layout class="QHBoxLayout" name="horizontalLayout_2">
 
        <property name="spacing">
 
         <number>3</number>
 
        </property>
 
        <property name="sizeConstraint">
 
         <enum>QLayout::SetMaximumSize</enum>
 
        </property>
 
        <item>
 
         <widget class="color_widgets::ColorSelector" name="colorSelector" native="true">
 
          <property name="sizePolicy">
 
           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
 
            <horstretch>0</horstretch>
 
            <verstretch>0</verstretch>
 
           </sizepolicy>
 
          </property>
 
          <property name="minimumSize">
 
@@ -86,30 +89,50 @@
 
           </size>
 
          </property>
 
         </widget>
 
        </item>
 
        <item>
 
         <spacer name="horizontalSpacer">
 
          <property name="orientation">
 
           <enum>Qt::Horizontal</enum>
 
          </property>
 
          <property name="sizeHint" stdset="0">
 
           <size>
 
            <width>1</width>
 
            <height>20</height>
 
            <height>1</height>
 
           </size>
 
          </property>
 
         </spacer>
 
        </item>
 
        <item>
 
         <widget class="QToolButton" name="tbShowAll">
 
          <property name="toolTip">
 
           <string>Show all channels</string>
 
          </property>
 
          <property name="text">
 
           <string>Show All</string>
 
          </property>
 
         </widget>
 
        </item>
 
        <item>
 
         <widget class="QToolButton" name="tbHideAll">
 
          <property name="toolTip">
 
           <string>Hide all channels</string>
 
          </property>
 
          <property name="text">
 
           <string>Hide All</string>
 
          </property>
 
         </widget>
 
        </item>
 
        <item>
 
         <widget class="QToolButton" name="tbReset">
 
          <property name="text">
 
           <string>Reset</string>
 
          </property>
 
          <property name="popupMode">
 
           <enum>QToolButton::MenuButtonPopup</enum>
 
          </property>
 
          <property name="arrowType">
 
           <enum>Qt::NoArrow</enum>
 
          </property>
 
         </widget>
 
        </item>
 
@@ -123,59 +146,74 @@
 
     <property name="orientation">
 
      <enum>Qt::Vertical</enum>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <layout class="QFormLayout" name="formLayout_2">
 
     <property name="fieldGrowthPolicy">
 
      <enum>QFormLayout::FieldsStayAtSizeHint</enum>
 
     </property>
 
     <item row="0" column="0">
 
      <widget class="QLabel" name="label_3">
 
       <property name="toolTip">
 
        <string/>
 
       </property>
 
       <property name="text">
 
        <string>Number Of Samples:</string>
 
        <string>Buffer Size:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="0" column="1">
 
      <widget class="QSpinBox" name="spNumOfSamples">
 
       <property name="minimumSize">
 
        <size>
 
         <width>100</width>
 
         <height>0</height>
 
        </size>
 
       </property>
 
       <property name="maximumSize">
 
        <size>
 
         <width>100</width>
 
         <height>16777215</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>length of X axis</string>
 
        <string>Length of acquisition as number of samples</string>
 
       </property>
 
       <property name="keyboardTracking">
 
        <bool>false</bool>
 
       </property>
 
       <property name="minimum">
 
        <number>2</number>
 
       </property>
 
       <property name="maximum">
 
        <number>1000000</number>
 
        <number>10000000</number>
 
       </property>
 
       <property name="value">
 
        <number>1000</number>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="1" column="0">
 
     <item row="2" column="0">
 
      <widget class="QCheckBox" name="cbIndex">
 
       <property name="text">
 
        <string>Index as X AXis</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="1" column="1">
 
     <item row="2" column="1">
 
      <layout class="QHBoxLayout" name="horizontalLayout_4">
 
       <item>
 
        <widget class="QLabel" name="lXmin">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="text">
 
          <string>Xmin</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
@@ -228,35 +266,35 @@
 
          <string>upper limit of Y axis</string>
 
         </property>
 
         <property name="maximum">
 
          <double>1000.000000000000000</double>
 
         </property>
 
         <property name="value">
 
          <double>1000.000000000000000</double>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item row="2" column="0">
 
     <item row="3" column="0">
 
      <widget class="QCheckBox" name="cbAutoScale">
 
       <property name="text">
 
        <string>Auto Scale Y Axis</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="2" column="1">
 
     <item row="3" column="1">
 
      <layout class="QHBoxLayout" name="horizontalLayout_3">
 
       <item>
 
        <widget class="QLabel" name="lYmin">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="text">
 
          <string>Ymin</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
@@ -309,52 +347,77 @@
 
          <string>upper limit of Y axis</string>
 
         </property>
 
         <property name="maximum">
 
          <double>1000.000000000000000</double>
 
         </property>
 
         <property name="value">
 
          <double>1000.000000000000000</double>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item row="5" column="0">
 
     <item row="6" column="0">
 
      <widget class="QLabel" name="label">
 
       <property name="text">
 
        <string>Select Range Preset:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="5" column="1">
 
     <item row="6" column="1">
 
      <widget class="QComboBox" name="cbRangePresets"/>
 
     </item>
 
     <item row="1" column="0">
 
      <widget class="QLabel" name="label_4">
 
       <property name="toolTip">
 
        <string/>
 
       </property>
 
       <property name="text">
 
        <string>Plot Width:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="1" column="1">
 
      <widget class="QSpinBox" name="spPlotWidth">
 
       <property name="minimumSize">
 
        <size>
 
         <width>100</width>
 
         <height>0</height>
 
        </size>
 
       </property>
 
       <property name="maximumSize">
 
        <size>
 
         <width>100</width>
 
         <height>16777215</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>Width of X axis as maximum number of samples that are shown in plot</string>
 
       </property>
 
       <property name="keyboardTracking">
 
        <bool>false</bool>
 
       </property>
 
       <property name="minimum">
 
        <number>2</number>
 
       </property>
 
       <property name="maximum">
 
        <number>100000</number>
 
       </property>
 
       <property name="value">
 
        <number>1000</number>
 
       </property>
 
      </widget>
 
     </item>
 
    </layout>
 
   </item>
 
   <item>
 
    <spacer name="horizontalSpacer_2">
 
     <property name="orientation">
 
      <enum>Qt::Horizontal</enum>
 
     </property>
 
     <property name="sizeType">
 
      <enum>QSizePolicy::MinimumExpanding</enum>
 
     </property>
 
     <property name="sizeHint" stdset="0">
 
      <size>
 
       <width>1</width>
 
       <height>20</height>
 
      </size>
 
     </property>
 
    </spacer>
 
   </item>
 
  </layout>
 
 </widget>
 
 <customwidgets>
 
  <customwidget>
 
   <class>color_widgets::ColorSelector</class>
 
   <extends>QWidget</extends>
 
   <header>color_selector.hpp</header>
 
   <container>1</container>
 
  </customwidget>
 
 </customwidgets>
 
 <resources/>
 
 <connections/>
src/plotmanager.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

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

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

	
 
PlotManager::PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel, QObject *parent) :
 
    QObject(parent),
 
    _plotArea(plotArea),
 
    showGridAction("&Grid", this),
 
    showMinorGridAction("&Minor Grid", this),
 
    unzoomAction("&Unzoom", this),
 
    darkBackgroundAction("&Dark Background", this),
 
    showLegendAction("&Legend", this),
 
    showMultiAction("Multi &Plot", this),
 
    setSymbolsAction("Symbols", this)
 
PlotManager::PlotManager(QWidget* plotArea, PlotMenu* menu,
 
                         const Stream* stream, QObject* parent) :
 
    QObject(parent)
 
{
 
    construct(plotArea, menu);
 
    _stream = stream;
 
    if (_stream == NULL) return;
 

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

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

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

	
 
}
 

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

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

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

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

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

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

	
 
    // initialize menu actions
 
    showGridAction.setToolTip("Show Grid");
 
    showMinorGridAction.setToolTip("Show Minor Grid");
 
    unzoomAction.setToolTip("Unzoom the Plot");
 
    darkBackgroundAction.setToolTip("Enable Dark Plot Background");
 
    showLegendAction.setToolTip("Display the Legend on Plot");
 
    showMultiAction.setToolTip("Display All Channels Separately");
 
    setSymbolsAction.setToolTip("Show/Hide symbols");
 

	
 
    showGridAction.setShortcut(QKeySequence("G"));
 
    showMinorGridAction.setShortcut(QKeySequence("M"));
 

	
 
    showGridAction.setCheckable(true);
 
    showMinorGridAction.setCheckable(true);
 
    darkBackgroundAction.setCheckable(true);
 
    showLegendAction.setCheckable(true);
 
    showMultiAction.setCheckable(true);
 

	
 
    showGridAction.setChecked(false);
 
    showMinorGridAction.setChecked(false);
 
    darkBackgroundAction.setChecked(false);
 
    showLegendAction.setChecked(true);
 
    showMultiAction.setChecked(false);
 

	
 
    showMinorGridAction.setEnabled(false);
 
    // connect to  menu
 
    connect(menu, &PlotMenu::symbolShowChanged, this, &PlotManager:: setSymbols);
 

	
 
    // setup symbols menu
 
    setSymbolsAutoAct = setSymbolsMenu.addAction("Show When Zoomed");
 
    setSymbolsAutoAct->setCheckable(true);
 
    setSymbolsAutoAct->setChecked(true);
 
    connect(setSymbolsAutoAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) setSymbols(Plot::ShowSymbolsAuto);
 
            });
 
    setSymbolsShowAct = setSymbolsMenu.addAction("Always Show");
 
    setSymbolsShowAct->setCheckable(true);
 
    connect(setSymbolsShowAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) setSymbols(Plot::ShowSymbolsShow);
 
            });
 
    setSymbolsHideAct = setSymbolsMenu.addAction("Always Hide");
 
    setSymbolsHideAct->setCheckable(true);
 
    connect(setSymbolsHideAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) setSymbols(Plot::ShowSymbolsHide);
 
            });
 
    setSymbolsAction.setMenu(&setSymbolsMenu);
 

	
 
    // add symbol actions to same group so that they appear as radio buttons
 
    auto group = new QActionGroup(this);
 
    group->addAction(setSymbolsAutoAct);
 
    group->addAction(setSymbolsShowAct);
 
    group->addAction(setSymbolsHideAct);
 
    connect(&menu->showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::showGrid);
 
    connect(&menu->showMinorGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::showMinorGrid);
 
    connect(&menu->darkBackgroundAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::darkBackground);
 
    connect(&menu->showLegendAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::showLegend);
 
    connect(&menu->showMultiAction, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            this, &PlotManager::setMulti);
 
    connect(&menu->unzoomAction, &QAction::triggered,
 
            this, &PlotManager::unzoom);
 

	
 
    connect(&showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::showGrid);
 
    connect(&showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            &showMinorGridAction, &QAction::setEnabled);
 
    connect(&showMinorGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::showMinorGrid);
 
    connect(&unzoomAction, &QAction::triggered, this, &PlotManager::unzoom);
 
    connect(&darkBackgroundAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::darkBackground);
 
    connect(&showLegendAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::showLegend);
 
    connect(&showLegendAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::showLegend);
 
    connect(&showMultiAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::setMulti);
 

	
 
    // connect to channel info model
 
    if (_infoModel != NULL)     // TODO: remove when snapshots have infomodel
 
    {
 
        connect(_infoModel, &QAbstractItemModel::dataChanged,
 
                this, &PlotManager::onChannelInfoChanged);
 

	
 
        connect(_infoModel, &QAbstractItemModel::modelReset,
 
                [this]()
 
                {
 
                    onChannelInfoChanged(_infoModel->index(0, 0), // start
 
                                         _infoModel->index(_infoModel->rowCount()-1, 0), // end
 
                                         {}); // roles ignored
 
                });
 
    }
 
    // initial settings from menu actions
 
    showGrid(menu->showGridAction.isChecked());
 
    showMinorGrid(menu->showMinorGridAction.isChecked());
 
    darkBackground(menu->darkBackgroundAction.isChecked());
 
    showLegend(menu->showLegendAction.isChecked());
 
    setMulti(menu->showMultiAction.isChecked());
 
}
 

	
 
PlotManager::~PlotManager()
 
{
 
    while (curves.size())
 
    {
 
        delete curves.takeLast();
 
    }
 

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

	
 
    if (scrollArea != NULL) delete scrollArea;
 
    if (emptyPlot != NULL) delete emptyPlot;
 
}
 

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

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

	
 
    replot();
 
}
 

	
 
void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
 
                                       const QModelIndex &bottomRight,
 
                                       const QVector<int> &roles)
 
{
 
    int start = topLeft.row();
 
    int end = bottomRight.row();
 

	
 
    for (int ci = start; ci <= end; ci++)
 
    {
 
        QString name = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::EditRole).toString();
 
@@ -180,33 +181,49 @@ void PlotManager::onChannelInfoChanged(c
 
        if (isMulti)
 
        {
 
            plotWidgets[ci]->updateSymbols(); // required for color change
 
            plotWidgets[ci]->updateLegend(curves[ci]);
 
            plotWidgets[ci]->setVisible(visible);
 
            if (visible)
 
            {
 
                plotWidgets[ci]->replot();
 
            }
 
        }
 
    }
 

	
 
    checkNoVisChannels();
 

	
 
    // replot single widget
 
    if (!isMulti)
 
    {
 
        plotWidgets[0]->updateSymbols();
 
        plotWidgets[0]->updateLegend();
 
        replot();
 
    }
 
}
 

	
 
void PlotManager::checkNoVisChannels()
 
{
 
    // if all channels are hidden show indicator
 
    bool allhidden = std::none_of(curves.cbegin(), curves.cend(),
 
                                  [](QwtPlotCurve* c) {return c->isVisible();});
 

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

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

	
 
    isMulti = enabled;
 

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

	
 
@@ -215,38 +232,46 @@ void PlotManager::setMulti(bool enabled)
 
    {
 
        delete plotWidgets.takeLast();
 
    }
 

	
 
    // setup new layout
 
    setupLayout(isMulti);
 

	
 
    if (isMulti)
 
    {
 
        // add new widgets and attach
 
        for (auto curve : curves)
 
        {
 
            curve->attach(addPlotWidget());
 
            auto plot = addPlotWidget();
 
            plot->setVisible(curve->isVisible());
 
            curve->attach(plot);
 
        }
 
    }
 
    else
 
    {
 
        // add a single widget
 
        auto plot = addPlotWidget();
 

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

	
 
    // will skip if no plot widgets exist (can happen during constructor)
 
    if (plotWidgets.length())
 
    {
 
        checkNoVisChannels();
 
    }
 
}
 

	
 
void PlotManager::setupLayout(bool multiPlot)
 
{
 
    // delete previous layout if it exists
 
    if (_plotArea->layout() != 0)
 
    {
 
        delete _plotArea->layout();
 
    }
 

	
 
    if (multiPlot)
 
    {
 
@@ -275,68 +300,63 @@ void PlotManager::setupLayout(bool multi
 
    }
 

	
 
    layout->setContentsMargins(2,2,2,2);
 
    layout->setSpacing(1);
 
}
 

	
 
Plot* PlotManager::addPlotWidget()
 
{
 
    auto plot = new Plot();
 
    plotWidgets.append(plot);
 
    layout->addWidget(plot);
 

	
 
    plot->darkBackground(darkBackgroundAction.isChecked());
 
    plot->showGrid(showGridAction.isChecked());
 
    plot->showMinorGrid(showMinorGridAction.isChecked());
 
    plot->showLegend(showLegendAction.isChecked());
 
    plot->darkBackground(_menu->darkBackgroundAction.isChecked());
 
    plot->showGrid(_menu->showGridAction.isChecked());
 
    plot->showMinorGrid(_menu->showMinorGridAction.isChecked());
 
    plot->showLegend(_menu->showLegendAction.isChecked());
 
    plot->setSymbols(_menu->showSymbols());
 

	
 
    plot->showDemoIndicator(isDemoShown);
 
    plot->setYAxis(_autoScaled, _yMin, _yMax);
 
    plot->setNumOfSamples(_numOfSamples);
 
    plot->setSymbols(showSymbols);
 

	
 
    plot->setPlotWidth(_plotWidth);
 
    if (_xAxisAsIndex)
 
    {
 
        plot->setXAxis(0, _numOfSamples);
 
    }
 
    else
 
    {
 
        plot->setXAxis(_xMin, _xMax);
 
    }
 

	
 
    return plot;
 
}
 

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

	
 
void PlotManager::addCurve(QString title, QVector<QPointF> data)
 
{
 
    auto curve = new QwtPlotCurve(title);
 
    curve->setSamples(data);
 
    _addCurve(curve);
 
}
 

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

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

	
 
    // create the plot for the curve if we are on multi display
 
    Plot* plot;
 
    if (isMulti)
 
    {
 
        // create a new plot widget
 
        plot = addPlotWidget();
 
    }
 
    else
 
    {
 
        plot = plotWidgets[0];
 
@@ -358,64 +378,44 @@ void PlotManager::removeCurves(unsigned 
 
            {
 
                delete plotWidgets.takeLast();
 
            }
 
        }
 
    }
 
}
 

	
 
unsigned PlotManager::numOfCurves()
 
{
 
    return curves.size();
 
}
 

	
 
void PlotManager::setTitle(unsigned index, QString title)
 
{
 
    curves[index]->setTitle(title);
 

	
 
    plotWidget(index)->replot();
 
}
 

	
 
Plot* PlotManager::plotWidget(unsigned curveIndex)
 
{
 
    if (isMulti)
 
    {
 
        return plotWidgets[curveIndex];
 
    }
 
    else
 
    {
 
        return plotWidgets[0];
 
    }
 
}
 

	
 
void PlotManager::replot()
 
{
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->replot();
 
    }
 
}
 

	
 
QList<QAction*> PlotManager::menuActions()
 
{
 
    QList<QAction*> actions;
 
    actions << &showGridAction;
 
    actions << &showMinorGridAction;
 
    actions << &unzoomAction;
 
    actions << &darkBackgroundAction;
 
    actions << &showLegendAction;
 
    actions << &showMultiAction;
 
    actions << &setSymbolsAction;
 
    return actions;
 
}
 

	
 
void PlotManager::showGrid(bool show)
 
{
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->showGrid(show);
 
    }
 
}
 

	
 
void PlotManager::showMinorGrid(bool show)
 
{
 
    for (auto plot : plotWidgets)
 
    {
 
@@ -474,159 +474,55 @@ void PlotManager::setYAxis(bool autoScal
 
    {
 
        plot->setYAxis(autoScaled, yAxisMin, yAxisMax);
 
    }
 
}
 

	
 
void PlotManager::setXAxis(bool asIndex, double xMin, double xMax)
 
{
 
    _xAxisAsIndex = asIndex;
 
    _xMin = xMin;
 
    _xMax = xMax;
 
    for (auto curve : curves)
 
    {
 
        // TODO: what happens when addCurve(QVector) is used?
 
        FrameBufferSeries* series = static_cast<FrameBufferSeries*>(curve->data());
 
        series->setXAxis(asIndex, xMin, xMax);
 
    }
 
    for (auto plot : plotWidgets)
 
    {
 
        if (asIndex)
 
        {
 
            plot->setXAxis(0, _numOfSamples);
 
        }
 
        else
 
        {
 
            plot->setXAxis(xMin, xMax);
 
        }
 
    }
 
    replot();
 
}
 

	
 
void PlotManager::flashSnapshotOverlay()
 
{
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->flashSnapshotOverlay(darkBackgroundAction.isChecked());
 
        plot->flashSnapshotOverlay(_menu->darkBackgroundAction.isChecked());
 
    }
 
}
 

	
 
void PlotManager::setNumOfSamples(unsigned value)
 
{
 
    _numOfSamples = value;
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->setNumOfSamples(value);
 
        if (_xAxisAsIndex) plot->setXAxis(0, value);
 
    }
 
}
 

	
 
PlotViewSettings PlotManager::viewSettings() const
 
{
 
    return PlotViewSettings(
 
        {
 
            showGridAction.isChecked(),
 
            showMinorGridAction.isChecked(),
 
            darkBackgroundAction.isChecked(),
 
            showLegendAction.isChecked(),
 
            showMultiAction.isChecked(),
 
            showSymbols
 
        });
 
}
 

	
 
void PlotManager::setViewSettings(const PlotViewSettings& settings)
 
void PlotManager::setPlotWidth(double width)
 
{
 
    showGridAction.setChecked(settings.showGrid);
 
    showGrid(settings.showGrid);
 
    showMinorGridAction.setChecked(settings.showMinorGrid);
 
    showMinorGrid(settings.showMinorGrid);
 
    darkBackgroundAction.setChecked(settings.darkBackground);
 
    darkBackground(settings.darkBackground);
 
    showLegendAction.setChecked(settings.showLegend);
 
    showLegend(settings.showLegend);
 
    showMultiAction.setChecked(settings.showMulti);
 
    setMulti(settings.showMulti);
 

	
 
    setSymbols(settings.showSymbols);
 
    if (showSymbols == Plot::ShowSymbolsAuto)
 
    _plotWidth = width;
 
    for (auto plot : plotWidgets)
 
    {
 
        setSymbolsAutoAct->setChecked(true);
 
    }
 
    else if (showSymbols == Plot::ShowSymbolsShow)
 
    {
 
        setSymbolsShowAct->setChecked(true);
 
    }
 
    else
 
    {
 
        setSymbolsHideAct->setChecked(true);
 
        plot->setPlotWidth(width);
 
    }
 
}
 

	
 
void PlotManager::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    settings->setValue(SG_Plot_DarkBackground, darkBackgroundAction.isChecked());
 
    settings->setValue(SG_Plot_Grid, showGridAction.isChecked());
 
    settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked());
 
    settings->setValue(SG_Plot_Legend, showLegendAction.isChecked());
 
    settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked());
 

	
 
    QString showSymbolsStr;
 
    if (showSymbols == Plot::ShowSymbolsAuto)
 
    {
 
        showSymbolsStr = "auto";
 
    }
 
    else if (showSymbols == Plot::ShowSymbolsShow)
 
    {
 
        showSymbolsStr = "show";
 
    }
 
    else
 
    {
 
        showSymbolsStr = "hide";
 
    }
 
    settings->setValue(SG_Plot_Symbols, showSymbolsStr);
 

	
 
    settings->endGroup();
 
}
 

	
 
void PlotManager::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    darkBackgroundAction.setChecked(
 
        settings->value(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()).toBool());
 
    darkBackground(darkBackgroundAction.isChecked());
 
    showGridAction.setChecked(
 
        settings->value(SG_Plot_Grid, showGridAction.isChecked()).toBool());
 
    showGrid(showGridAction.isChecked());
 
    showMinorGridAction.setChecked(
 
        settings->value(SG_Plot_MinorGrid, showMinorGridAction.isChecked()).toBool());
 
    showMinorGridAction.setEnabled(showGridAction.isChecked());
 
    showMinorGrid(showMinorGridAction.isChecked());
 
    showLegendAction.setChecked(
 
        settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool());
 
    showLegend(showLegendAction.isChecked());
 
    showMultiAction.setChecked(
 
        settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
 
    setMulti(showMultiAction.isChecked());
 

	
 
    QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString();
 
    if (showSymbolsStr == "auto")
 
    {
 
        setSymbols(Plot::ShowSymbolsAuto);
 
        setSymbolsAutoAct->setChecked(true);
 
    }
 
    else if (showSymbolsStr == "show")
 
    {
 
        setSymbols(Plot::ShowSymbolsShow);
 
        setSymbolsShowAct->setChecked(true);
 
    }
 
    else if (showSymbolsStr == "hide")
 
    {
 
        setSymbols(Plot::ShowSymbolsHide);
 
        setSymbolsHideAct->setChecked(true);
 
    }
 
    else
 
    {
 
        qCritical() << "Invalid symbol setting:" << showSymbolsStr;
 
    }
 

	
 
    settings->endGroup();
 
}
src/plotmanager.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -17,128 +17,109 @@
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef PLOTMANAGER_H
 
#define PLOTMANAGER_H
 

	
 
#include <QObject>
 
#include <QWidget>
 
#include <QScrollArea>
 
#include <QVBoxLayout>
 
#include <QList>
 
#include <QSettings>
 
#include <QAction>
 
#include <QMenu>
 

	
 
#include <qwt_plot_curve.h>
 
#include "plot.h"
 
#include "framebufferseries.h"
 
#include "channelinfomodel.h"
 

	
 
struct PlotViewSettings
 
{
 
    bool showGrid;
 
    bool showMinorGrid;
 
    bool darkBackground;
 
    bool showLegend;
 
    bool showMulti;
 
    Plot::ShowSymbols showSymbols;
 
};
 
#include "stream.h"
 
#include "snapshot.h"
 
#include "plotmenu.h"
 

	
 
class PlotManager : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel = NULL, QObject *parent = 0);
 
    explicit PlotManager(QWidget* plotArea, PlotMenu* menu,
 
                         const Stream* stream = NULL,
 
                         QObject *parent = 0);
 
    explicit PlotManager(QWidget* plotArea, PlotMenu* menu,
 
                         Snapshot* snapshot,
 
                         QObject *parent = 0);
 
    ~PlotManager();
 
    /// Add a new curve with title and buffer. A color is
 
    /// automatically chosen for curve.
 
    void addCurve(QString title, FrameBuffer* buffer);
 
    /// Alternative of `addCurve` for static curve data (snapshots).
 
    void addCurve(QString title, QVector<QPointF> data);
 
    /// Set the displayed title for a curve
 
    void setTitle(unsigned index, QString title);
 
    void addCurve(QString title, const FrameBuffer* buffer);
 
    /// Removes curves from the end
 
    void removeCurves(unsigned number);
 
    /// Returns current number of curves known by plot manager
 
    unsigned numOfCurves();
 
    /// Returns the list of actions to be inserted into the `View` menu
 
    QList<QAction*> menuActions();
 
    /// Returns current status of menu actions
 
    PlotViewSettings viewSettings() const;
 
    /// Set the current state of view
 
    void setViewSettings(const PlotViewSettings& settings);
 
    /// Stores plot settings into a `QSettings`.
 
    void saveSettings(QSettings* settings);
 
    /// Loads plot settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    /// Enable/Disable multiple plot display
 
    void setMulti(bool enabled);
 
    /// Update all plot widgets
 
    void replot();
 
    /// Enable display of a "DEMO" label on each plot
 
    void showDemoIndicator(bool show = true);
 
    /// Set the Y axis
 
    void setYAxis(bool autoScaled, double yMin = 0, double yMax = 1);
 
    /// Set the X axis
 
    void setXAxis(bool asIndex, double xMin = 0 , double xMax = 1);
 
    /// Display an animation for snapshot
 
    void flashSnapshotOverlay();
 
    /// Should be called to update zoom base
 
    void setNumOfSamples(unsigned value);
 
    /// Maximum width of X axis (limit of hscroll)
 
    void setPlotWidth(double width);
 

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

	
 
    // menu actions
 
    QAction showGridAction;
 
    QAction showMinorGridAction;
 
    QAction unzoomAction;
 
    QAction darkBackgroundAction;
 
    QAction showLegendAction;
 
    QAction showMultiAction;
 
    QAction setSymbolsAction;
 
    QMenu setSymbolsMenu;
 
    QAction* setSymbolsAutoAct;
 
    QAction* setSymbolsShowAct;
 
    QAction* setSymbolsHideAct;
 

	
 
    /// Common constructor
 
    void construct(QWidget* plotArea, PlotMenu* menu);
 
    /// Setups the layout for multi or single plot
 
    void setupLayout(bool multiPlot);
 
    /// Inserts a new plot widget to the current layout.
 
    Plot* addPlotWidget();
 
    /// Returns the plot widget that given curve is attached to
 
    Plot* plotWidget(unsigned curveIndex);
 
    /// Common part of overloaded `addCurve` functions
 
    void _addCurve(QwtPlotCurve* curve);
 
    void setSymbols(Plot::ShowSymbols shown);
 
    /// Check and make sure "no visible channels" text is shown
 
    void checkNoVisChannels();
 

	
 
private slots:
 
    void showGrid(bool show = true);
 
    void showMinorGrid(bool show = true);
 
    void showLegend(bool show = true);
 
    void unzoom();
 
    void darkBackground(bool enabled = true);
 
    void setSymbols(Plot::ShowSymbols shown);
 

	
 
    void onNumChannelsChanged(unsigned value);
 
    void onChannelInfoChanged(const QModelIndex & topLeft,
 
                              const QModelIndex & bottomRight,
 
                              const QVector<int> & roles = QVector<int> ());
 
};
 

	
 
#endif // PLOTMANAGER_H
src/plotmenu.cpp
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "plotmenu.h"
 
#include "setting_defines.h"
 
#include "utils.h"
 

	
 
PlotMenu::PlotMenu(QWidget* parent) :
 
    QMenu(tr("&View"), parent),
 
    showGridAction("&Grid", this),
 
    showMinorGridAction("&Minor Grid", this),
 
    unzoomAction("&Unzoom", this),
 
    darkBackgroundAction("&Dark Background", this),
 
    showLegendAction("&Legend", this),
 
    showMultiAction("Multi &Plot", this),
 
    setSymbolsAction("&Symbols", this),
 
    setSymbolsAutoAct("Show When &Zoomed", this),
 
    setSymbolsShowAct("Always &Show", this),
 
    setSymbolsHideAct("Always &Hide", this)
 
{
 
    showGridAction.setToolTip("Show Grid");
 
    showMinorGridAction.setToolTip("Show Minor Grid");
 
    unzoomAction.setToolTip("Unzoom the Plot");
 
    darkBackgroundAction.setToolTip("Enable Dark Plot Background");
 
    showLegendAction.setToolTip("Display the Legend on Plot");
 
    showMultiAction.setToolTip("Display All Channels Separately");
 
    setSymbolsAction.setToolTip("Show/Hide symbols");
 

	
 
    showGridAction.setShortcut(QKeySequence("G"));
 
    showMinorGridAction.setShortcut(QKeySequence("M"));
 

	
 
    showGridAction.setCheckable(true);
 
    showMinorGridAction.setCheckable(true);
 
    darkBackgroundAction.setCheckable(true);
 
    showLegendAction.setCheckable(true);
 
    showMultiAction.setCheckable(true);
 

	
 
    showGridAction.setChecked(false);
 
    showMinorGridAction.setChecked(false);
 
    darkBackgroundAction.setChecked(false);
 
    showLegendAction.setChecked(true);
 
    showMultiAction.setChecked(false);
 

	
 
    // minor grid is only enabled when _major_ grid is enabled
 
    showMinorGridAction.setEnabled(false);
 
    connect(&showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            &showMinorGridAction, &QAction::setEnabled);
 

	
 
    // setup set symbols menu
 
    setSymbolsMenu.addAction(&setSymbolsAutoAct);
 
    setSymbolsAutoAct.setCheckable(true);
 
    setSymbolsAutoAct.setChecked(true);
 
    connect(&setSymbolsAutoAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) emit symbolShowChanged(Plot::ShowSymbolsAuto);
 
            });
 

	
 
    setSymbolsMenu.addAction(&setSymbolsShowAct);
 
    setSymbolsShowAct.setCheckable(true);
 
    connect(&setSymbolsShowAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) symbolShowChanged(Plot::ShowSymbolsShow);
 
            });
 

	
 
    setSymbolsMenu.addAction(&setSymbolsHideAct);
 
    setSymbolsHideAct.setCheckable(true);
 
    connect(&setSymbolsHideAct, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            [this](bool checked)
 
            {
 
                if (checked) symbolShowChanged(Plot::ShowSymbolsHide);
 
            });
 

	
 
    // add symbol actions to same group so that they appear as radio buttons
 
    auto group = new QActionGroup(this);
 
    group->addAction(&setSymbolsAutoAct);
 
    group->addAction(&setSymbolsShowAct);
 
    group->addAction(&setSymbolsHideAct);
 

	
 
    setSymbolsAction.setMenu(&setSymbolsMenu);
 

	
 
    // add all actions to create this menu
 
    addAction(&showGridAction);
 
    addAction(&showMinorGridAction);
 
    addAction(&unzoomAction);
 
    addAction(&darkBackgroundAction);
 
    addAction(&showLegendAction);
 
    addAction(&showMultiAction);
 
    addAction(&setSymbolsAction);
 
}
 

	
 
PlotMenu::PlotMenu(PlotViewSettings s, QWidget* parent) :
 
    PlotMenu(parent)
 
{
 
    showGridAction.setChecked(s.showGrid);
 
    showMinorGridAction.setChecked(s.showMinorGrid);
 
    darkBackgroundAction.setChecked(s.darkBackground);
 
    showLegendAction.setChecked(s.showLegend);
 
    showMultiAction.setChecked(s.showMulti);
 
    switch (s.showSymbols)
 
    {
 
        case Plot::ShowSymbolsAuto:
 
             setSymbolsAutoAct.setChecked(true);
 
            break;
 
        case Plot::ShowSymbolsShow:
 
            setSymbolsShowAct.setChecked(true);
 
            break;
 
        case Plot::ShowSymbolsHide:
 
            setSymbolsHideAct.setChecked(true);
 
            break;
 
    }
 
}
 

	
 
PlotViewSettings PlotMenu::viewSettings() const
 
{
 
    return PlotViewSettings(
 
        {
 
            showGridAction.isChecked(),
 
            showMinorGridAction.isChecked(),
 
            darkBackgroundAction.isChecked(),
 
            showLegendAction.isChecked(),
 
            showMultiAction.isChecked(),
 
            showSymbols()
 
        });
 
}
 

	
 
Plot::ShowSymbols PlotMenu::showSymbols() const
 
{
 
    if (setSymbolsAutoAct.isChecked())
 
    {
 
        return Plot::ShowSymbolsAuto;
 
    }
 
    else if (setSymbolsShowAct.isChecked())
 
    {
 
        return Plot::ShowSymbolsShow;
 
    }
 
    else // setSymbolsHideAct.isChecked()
 
    {
 
        return Plot::ShowSymbolsHide;
 
    }
 
}
 

	
 
void PlotMenu::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    settings->setValue(SG_Plot_DarkBackground, darkBackgroundAction.isChecked());
 
    settings->setValue(SG_Plot_Grid, showGridAction.isChecked());
 
    settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked());
 
    settings->setValue(SG_Plot_Legend, showLegendAction.isChecked());
 
    settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked());
 

	
 
    QString showSymbolsStr;
 
    if (showSymbols() == Plot::ShowSymbolsAuto)
 
    {
 
        showSymbolsStr = "auto";
 
    }
 
    else if (showSymbols() == Plot::ShowSymbolsShow)
 
    {
 
        showSymbolsStr = "show";
 
    }
 
    else
 
    {
 
        showSymbolsStr = "hide";
 
    }
 
    settings->setValue(SG_Plot_Symbols, showSymbolsStr);
 

	
 
    settings->endGroup();
 
}
 

	
 
void PlotMenu::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    darkBackgroundAction.setChecked(
 
        settings->value(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()).toBool());
 
    showGridAction.setChecked(
 
        settings->value(SG_Plot_Grid, showGridAction.isChecked()).toBool());
 
    showMinorGridAction.setChecked(
 
        settings->value(SG_Plot_MinorGrid, showMinorGridAction.isChecked()).toBool());
 
    showMinorGridAction.setEnabled(showGridAction.isChecked());
 
    showLegendAction.setChecked(
 
        settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool());
 
    showMultiAction.setChecked(
 
        settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
 

	
 
    QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString();
 
    if (showSymbolsStr == "auto")
 
    {
 
        // setSymbols(Plot::ShowSymbolsAuto);
 
        setSymbolsAutoAct.setChecked(true);
 
    }
 
    else if (showSymbolsStr == "show")
 
    {
 
        // setSymbols(Plot::ShowSymbolsShow);
 
        setSymbolsShowAct.setChecked(true);
 
    }
 
    else if (showSymbolsStr == "hide")
 
    {
 
        // setSymbols(Plot::ShowSymbolsHide);
 
        setSymbolsHideAct.setChecked(true);
 
    }
 
    else
 
    {
 
        qCritical() << "Invalid symbol setting:" << showSymbolsStr;
 
    }
 

	
 
    settings->endGroup();
 
}
src/plotmenu.h
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef PLOTMENU_H
 
#define PLOTMENU_H
 

	
 
#include <QMenu>
 
#include <QAction>
 
#include <QSettings>
 

	
 
#include "plot.h"
 

	
 
/// Used to quickly transfer view options between different menus
 
struct PlotViewSettings
 
{
 
    bool showGrid;
 
    bool showMinorGrid;
 
    bool darkBackground;
 
    bool showLegend;
 
    bool showMulti;
 
    Plot::ShowSymbols showSymbols;
 
};
 

	
 
class PlotMenu : public QMenu
 
{
 
    Q_OBJECT
 

	
 
public:
 
    PlotMenu(QWidget* parent = 0);
 
    PlotMenu(PlotViewSettings s, QWidget* parent = 0);
 

	
 
    QAction showGridAction;
 
    QAction showMinorGridAction;
 
    QAction unzoomAction;
 
    QAction darkBackgroundAction;
 
    QAction showLegendAction;
 
    QAction showMultiAction;
 
    QAction setSymbolsAction;
 
    QMenu setSymbolsMenu;
 
    QAction setSymbolsAutoAct;
 
    QAction setSymbolsShowAct;
 
    QAction setSymbolsHideAct;
 

	
 
    /// Returns a bundle of current view settings (menu selections)
 
    PlotViewSettings viewSettings() const;
 
    /// Selected "show symbol" option
 
    Plot::ShowSymbols showSymbols() const;
 
    /// Stores plot settings into a `QSettings`.
 
    void saveSettings(QSettings* settings);
 
    /// Loads plot settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
signals:
 
    void symbolShowChanged(Plot::ShowSymbols shown);
 
};
 

	
 
#endif // PLOTMENU_H
src/portcontrol.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  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.
 
@@ -39,24 +39,26 @@ const QMap<QSerialPort::Parity, QString>
 
    });
 

	
 
PortControl::PortControl(QSerialPort* port, QWidget* parent) :
 
    QWidget(parent),
 
    ui(new Ui::PortControl),
 
    portToolBar("Port Toolbar"),
 
    openAction("Open", this),
 
    loadPortListAction("↺", this)
 
{
 
    ui->setupUi(this);
 

	
 
    serialPort = port;
 
    connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)),
 
            this, SLOT(onPortError(QSerialPort::SerialPortError)));
 

	
 
    // setup actions
 
    openAction.setCheckable(true);
 
    openAction.setShortcut(QKeySequence("F12"));
 
    openAction.setToolTip("Open Port");
 
    QObject::connect(&openAction, &QAction::triggered,
 
                     this, &PortControl::openActionTriggered);
 

	
 
    loadPortListAction.setToolTip("Reload port list");
 
    QObject::connect(&loadPortListAction, &QAction::triggered,
 
                     [this](bool checked){loadPortList();});
 

	
 
@@ -121,24 +123,58 @@ PortControl::PortControl(QSerialPort* po
 
    // setup flow control selection buttons
 
    flowControlButtons.addButton(ui->rbNoFlowControl,
 
                                 (int) QSerialPort::NoFlowControl);
 
    flowControlButtons.addButton(ui->rbHardwareControl,
 
                                 (int) QSerialPort::HardwareControl);
 
    flowControlButtons.addButton(ui->rbSoftwareControl,
 
                                 (int) QSerialPort::SoftwareControl);
 

	
 
    QObject::connect(&flowControlButtons,
 
                     SELECT<int>::OVERLOAD_OF(&QButtonGroup::buttonClicked),
 
                     this, &PortControl::selectFlowControl);
 

	
 
    // initialize signal leds
 
    ui->ledDTR->setOn(true);
 
    ui->ledRTS->setOn(true);
 

	
 
    // connect output signals
 
    connect(ui->pbDTR, &QPushButton::clicked, [this]()
 
            {
 
                // toggle DTR
 
                ui->ledDTR->toggle();
 
                if (serialPort->isOpen())
 
                {
 
                    serialPort->setDataTerminalReady(ui->ledDTR->isOn());
 
                }
 
            });
 

	
 
    connect(ui->pbRTS, &QPushButton::clicked, [this]()
 
            {
 
                // toggle RTS
 
                ui->ledRTS->toggle();
 
                if (serialPort->isOpen())
 
                {
 
                    serialPort->setRequestToSend(ui->ledRTS->isOn());
 
                }
 
            });
 

	
 
    // setup pin update leds
 
    ui->ledDCD->setColor(Qt::yellow);
 
    ui->ledDSR->setColor(Qt::yellow);
 
    ui->ledRI->setColor(Qt::yellow);
 
    ui->ledCTS->setColor(Qt::yellow);
 

	
 
    pinUpdateTimer.setInterval(1000); // ms
 
    connect(&pinUpdateTimer, &QTimer::timeout, this, &PortControl::updatePinLeds);
 

	
 
    loadPortList();
 
    loadBaudRateList();
 
    ui->cbBaudRate->setCurrentIndex(ui->cbBaudRate->findText("9600"));
 
}
 

	
 
PortControl::~PortControl()
 
{
 
    delete ui;
 
}
 

	
 
void PortControl::loadPortList()
 
{
 
@@ -212,24 +248,25 @@ void PortControl::selectFlowControl(int 
 
    {
 
        if(!serialPort->setFlowControl((QSerialPort::FlowControl) flowControl))
 
        {
 
            qCritical() << "Can't set flow control option!";
 
        }
 
    }
 
}
 

	
 
void PortControl::togglePort()
 
{
 
    if (serialPort->isOpen())
 
    {
 
        pinUpdateTimer.stop();
 
        serialPort->close();
 
        qDebug() << "Closed port:" << serialPort->portName();
 
        emit portToggled(false);
 
    }
 
    else
 
    {
 
        // we get the port name from the edit text, which may not be
 
        // in the portList if user hasn't pressed Enter
 
        // Also note that, portText may not be the `portName`
 
        QString portText = ui->cbPortList->currentText();
 
        QString portName;
 
        int portIndex = portList.indexOf(portText);
 
@@ -249,24 +286,32 @@ void PortControl::togglePort()
 
        serialPort->setPortName(ui->cbPortList->currentData(PortNameRole).toString());
 

	
 
        // open port
 
        if (serialPort->open(QIODevice::ReadWrite))
 
        {
 
            // set port settings
 
            selectBaudRate(ui->cbBaudRate->currentText());
 
            selectParity((QSerialPort::Parity) parityButtons.checkedId());
 
            selectDataBits((QSerialPort::DataBits) dataBitsButtons.checkedId());
 
            selectStopBits((QSerialPort::StopBits) stopBitsButtons.checkedId());
 
            selectFlowControl((QSerialPort::FlowControl) flowControlButtons.checkedId());
 

	
 
            // set output signals
 
            serialPort->setDataTerminalReady(ui->ledDTR->isOn());
 
            serialPort->setRequestToSend(ui->ledRTS->isOn());
 

	
 
            // update pin signals
 
            updatePinLeds();
 
            pinUpdateTimer.start();
 

	
 
            qDebug() << "Opened port:" << serialPort->portName();
 
            emit portToggled(true);
 
        }
 
    }
 
    openAction.setChecked(serialPort->isOpen());
 
}
 

	
 
void PortControl::selectPort(QString portName)
 
{
 
    // portName may be coming from combobox
 
    portName = portName.split(" ")[0];
 
    // has selection actually changed
 
@@ -310,24 +355,91 @@ void PortControl::openActionTriggered(bo
 
}
 

	
 
void PortControl::onCbPortListActivated(int index)
 
{
 
    tbPortList.setCurrentIndex(index);
 
}
 

	
 
void PortControl::onTbPortListActivated(int index)
 
{
 
    ui->cbPortList->setCurrentIndex(index);
 
}
 

	
 
void PortControl::onPortError(QSerialPort::SerialPortError error)
 
{
 
    switch(error)
 
    {
 
        case QSerialPort::NoError :
 
            break;
 
        case QSerialPort::ResourceError :
 
            qWarning() << "Port error: resource unavaliable; most likely device removed.";
 
            if (serialPort->isOpen())
 
            {
 
                qWarning() << "Closing port on resource error: " << serialPort->portName();
 
                togglePort();
 
            }
 
            loadPortList();
 
            break;
 
        case QSerialPort::DeviceNotFoundError:
 
            qCritical() << "Device doesn't exists: " << serialPort->portName();
 
            break;
 
        case QSerialPort::PermissionError:
 
            qCritical() << "Permission denied. Either you don't have \
 
required privileges or device is already opened by another process.";
 
            break;
 
        case QSerialPort::OpenError:
 
            qWarning() << "Device is already opened!";
 
            break;
 
        case QSerialPort::NotOpenError:
 
            qCritical() << "Device is not open!";
 
            break;
 
        case QSerialPort::ParityError:
 
            qCritical() << "Parity error detected.";
 
            break;
 
        case QSerialPort::FramingError:
 
            qCritical() << "Framing error detected.";
 
            break;
 
        case QSerialPort::BreakConditionError:
 
            qCritical() << "Break condition is detected.";
 
            break;
 
        case QSerialPort::WriteError:
 
            qCritical() << "An error occurred while writing data.";
 
            break;
 
        case QSerialPort::ReadError:
 
            qCritical() << "An error occurred while reading data.";
 
            break;
 
        case QSerialPort::UnsupportedOperationError:
 
            qCritical() << "Operation is not supported.";
 
            break;
 
        case QSerialPort::TimeoutError:
 
            qCritical() << "A timeout error occurred.";
 
            break;
 
        case QSerialPort::UnknownError:
 
            qCritical() << "Unknown error! Error: " << serialPort->errorString();
 
            break;
 
        default:
 
            qCritical() << "Unhandled port error: " << error;
 
            break;
 
    }
 
}
 

	
 
void PortControl::updatePinLeds(void)
 
{
 
    auto pins = serialPort->pinoutSignals();
 
    ui->ledDCD->setOn(pins & QSerialPort::DataCarrierDetectSignal);
 
    ui->ledDSR->setOn(pins & QSerialPort::DataSetReadySignal);
 
    ui->ledRI->setOn(pins & QSerialPort::RingIndicatorSignal);
 
    ui->ledCTS->setOn(pins & QSerialPort::ClearToSendSignal);
 
}
 

	
 
QString PortControl::currentParityText()
 
{
 
    return paritySettingMap.value(
 
        (QSerialPort::Parity) parityButtons.checkedId());
 
}
 

	
 
QString PortControl::currentFlowControlText()
 
{
 
    if (flowControlButtons.checkedId() == QSerialPort::HardwareControl)
 
    {
 
        return "hardware";
 
    }
src/portcontrol.h
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  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.
 
@@ -19,24 +19,25 @@
 

	
 
#ifndef PORTCONTROL_H
 
#define PORTCONTROL_H
 

	
 
#include <QWidget>
 
#include <QButtonGroup>
 
#include <QSerialPort>
 
#include <QStringList>
 
#include <QToolBar>
 
#include <QAction>
 
#include <QComboBox>
 
#include <QSettings>
 
#include <QTimer>
 

	
 
#include "portlist.h"
 

	
 
namespace Ui {
 
class PortControl;
 
}
 

	
 
class PortControl : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
@@ -56,42 +57,46 @@ private:
 

	
 
    QButtonGroup parityButtons;
 
    QButtonGroup dataBitsButtons;
 
    QButtonGroup stopBitsButtons;
 
    QButtonGroup flowControlButtons;
 

	
 
    QToolBar portToolBar;
 
    QAction openAction;
 
    QAction loadPortListAction;
 
    QComboBox tbPortList;
 
    PortList portList;
 

	
 
    /// Used to refresh pinout signal leds periodically
 
    QTimer pinUpdateTimer;
 

	
 
    /// Returns the currently selected (entered) "portName" in the UI
 
    QString selectedPortName();
 
    /// Returns currently selected parity as text to be saved in settings
 
    QString currentParityText();
 
    /// Returns currently selected flow control as text to be saved in settings
 
    QString currentFlowControlText();
 

	
 
public slots:
 
    void loadPortList();
 
    void loadBaudRateList();
 
    void togglePort();
 
    void selectPort(QString portName);
 

	
 
    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);
 
    void onPortError(QSerialPort::SerialPortError error);
 
    void updatePinLeds(void);
 

	
 
signals:
 
    void portToggled(bool open);
 
};
 

	
 
#endif // PORTCONTROL_H
src/portcontrol.ui
Show inline comments
 
@@ -24,31 +24,37 @@
 
          <string>Port:</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="0" column="1">
 
        <widget class="QComboBox" name="cbPortList">
 
         <property name="sizePolicy">
 
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="toolTip">
 
          <string>You can enter a port name even if it's not listed, such as pseudo terminals.</string>
 
         </property>
 
         <property name="editable">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="1">
 
        <widget class="QComboBox" name="cbBaudRate">
 
         <property name="toolTip">
 
          <string>You can enter a custom baud rate if it's supported by your OS/adapter.</string>
 
         </property>
 
         <property name="inputMethodHints">
 
          <set>Qt::ImhPreferNumbers</set>
 
         </property>
 
         <property name="editable">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="0">
 
        <widget class="QLabel" name="label_2">
 
         <property name="text">
 
          <string>Baud Rate:</string>
 
@@ -61,25 +67,25 @@
 
          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>30</width>
 
           <height>16777215</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string/>
 
          <string>Reload the list of ports</string>
 
         </property>
 
         <property name="text">
 
          <string>↺</string>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <layout class="QHBoxLayout" name="horizontalLayout_2">
 
       <item>
 
        <widget class="QFrame" name="frame">
 
@@ -292,40 +298,266 @@
 
       <property name="toolTip">
 
        <string>Toggle port status</string>
 
       </property>
 
       <property name="text">
 
        <string>Open</string>
 
       </property>
 
       <property name="checkable">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <layout class="QGridLayout" name="gridLayout_2">
 
       <property name="spacing">
 
        <number>2</number>
 
       </property>
 
       <item row="1" column="2">
 
        <widget class="LedWidget" name="ledRTS" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Request To Send</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="0" column="0">
 
        <spacer name="horizontalSpacer_2">
 
         <property name="orientation">
 
          <enum>Qt::Horizontal</enum>
 
         </property>
 
         <property name="sizeHint" stdset="0">
 
          <size>
 
           <width>40</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
        </spacer>
 
       </item>
 
       <item row="0" column="1">
 
        <widget class="QPushButton" name="pbDTR">
 
         <property name="maximumSize">
 
          <size>
 
           <width>40</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Terminal Ready</string>
 
         </property>
 
         <property name="text">
 
          <string>DTR</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="0" column="2">
 
        <widget class="LedWidget" name="ledDTR" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Terminal Ready</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="2">
 
        <widget class="LedWidget" name="ledDSR" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Set Ready</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="1">
 
        <widget class="QLabel" name="labDSR">
 
         <property name="toolTip">
 
          <string>Data Set Ready</string>
 
         </property>
 
         <property name="text">
 
          <string>DSR</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="1">
 
        <widget class="QPushButton" name="pbRTS">
 
         <property name="maximumSize">
 
          <size>
 
           <width>40</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Request To Send</string>
 
         </property>
 
         <property name="text">
 
          <string>RTS</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="2" column="1">
 
        <widget class="QLabel" name="labDCD">
 
         <property name="toolTip">
 
          <string>Data Carrier Detect</string>
 
         </property>
 
         <property name="text">
 
          <string>DCD</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="2" column="2">
 
        <widget class="LedWidget" name="ledDCD" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Data Carrier Detect</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="4" column="1">
 
        <widget class="QLabel" name="labRI">
 
         <property name="toolTip">
 
          <string>Ring Indicator</string>
 
         </property>
 
         <property name="text">
 
          <string>RI</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="4" column="2">
 
        <widget class="LedWidget" name="ledRI" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Ring Indicator</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="5" column="2">
 
        <widget class="LedWidget" name="ledCTS" native="true">
 
         <property name="minimumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>15</width>
 
           <height>15</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>Clear To Send</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="5" column="1">
 
        <widget class="QLabel" name="labCTS">
 
         <property name="toolTip">
 
          <string>Clear To Send</string>
 
         </property>
 
         <property name="text">
 
          <string>CTS</string>
 
         </property>
 
         <property name="alignment">
 
          <set>Qt::AlignCenter</set>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <spacer name="verticalSpacer">
 
       <property name="orientation">
 
        <enum>Qt::Vertical</enum>
 
       </property>
 
       <property name="sizeHint" stdset="0">
 
        <size>
 
         <width>20</width>
 
         <height>40</height>
 
         <height>1</height>
 
        </size>
 
       </property>
 
      </spacer>
 
     </item>
 
    </layout>
 
   </item>
 
  </layout>
 
 </widget>
 
 <customwidgets>
 
  <customwidget>
 
   <class>LedWidget</class>
 
   <extends>QWidget</extends>
 
   <header>ledwidget.h</header>
 
   <container>1</container>
 
  </customwidget>
 
 </customwidgets>
 
 <tabstops>
 
  <tabstop>cbPortList</tabstop>
 
  <tabstop>pbReloadPorts</tabstop>
 
  <tabstop>pbOpenPort</tabstop>
 
  <tabstop>cbBaudRate</tabstop>
 
  <tabstop>rbNoParity</tabstop>
 
  <tabstop>rbOddParity</tabstop>
 
  <tabstop>rbEvenParity</tabstop>
 
  <tabstop>rb8Bits</tabstop>
 
  <tabstop>rb7Bits</tabstop>
 
  <tabstop>rb6Bits</tabstop>
 
  <tabstop>rb5Bits</tabstop>
src/portlist.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  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.
 
@@ -41,25 +41,26 @@ PortListItem::PortListItem(QSerialPortIn
 
    {
 
        construct(portInfo->portName());
 
    }
 
}
 

	
 
void PortListItem::construct(QString name, QString description, quint16 vid, quint16 pid)
 
{
 
    QString text = name;
 
    if (!description.isEmpty())
 
    {
 
        text += QString(" ") + description;
 
    }
 
    if (vid && pid)
 
    // Note: in some cases internal ports or RS232 ports may have VID&PID
 
    if (vid && pid && !name.contains("tty"))
 
    {
 
        text += QString("[%1:").arg(vid, 4, 16, QChar('0'));
 
        text += QString("%1]").arg(pid, 4, 16, QChar('0'));
 
        setIcon(QIcon(":/usb_icon.png"));
 
    }
 
    else if (name.contains("rfcomm"))
 
    {
 
        setIcon(QIcon(":/bluetooth_icon.png"));
 
    }
 
    else
 
    {
 
        setIcon(QIcon(":/rs232_icon.png"));
src/readonlybuffer.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtGlobal>
 
#include <string.h>
 

	
 
#include "readonlybuffer.h"
 

	
 
ReadOnlyBuffer::ReadOnlyBuffer(const FrameBuffer* source) :
 
    ReadOnlyBuffer(source, 0, source->size())
 
{
 
    // intentionally empty, see ↑
 
}
 

	
 
ReadOnlyBuffer::ReadOnlyBuffer(const FrameBuffer* source, unsigned start, unsigned n)
 
{
 
    Q_ASSERT(source->size() > 0);
 
    Q_ASSERT(start + n <= source->size());
 

	
 
    _size = n;
 
    data = new double[_size];
 

	
 
    for (unsigned i = 0; i < n; i++)
 
    {
 
        data[i] = source->sample(start + i);
 
    }
 

	
 
    /// if not exact copy of source re-calculate limits
 
    if (start == 0 && n == source->size())
 
    {
 
        _limits = source->limits();
 
    }
 
    else
 
    {
 
        updateLimits();
 
    }
 
}
 

	
 
ReadOnlyBuffer::ReadOnlyBuffer(const double* source, unsigned ssize)
 
{
 
    Q_ASSERT(source != nullptr && ssize);
 

	
 
    _size = ssize;
 
    data = new double[_size];
 
    memcpy(data, source, sizeof(double) * ssize);
 
    updateLimits();
 
}
 

	
 
ReadOnlyBuffer::~ReadOnlyBuffer()
 
{
 
    delete[] data;
 
}
 

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

	
 
double ReadOnlyBuffer::sample(unsigned i) const
 
{
 
    return data[i];
 
}
 

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

	
 
void ReadOnlyBuffer::updateLimits()
 
{
 
    Q_ASSERT(_size);
 

	
 
    _limits.start = data[0];
 
    _limits.end = data[0];
 

	
 
    for (unsigned i = 0; i < _size; i++)
 
    {
 
        if (data[i] > _limits.end)
 
        {
 
            _limits.end = data[i];
 
        }
 
        else if (data[i] < _limits.start)
 
        {
 
            _limits.start = data[i];
 
        }
 
    }
 
}
src/readonlybuffer.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef READONLYBUFFER_H
 
#define READONLYBUFFER_H
 

	
 
#include "framebuffer.h"
 

	
 
/// A read only frame buffer used for storing snapshot data. Main advantage of
 
/// this compared to `RingBuffer` is that reading data should be somewhat
 
/// faster.
 
class ReadOnlyBuffer : public FrameBuffer
 
{
 
public:
 
    /// Creates a buffer with data copied from `source`. Source buffer cannot be
 
    /// empty.
 
    ReadOnlyBuffer(const FrameBuffer* source);
 

	
 
    /// Creates a buffer from a slice of the `source`.
 
    ///
 
    /// @param start start of the slice
 
    /// @param n number of samples
 
    ///
 
    /// @important (start + n) should be smaller or equal than `source->size()`,
 
    /// otherwise it's an error.
 
    ReadOnlyBuffer(const FrameBuffer* source, unsigned start, unsigned n);
 

	
 
    /// Creates a buffer with data copied from an array
 
    ReadOnlyBuffer(const double* source, unsigned ssize);
 

	
 
    ~ReadOnlyBuffer();
 

	
 
    virtual unsigned size() const;
 
    virtual double sample(unsigned i) const;
 
    virtual Range limits() const;
 

	
 
private:
 
    double* data;    ///< data storage
 
    unsigned _size;  ///< data size
 
    Range _limits;   ///< limits cache
 

	
 
    // TODO: duplicate with `RingBuffer`
 
    void updateLimits(); ///< Updates limits cache
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QIcon>
 
#include <QFile>
 
#include <QFileInfo>
 
#include <QMessageBox>
 
#include <QFileDialog>
 
#include <QRegularExpression>
 
#include <QCompleter>
 
#include <QFileSystemModel>
 
#include <QDirModel>
 
#include <QtDebug>
 
#include <ctime>
 

	
 
#include "recordpanel.h"
 
#include "ui_recordpanel.h"
 
#include "setting_defines.h"
 

	
 
RecordPanel::RecordPanel(DataRecorder* recorder, ChannelManager* channelMan, QWidget *parent) :
 
RecordPanel::RecordPanel(Stream* stream, QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::RecordPanel),
 
    recordToolBar(tr("Record Toolbar")),
 
    recordAction(QIcon::fromTheme("media-record"), tr("Record"), this)
 
    recordAction(QIcon::fromTheme("media-record"), tr("Record"), this),
 
    recorder(this)
 
{
 
    overwriteSelected = false;
 
    _recorder = recorder;
 
    _channelMan = channelMan;
 
    _stream = stream;
 

	
 
    ui->setupUi(this);
 

	
 
    recordToolBar.setObjectName("tbRecord");
 

	
 
    recordAction.setCheckable(true);
 
    recordToolBar.addAction(&recordAction);
 
    ui->pbRecord->setDefaultAction(&recordAction);
 

	
 
    connect(ui->pbBrowse, &QPushButton::clicked,
 
            this, &RecordPanel::selectFile);
 
    connect(&recordAction, &QAction::triggered,
 
            this, &RecordPanel::onRecord);
 

	
 
    connect(ui->cbRecordPaused, SIGNAL(toggled(bool)),
 
            this, SIGNAL(recordPausedChanged(bool)));
 

	
 
    connect(ui->cbDisableBuffering, &QCheckBox::toggled,
 
            [this](bool enabled)
 
            {
 
                _recorder->disableBuffering = enabled;
 
                recorder.disableBuffering = enabled;
 
            });
 

	
 
    connect(ui->cbWindowsLE, &QCheckBox::toggled,
 
            [this](bool enabled)
 
            {
 
                _recorder->windowsLE = enabled;
 
                recorder.windowsLE = enabled;
 
            });
 

	
 
    connect(&recordAction, &QAction::toggled, ui->cbWindowsLE, &QWidget::setDisabled);
 
    connect(&recordAction, &QAction::toggled, ui->cbTimestamp, &QWidget::setDisabled);
 
    connect(&recordAction, &QAction::toggled, ui->leSeparator, &QWidget::setDisabled);
 
    connect(&recordAction, &QAction::toggled, ui->pbBrowse, &QWidget::setDisabled);
 

	
 
    QCompleter *completer = new QCompleter(this);
 
    // TODO: QDirModel is deprecated, use QFileSystemModel (but it doesn't work)
 
    completer->setModel(new QDirModel(completer));
 
    completer->setCaseSensitivity(Qt::CaseInsensitive);
 
    ui->leFileName->setCompleter(completer);
 
}
 

	
 
RecordPanel::~RecordPanel()
 
{
 
    delete ui;
 
}
 

	
 
QToolBar* RecordPanel::toolbar()
 
{
 
    return &recordToolBar;
 
}
 

	
 
bool RecordPanel::isRecording()
 
{
 
    return recordAction.isChecked();
 
}
 

	
 
bool RecordPanel::recordPaused()
 
{
 
    return ui->cbRecordPaused->isChecked();
 
}
 

	
 
bool RecordPanel::selectFile()
 
{
 
    QString fileName = QFileDialog::getSaveFileName(
 
        parentWidget(), tr("Select recording file"));
 

	
 
    if (fileName.isEmpty())
 
    {
 
        return false;
 
    }
 
    else
 
    {
 
        selectedFile = fileName;
 
        ui->lbFileName->setText(selectedFile);
 
        setSelectedFile(fileName);
 
        overwriteSelected = QFile::exists(fileName);
 
        return true;
 
    }
 
}
 

	
 
QString RecordPanel::selectedFile() const
 
{
 
    return ui->leFileName->text();
 
}
 

	
 
void RecordPanel::setSelectedFile(QString f)
 
{
 
    ui->leFileName->setText(f);
 
}
 

	
 
QString RecordPanel::getSelectedFile()
 
{
 
    if (selectedFile().isEmpty())
 
    {
 
        if (!selectFile()) return QString();
 
    }
 

	
 
    // assume that file name contains a time format specifier
 
    if (selectedFile().contains("%"))
 
    {
 
        auto ts = formatTimeStamp(selectedFile());
 
        if (!QFile::exists(ts) || // file doesn't exists
 
            confirmOverwrite(ts)) // exists but user accepted overwrite
 
        {
 
            return ts;
 
        }
 
        return QString();
 
    }
 

	
 
    // if no timestamp and file exists try autoincrement option
 
    if (!overwriteSelected && QFile::exists(selectedFile()))
 
    {
 
        if (ui->cbAutoIncrement->isChecked())
 
        {
 
            if (!incrementFileName()) return QString();
 
        }
 
        else
 
        {
 
            if (!confirmOverwrite(selectedFile()))
 
                return QString();
 
        }
 
    }
 

	
 
    return selectedFile();
 
}
 

	
 
QString RecordPanel::formatTimeStamp(QString t) const
 
{
 
    auto maxSize = t.size() + 1024;
 
    auto r = new char[maxSize];
 

	
 
    time_t rawtime;
 
    struct tm * timeinfo;
 

	
 
    time(&rawtime);
 
    timeinfo = localtime (&rawtime);
 
    strftime(r, maxSize, t.toLatin1().data(), timeinfo);
 

	
 
    auto rs = QString(r);
 
    delete r;
 
    return rs;
 
}
 

	
 
void RecordPanel::onRecord(bool start)
 
{
 
    if (!start)
 
    {
 
        stopRecording();
 
        return;
 
    }
 

	
 
    bool canceled = false;
 
    if (ui->leSeparator->text().isEmpty())
 
    {
 
        QMessageBox::critical(this, "Error",
 
                              "Column separator cannot be empty! Please select a separator.");
 
        ui->leSeparator->setFocus(Qt::OtherFocusReason);
 
        canceled = true;
 
    }
 

	
 
    // check file name
 
    if (!canceled && selectedFile.isEmpty() && !selectFile())
 
    {
 
        canceled = true;
 
    }
 

	
 
    if (!canceled && !overwriteSelected && QFile::exists(selectedFile))
 
    QString fn;
 
    if (!canceled)
 
    {
 
        if (ui->cbAutoIncrement->isChecked())
 
        {
 
            // TODO: should we increment even if user selected to replace?
 
            canceled = !incrementFileName();
 
        }
 
        else
 
        {
 
            canceled = !confirmOverwrite(selectedFile);
 
        }
 
        fn = getSelectedFile();
 
        canceled = fn.isEmpty();
 
    }
 

	
 
    if (canceled)
 
    {
 
        recordAction.setChecked(false);
 
    }
 
    else
 
    {
 
        overwriteSelected = false;
 
        startRecording();
 
        // TODO: show more visible error message when recording fails
 
        if (!startRecording(fn))
 
            recordAction.setChecked(false);
 
    }
 
}
 

	
 
bool RecordPanel::incrementFileName(void)
 
{
 
    QFileInfo fileInfo(selectedFile);
 
    QFileInfo fileInfo(selectedFile());
 

	
 
    QString base = fileInfo.completeBaseName();
 
    QRegularExpression regex("(.*?)(\\d+)(?!.*\\d)(.*)");
 
    auto match = regex.match(base);
 

	
 
    if (match.hasMatch())
 
    {
 
        bool ok;
 
        int fileNum = match.captured(2).toInt(&ok);
 
        base = match.captured(1) + QString::number(fileNum + 1) + match.captured(3);
 
    }
 
    else
 
@@ -183,28 +244,27 @@ bool RecordPanel::incrementFileName(void
 
    QString autoFileName = fileInfo.path() + "/" + base + suffix;
 

	
 
    // check if auto generated file name exists, ask user another name
 
    if (QFile::exists(autoFileName))
 
    {
 
        if (!confirmOverwrite(autoFileName))
 
        {
 
            return false;
 
        }
 
    }
 
    else
 
    {
 
        selectedFile = autoFileName;
 
        setSelectedFile(autoFileName);
 
    }
 

	
 
    ui->lbFileName->setText(selectedFile);
 
    return true;
 
}
 

	
 
bool RecordPanel::confirmOverwrite(QString fileName)
 
{
 
    // prepare message box
 
    QMessageBox mb(parentWidget());
 
    mb.setWindowTitle(tr("File Already Exists"));
 
    mb.setIcon(QMessageBox::Warning);
 
    mb.setText(tr("File (%1) already exists. How to continue?").arg(fileName));
 

	
 
    auto bCancel    = mb.addButton(QMessageBox::Cancel);
 
@@ -213,48 +273,56 @@ bool RecordPanel::confirmOverwrite(QStri
 

	
 
    mb.setEscapeButton(bCancel);
 

	
 
    // show message box
 
    mb.exec();
 

	
 
    if (mb.clickedButton() == bCancel)
 
    {
 
        return false;
 
    }
 
    else if (mb.clickedButton() == bOverwrite)
 
    {
 
        selectedFile = fileName;
 
        setSelectedFile(fileName);
 
        return true;
 
    }
 
    else                    // select button
 
    {
 
        return selectFile();
 
    }
 
}
 

	
 
void RecordPanel::startRecording(void)
 
bool RecordPanel::startRecording(QString fileName)
 
{
 
    QStringList channelNames;
 
    if (ui->cbHeader->isChecked())
 
    {
 
        channelNames = _channelMan->infoModel()->channelNames();
 
        channelNames = _stream->infoModel()->channelNames();
 
    }
 
    _recorder->startRecording(selectedFile, getSeparator(), channelNames);
 
    emit recordStarted();
 
    if (recorder.startRecording(fileName, getSeparator(),
 
                                channelNames, ui->cbTimestamp->isChecked()))
 
    {
 
        _stream->connectFollower(&recorder);
 
        return true;
 
    }
 
    else
 
    {
 
        return false;
 
    }
 
}
 

	
 
void RecordPanel::stopRecording(void)
 
{
 
    emit recordStopped();
 
    _recorder->stopRecording();
 
    recorder.stopRecording();
 
    _stream->disconnectFollower(&recorder);
 
}
 

	
 
void RecordPanel::onPortClose()
 
{
 
    if (recordAction.isChecked() && ui->cbStopOnClose->isChecked())
 
    {
 
        stopRecording();
 
        recordAction.setChecked(false);
 
    }
 
}
 

	
 
QString RecordPanel::getSeparator() const
 
@@ -263,32 +331,35 @@ QString RecordPanel::getSeparator() cons
 
    sep.replace("\\t", "\t");
 
    return sep;
 
}
 

	
 
void RecordPanel::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Record);
 
    settings->setValue(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked());
 
    settings->setValue(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked());
 
    settings->setValue(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked());
 
    settings->setValue(SG_Record_Header, ui->cbHeader->isChecked());
 
    settings->setValue(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked());
 
    settings->setValue(SG_Record_Timestamp, ui->cbTimestamp->isChecked());
 
    settings->setValue(SG_Record_Separator, ui->leSeparator->text());
 
    settings->endGroup();
 
}
 

	
 
void RecordPanel::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Record);
 
    ui->cbAutoIncrement->setChecked(
 
        settings->value(SG_Record_AutoIncrement, ui->cbAutoIncrement->isChecked()).toBool());
 
    ui->cbRecordPaused->setChecked(
 
        settings->value(SG_Record_RecordPaused, ui->cbRecordPaused->isChecked()).toBool());
 
    ui->cbStopOnClose->setChecked(
 
        settings->value(SG_Record_StopOnClose, ui->cbStopOnClose->isChecked()).toBool());
 
    ui->cbHeader->setChecked(
 
        settings->value(SG_Record_Header, ui->cbHeader->isChecked()).toBool());
 
    ui->cbDisableBuffering->setChecked(
 
        settings->value(SG_Record_DisableBuffering, ui->cbDisableBuffering->isChecked()).toBool());
 
    ui->cbTimestamp->setChecked(
 
        settings->value(SG_Record_Timestamp, ui->cbTimestamp->isChecked()).toBool());
 
    ui->leSeparator->setText(settings->value(SG_Record_Separator, ui->leSeparator->text()).toString());
 
    settings->endGroup();
 
}
src/recordpanel.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -17,91 +17,107 @@
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef RECORDPANEL_H
 
#define RECORDPANEL_H
 

	
 
#include <QWidget>
 
#include <QString>
 
#include <QToolBar>
 
#include <QAction>
 

	
 
#include "datarecorder.h"
 
#include "channelmanager.h"
 
#include "stream.h"
 

	
 
namespace Ui {
 
class RecordPanel;
 
}
 

	
 
class RecordPanel : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit RecordPanel(DataRecorder* recorder, ChannelManager* channelMan,
 
                         QWidget* parent = 0);
 
    explicit RecordPanel(Stream* stream, QWidget* parent = 0);
 
    ~RecordPanel();
 

	
 
    QToolBar* toolbar();
 

	
 
    bool isRecording();
 
    bool recordPaused();
 

	
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
signals:
 
    void recordStarted();
 
    void recordStopped();
 
    void recordPausedChanged(bool enabled);
 

	
 
public slots:
 
    /// Must be called when port is closed
 
    void onPortClose();
 

	
 
private:
 
    Ui::RecordPanel *ui;
 
    QToolBar recordToolBar;
 
    QAction recordAction;
 
    QString selectedFile;
 
    bool overwriteSelected;
 
    DataRecorder* _recorder;
 
    ChannelManager* _channelMan;
 
    DataRecorder recorder;
 
    Stream* _stream;
 

	
 
    /**
 
     * @brief Increments the file name.
 
     *
 
     * If file name doesn't have a number at the end of it, a number is appended
 
     * with underscore starting from 1.
 
     *
 
     * @return false if user cancels
 
     */
 
    bool incrementFileName(void);
 

	
 
    /**
 
     * @brief Used to ask user confirmation if auto generated file
 
     * name exists.
 
     *
 
     * If user confirms overwrite, `selectedFile` is set to
 
     * `fileName`. User is also given option to select file and is
 
     * shown a file select dialog in this case.
 
     *
 
     * @param fileName auto generated file name.
 
     * @return false if user cancels
 
     */
 
    bool confirmOverwrite(QString fileName);
 

	
 
    void startRecording(void);
 
    /// Returns filename in edit box. May be invalid!
 
    QString selectedFile() const;
 
    /// Sets the filename in edit box.
 
    void setSelectedFile(QString f);
 

	
 
    /**
 
     * Tries to get a valid file name by handling user interactions and
 
     * automatic naming (increment, timestamp etc).
 
     *
 
     * Returned file name can be used immediately. File name box should also be
 
     * set to selected file name.
 
     *
 
     * @return empty if failure otherwise valid filename
 
     */
 
    QString getSelectedFile();
 

	
 
    /// Formats timestamp in given text
 
    QString formatTimeStamp(QString t) const;
 

	
 
    bool startRecording(QString fileName);
 
    void stopRecording(void);
 

	
 
    /// Returns separator text from ui. "\t" is converted to TAB
 
    /// character.
 
    QString getSeparator() const;
 

	
 
private slots:
 
    /**
 
     * @brief Opens up the file select dialog
 
     *
 
     * If you cancel the selection operation, currently selected file is not
 
     * changed.
src/recordpanel.ui
Show inline comments
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>RecordPanel</class>
 
 <widget class="QWidget" name="RecordPanel">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>627</width>
 
    <height>261</height>
 
    <height>207</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QHBoxLayout" name="horizontalLayout_2">
 
   <item>
 
    <layout class="QVBoxLayout" name="verticalLayout">
 
     <item>
 
      <layout class="QHBoxLayout" name="horizontalLayout">
 
       <item>
 
        <widget class="QLineEdit" name="leFileName">
 
         <property name="sizePolicy">
 
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="toolTip">
 
          <string>You can use C `strftime` function format specifiers for timestamps in your file name.</string>
 
         </property>
 
         <property name="placeholderText">
 
          <string>Enter file name or browse</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QPushButton" name="pbBrowse">
 
         <property name="toolTip">
 
          <string>Select record file</string>
 
         </property>
 
         <property name="text">
 
          <string>Browse</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QLabel" name="lbFileName">
 
         <property name="sizePolicy">
 
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
 
           <horstretch>0</horstretch>
 
           <verstretch>0</verstretch>
 
          </sizepolicy>
 
         </property>
 
         <property name="text">
 
          <string>Select file...</string>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <layout class="QGridLayout" name="gridLayout">
 
       <item row="2" column="1">
 
        <widget class="QCheckBox" name="cbStopOnClose">
 
         <property name="text">
 
          <string>Stop recording when port closed</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="0">
 
        <widget class="QCheckBox" name="cbAutoIncrement">
 
         <property name="toolTip">
 
          <string>Increments file name automatically everytime a new recording starts</string>
 
         </property>
 
         <property name="text">
 
          <string>Auto increment file name</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="0">
 
        <widget class="QCheckBox" name="cbDisableBuffering">
 
         <property name="toolTip">
 
          <string>Do not buffer when writing to file. Check this if you are using other software to open the file during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Disable buffering</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="1">
 
        <widget class="QCheckBox" name="cbWindowsLE">
 
         <property name="toolTip">
 
          <string>Use CR+LF as line endings. Some windows software may not show lines correctly otherwise. Can't be changed during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Windows Style Line Endings</string>
 
         </property>
 
         <property name="checked">
 
          <bool>false</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="1">
 
        <widget class="QCheckBox" name="cbHeader">
 
         <property name="toolTip">
 
          <string>Channel names are written to the first line of record file</string>
 
         </property>
 
         <property name="text">
 
          <string>Write header line</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
@@ -62,83 +111,47 @@
 
        <widget class="QCheckBox" name="cbRecordPaused">
 
         <property name="toolTip">
 
          <string>Continue recording to file even when plotting is paused</string>
 
         </property>
 
         <property name="text">
 
          <string>Record while paused</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="0">
 
        <widget class="QCheckBox" name="cbDisableBuffering">
 
         <property name="toolTip">
 
          <string>Do not buffer when writing to file. Check this if you are using other software to open the file during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Disable buffering</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="2" column="1">
 
        <widget class="QCheckBox" name="cbStopOnClose">
 
         <property name="text">
 
          <string>Stop recording when port closed</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="0">
 
        <widget class="QCheckBox" name="cbAutoIncrement">
 
         <property name="toolTip">
 
          <string>Increments file name automatically everytime a new recording starts</string>
 
         </property>
 
         <property name="text">
 
          <string>Auto increment file name</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="1">
 
        <widget class="QCheckBox" name="cbWindowsLE">
 
         <property name="toolTip">
 
          <string>Use CR+LF as line endings. Some windows software may not show lines correctly otherwise. Can't be changed during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Windows Style Line Endings</string>
 
         </property>
 
         <property name="checked">
 
          <bool>false</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="2">
 
        <spacer name="horizontalSpacer_2">
 
         <property name="orientation">
 
          <enum>Qt::Horizontal</enum>
 
         </property>
 
         <property name="sizeHint" stdset="0">
 
          <size>
 
           <width>1</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
        </spacer>
 
       </item>
 
       <item row="4" column="0">
 
        <widget class="QCheckBox" name="cbTimestamp">
 
         <property name="toolTip">
 
          <string>Insert timestamp (milliseconds from epoch) as first column</string>
 
         </property>
 
         <property name="text">
 
          <string>Insert timestamp</string>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <layout class="QHBoxLayout" name="horizontalLayout_3">
 
       <item>
 
        <widget class="QLabel" name="label">
 
         <property name="text">
 
          <string>Column Separator:</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
src/ringbuffer.cpp
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 

	
 
#include "ringbuffer.h"
 

	
 
RingBuffer::RingBuffer(unsigned n)
 
{
 
    _size = n;
 
    data = new double[_size]();
 
    headIndex = 0;
 

	
 
    limInvalid = false;
 
    limCache = {0, 0};
 
}
 

	
 
RingBuffer::~RingBuffer()
 
{
 
    delete[] data;
 
}
 

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

	
 
double RingBuffer::sample(unsigned i) const
 
{
 
    unsigned index = headIndex + i;
 
    if (index >= _size) index -= _size;
 
    return data[index];
 
}
 

	
 
Range RingBuffer::limits() const
 
{
 
    if (limInvalid) updateLimits();
 
    return limCache;
 
}
 

	
 
void RingBuffer::resize(unsigned n)
 
{
 
    Q_ASSERT(n != _size);
 

	
 
    int offset = (int) n - (int) _size;
 
    if (offset == 0) return;
 

	
 
    double* newData = new double[n];
 

	
 
    // move data to new array
 
    int fill_start = offset > 0 ? offset : 0;
 

	
 
    for (int i = fill_start; i < int(n); i++)
 
    {
 
        newData[i] = sample(i - offset);
 
    }
 

	
 
    // fill the beginning of the new data
 
    if (fill_start > 0)
 
    {
 
        for (int i = 0; i < fill_start; i++)
 
        {
 
            newData[i] = 0;
 
        }
 
    }
 

	
 
    // data is ready, clean up and re-point
 
    delete data;
 
    data = newData;
 
    headIndex = 0;
 
    _size = n;
 

	
 
    // invalidate bounding rectangle
 
    limInvalid = true;
 
}
 

	
 
void RingBuffer::addSamples(double* samples, unsigned n)
 
{
 
    unsigned shift = n;
 
    if (shift < _size)
 
    {
 
        unsigned x = _size - headIndex; // distance of `head` to end
 

	
 
        if (shift <= x) // there is enough room at the end of array
 
        {
 
            for (unsigned i = 0; i < shift; i++)
 
            {
 
                data[i+headIndex] = samples[i];
 
            }
 

	
 
            if (shift == x) // we used all the room at the end
 
            {
 
                headIndex = 0;
 
            }
 
            else
 
            {
 
                headIndex += shift;
 
            }
 
        }
 
        else // there isn't enough room
 
        {
 
            for (unsigned i = 0; i < x; i++) // fill the end part
 
            {
 
                data[i+headIndex] = samples[i];
 
            }
 
            for (unsigned i = 0; i < (shift-x); i++) // continue from the beginning
 
            {
 
                data[i] = samples[i+x];
 
            }
 
            headIndex = shift-x;
 
        }
 
    }
 
    else // number of new samples equal or bigger than current size (doesn't fit)
 
    {
 
        int x = shift - _size;
 
        for (unsigned i = 0; i < _size; i++)
 
        {
 
            data[i] = samples[i+x];
 
        }
 
        headIndex = 0;
 
    }
 

	
 
    // invalidate cache
 
    limInvalid = true;
 
}
 

	
 
void RingBuffer::clear()
 
{
 
    for (unsigned i=0; i < _size; i++)
 
    {
 
        data[i] = 0.;
 
    }
 

	
 
    limCache = {0, 0};
 
    limInvalid = false;
 
}
 

	
 
void RingBuffer::updateLimits() const
 
{
 
    limCache.start = data[0];
 
    limCache.end = data[0];
 

	
 
    for (unsigned i = 0; i < _size; i++)
 
    {
 
        if (data[i] > limCache.end)
 
        {
 
            limCache.end = data[i];
 
        }
 
        else if (data[i] < limCache.start)
 
        {
 
            limCache.start = data[i];
 
        }
 
    }
 

	
 
    limInvalid = false;
 
}
src/ringbuffer.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef RINGBUFFER_H
 
#define RINGBUFFER_H
 

	
 
#include "framebuffer.h"
 

	
 
/// A fast buffer implementation for storing data.
 
class RingBuffer : public WFrameBuffer
 
{
 
public:
 
    RingBuffer(unsigned n);
 
    ~RingBuffer();
 

	
 
    virtual unsigned size() const;
 
    virtual double sample(unsigned i) const;
 
    virtual Range limits() const;
 
    virtual void resize(unsigned n);
 
    virtual void addSamples(double* samples, unsigned n);
 
    virtual void clear();
 

	
 
private:
 
    unsigned _size;            ///< size of `data`
 
    double* data;              ///< storage
 
    unsigned headIndex;        ///< indicates the actual `0` index of the ring buffer
 

	
 
    mutable bool limInvalid;   ///< Indicates that limits needs to be re-calculated
 
    mutable Range limCache;    ///< Cache for limits()
 
    void updateLimits() const; ///< Updates limits cache
 
};
 

	
 
#endif
src/samplecounter.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QDateTime>
 
#include "samplecounter.h"
 

	
 
SampleCounter::SampleCounter()
 
{
 
    prevTimeMs = QDateTime::currentMSecsSinceEpoch();
 
    count = 0;
 
}
 

	
 
#include <QtDebug>
 

	
 
void SampleCounter::feedIn(const SamplePack& data)
 
{
 
    count += data.numSamples();
 

	
 
    qint64 current = QDateTime::currentMSecsSinceEpoch();
 
    auto diff = current - prevTimeMs;
 
    if (diff > 1000) // 1sec
 
    {
 
        emit spsChanged(1000 * float(count) / diff);
 

	
 
        prevTimeMs = current;
 
        count = 0;
 
    }
 
}
src/samplecounter.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef SAMPLECOUNTER_H
 
#define SAMPLECOUNTER_H
 

	
 
#include <QObject>
 
#include "sink.h"
 

	
 
/// A `Sink` class for counting and reporting number of samples per second.
 
class SampleCounter : public QObject, public Sink
 
{
 
    Q_OBJECT
 

	
 
public:
 
    SampleCounter();
 

	
 
protected:
 
    // implementations for `Sink`
 
    virtual void feedIn(const SamplePack& data);
 

	
 
signals:
 
    /// Emitted per second if SPS value has changed.
 
    void spsChanged(float value);
 

	
 
private:
 
    qint64 prevTimeMs;
 
    unsigned count;
 
};
 

	
 
#endif // SAMPLECOUNTER_H
src/samplepack.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <cstring>
 
#include <QtGlobal>
 

	
 
#include "samplepack.h"
 

	
 
SamplePack::SamplePack(unsigned ns, unsigned nc, bool x)
 
{
 
    Q_ASSERT(ns > 0 && nc > 0);
 

	
 
    _numSamples = ns;
 
    _numChannels = nc;
 

	
 
    _yData = new double[_numSamples * _numChannels]();
 
    if (x)
 
    {
 
        _xData = new double[_numSamples]();
 
    }
 
    else
 
    {
 
        _xData = nullptr;
 
    }
 
}
 

	
 
SamplePack::SamplePack(const SamplePack& other) :
 
    SamplePack(other.numSamples(), other.numChannels(), other.hasX())
 
{
 
    size_t dataSize = sizeof(double) * numSamples();
 
    if (hasX())
 
        memcpy(xData(), other.xData(), dataSize);
 
    memcpy(_yData, other._yData, dataSize * numChannels());
 
}
 

	
 
SamplePack::~SamplePack()
 
{
 
    delete[] _yData;
 
    if (_xData != nullptr)
 
    {
 
        delete[] _xData;
 
    }
 
}
 

	
 
bool SamplePack::hasX() const
 
{
 
    return _xData != nullptr;
 
}
 

	
 
unsigned SamplePack::numChannels() const
 
{
 
    return _numChannels;
 
}
 

	
 
unsigned SamplePack::numSamples() const
 
{
 
    return _numSamples;
 
}
 

	
 
double* SamplePack::xData() const
 
{
 
    Q_ASSERT(_xData != nullptr);
 

	
 
    return _xData;
 
}
 

	
 
double* SamplePack::data(unsigned channel) const
 
{
 
    Q_ASSERT(channel < _numChannels);
 

	
 
    return &_yData[channel * _numSamples];
 
}
 

	
 
double* SamplePack::xData()
 
{
 
    return const_cast<double*>(static_cast<const SamplePack&>(*this).xData());
 
}
 

	
 
double* SamplePack::data(unsigned channel)
 
{
 
    return const_cast<double*>(static_cast<const SamplePack&>(*this).data(channel));
 
}
src/samplepack.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef SAMPLEPACK_H
 
#define SAMPLEPACK_H
 

	
 
class SamplePack
 
{
 
public:
 
    /**
 
     * @param ns number of samples
 
     * @param nc number of channels
 
     * @param x has X channel
 
     */
 
    SamplePack(unsigned ns, unsigned nc, bool x = false);
 
    SamplePack(const SamplePack& other);
 
    ~SamplePack();
 

	
 
    bool hasX() const;
 
    unsigned numChannels() const;
 
    unsigned numSamples() const;
 
    double* xData() const;
 
    double* data(unsigned channel) const;
 

	
 
    double* xData();
 
    double* data(unsigned channel);
 

	
 
private:
 
    unsigned _numSamples, _numChannels;
 
    double* _xData;
 
    double* _yData;
 
};
 

	
 
#endif // SAMPLEPACK_H
src/scrollzoomer.cpp
Show inline comments
 
@@ -33,41 +33,80 @@ public:
 
    ScrollBar *scrollBar;
 
    ScrollZoomer::ScrollBarPosition position;
 
    Qt::ScrollBarPolicy mode;
 
};
 

	
 
ScrollZoomer::ScrollZoomer( QWidget *canvas ):
 
    QwtPlotZoomer( canvas ),
 
    d_cornerWidget( NULL ),
 
    d_hScrollData( NULL ),
 
    d_vScrollData( NULL ),
 
    d_inZoom( false )
 
{
 
    xMin = 0.;
 
    xMax = 10000.;
 
    hViewSize = 10000;
 

	
 
    for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
 
        d_alignCanvasToScales[ axis ] = false;
 

	
 
    if ( !canvas )
 
        return;
 

	
 
    d_hScrollData = new ScrollData;
 
    d_vScrollData = new ScrollData;
 
    hscrollmove = false;
 
    vscrollmove = false;
 
}
 

	
 
ScrollZoomer::~ScrollZoomer()
 
{
 
    delete d_cornerWidget;
 
    delete d_vScrollData;
 
    delete d_hScrollData;
 
}
 

	
 
void ScrollZoomer::setXLimits(double min, double max)
 
{
 
    xMin = min;
 
    xMax = max;
 
    setZoomBase();
 
}
 

	
 
void ScrollZoomer::setHViewSize(double size)
 
{
 
    hscrollmove = true;
 
    hViewSize = size;
 
    setZoomBase();
 
    hscrollmove = false;
 
}
 

	
 
void ScrollZoomer::setZoomBase(bool doReplot)
 
{
 
    QwtPlotZoomer::setZoomBase(doReplot);
 
    auto zb = zoomBase();
 
    auto zs = zoomStack();
 
    zb.setRight(xMax);
 
    if ((xMax - xMin) < hViewSize)
 
    {
 
        zb.setLeft(xMin);
 
    }
 
    else
 
    {
 
        zb.setLeft(xMax-hViewSize);
 
    }
 
    zs[0] = zb;
 
    setZoomStack(zs);
 
}
 

	
 
void ScrollZoomer::rescale()
 
{
 
    QwtScaleWidget *xScale = plot()->axisWidget( xAxis() );
 
    QwtScaleWidget *yScale = plot()->axisWidget( yAxis() );
 

	
 
    if ( zoomRectIndex() <= 0 )
 
    {
 
        if ( d_inZoom )
 
        {
 
            xScale->setMinBorderDist( 0, 0 );
 
            yScale->setMinBorderDist( 0, 0 );
 

	
 
@@ -103,25 +142,63 @@ void ScrollZoomer::rescale()
 
            for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
 
            {
 
                d_alignCanvasToScales[axis] = 
 
                    layout->alignCanvasToScale( axis );
 
            }
 

	
 
            layout->setAlignCanvasToScales( false );
 

	
 
            d_inZoom = true;
 
        }
 
    }
 

	
 
    QwtPlotZoomer::rescale();
 
    // NOTE: Below snippet is copied from QwtPlotZoomer::rescale() just so that
 
    // we can refrain from updating y axis when moving horizontal scrollbar, so
 
    // that auto-scale isn't disrupted. Also we don't want to jump around in
 
    // x-axis when moving vertical scroll.
 
    {
 
        QwtPlot *plt = plot();
 
        if ( !plt )
 
            return;
 

	
 
        const QRectF &rect = zoomStack()[zoomRectIndex()];
 
        if ( rect != scaleRect() )
 
        {
 
            const bool doReplot = plt->autoReplot();
 
            plt->setAutoReplot( false );
 

	
 
            if (!vscrollmove)
 
            {
 
                double x1 = rect.left();
 
                double x2 = rect.right();
 
                if ( !plt->axisScaleDiv( xAxis() ).isIncreasing() )
 
                    qSwap( x1, x2 );
 

	
 
                plt->setAxisScale( xAxis(), x1, x2 );
 
            }
 

	
 
            if (!hscrollmove)
 
            {
 
                double y1 = rect.top();
 
                double y2 = rect.bottom();
 
                if ( !plt->axisScaleDiv( yAxis() ).isIncreasing() )
 
                    qSwap( y1, y2 );
 

	
 
                plt->setAxisScale( yAxis(), y1, y2 );
 

	
 
                plt->setAutoReplot( doReplot );
 
            }
 
            plt->replot();
 
        }
 
    }
 
    updateScrollBars();
 
}
 

	
 
ScrollBar *ScrollZoomer::scrollBar( Qt::Orientation orientation )
 
{
 
    ScrollBar *&sb = ( orientation == Qt::Vertical )
 
        ? d_vScrollData->scrollBar : d_hScrollData->scrollBar;
 

	
 
    if ( sb == NULL )
 
    {
 
        sb = new ScrollBar( orientation, canvas() );
 
        sb->hide();
 
@@ -228,24 +305,29 @@ bool ScrollZoomer::eventFilter( QObject 
 
            case QEvent::Resize:
 
            {
 
                int left, top, right, bottom;
 
                canvas()->getContentsMargins( &left, &top, &right, &bottom );
 

	
 
                QRect rect;
 
                rect.setSize( static_cast<QResizeEvent *>( event )->size() );
 
                rect.adjust( left, top, -right, -bottom );
 

	
 
                layoutScrollBars( rect );
 
                break;
 
            }
 
            case QEvent::Show:
 
            {
 
                layoutScrollBars( canvas()->contentsRect() );
 
                break;
 
            }
 
            case QEvent::ChildRemoved:
 
            {
 
                const QObject *child =
 
                    static_cast<QChildEvent *>( event )->child();
 

	
 
                if ( child == d_cornerWidget )
 
                    d_cornerWidget = NULL;
 
                else if ( child == d_hScrollData->scrollBar )
 
                    d_hScrollData->scrollBar = NULL;
 
                else if ( child == d_vScrollData->scrollBar )
 
                    d_vScrollData->scrollBar = NULL;
 
                break;
 
@@ -256,26 +338,26 @@ bool ScrollZoomer::eventFilter( QObject 
 
    }
 
    return QwtPlotZoomer::eventFilter( object, event );
 
}
 

	
 
bool ScrollZoomer::needScrollBar( Qt::Orientation orientation ) const
 
{
 
    Qt::ScrollBarPolicy mode;
 
    double zoomMin, zoomMax, baseMin, baseMax;
 

	
 
    if ( orientation == Qt::Horizontal )
 
    {
 
        mode = d_hScrollData->mode;
 
        baseMin = zoomBase().left();
 
        baseMax = zoomBase().right();
 
        baseMin = xMin;
 
        baseMax = xMax;
 
        zoomMin = zoomRect().left();
 
        zoomMax = zoomRect().right();
 
    }
 
    else
 
    {
 
        mode = d_vScrollData->mode;
 
        baseMin = zoomBase().top();
 
        baseMax = zoomBase().bottom();
 
        zoomMin = zoomRect().top();
 
        zoomMax = zoomRect().bottom();
 
    }
 

	
 
@@ -314,25 +396,25 @@ void ScrollZoomer::updateScrollBars()
 
    if ( vScrollBarPosition() == OppositeToScale )
 
        yScrollBarAxis = oppositeAxis( yScrollBarAxis );
 

	
 

	
 
    QwtPlotLayout *layout = plot()->plotLayout();
 

	
 
    bool showHScrollBar = needScrollBar( Qt::Horizontal );
 
    if ( showHScrollBar )
 
    {
 
        ScrollBar *sb = scrollBar( Qt::Horizontal );
 
        sb->setPalette( plot()->palette() );
 
        sb->setInverted( !plot()->axisScaleDiv( xAxis ).isIncreasing() );
 
        sb->setBase( zoomBase().left(), zoomBase().right() );
 
        sb->setBase( xMin, xMax );
 
        sb->moveSlider( zoomRect().left(), zoomRect().right() );
 

	
 
        if ( !sb->isVisibleTo( canvas() ) )
 
        {
 
            sb->show();
 
            layout->setCanvasMargin( layout->canvasMargin( xScrollBarAxis )
 
                + sb->extent(), xScrollBarAxis );
 
        }
 
    }
 
    else
 
    {
 
        if ( horizontalScrollBar() )
 
@@ -453,37 +535,72 @@ void ScrollZoomer::layoutScrollBars( con
 
                vdim, hdim );
 
            d_cornerWidget->setGeometry( cornerRect );
 
        }
 
    }
 
}
 

	
 
void ScrollZoomer::scrollBarMoved(
 
    Qt::Orientation o, double min, double max )
 
{
 
    Q_UNUSED( max );
 

	
 
    if ( o == Qt::Horizontal )
 
    {
 
        hscrollmove = true;
 
        moveTo( QPointF( min, zoomRect().top() ) );
 
        hscrollmove = false;
 
    }
 
    else
 
    {
 
        vscrollmove = true;
 
        moveTo( QPointF( zoomRect().left(), min ) );
 
        vscrollmove = false;
 
    }
 

	
 
    Q_EMIT zoomed( zoomRect() );
 
}
 

	
 
int ScrollZoomer::oppositeAxis( int axis ) const
 
{
 
    switch( axis )
 
    {
 
        case QwtPlot::xBottom:
 
            return QwtPlot::xTop;
 
        case QwtPlot::xTop:
 
            return QwtPlot::xBottom;
 
        case QwtPlot::yLeft:
 
            return QwtPlot::yRight;
 
        case QwtPlot::yRight:
 
            return QwtPlot::yLeft;
 
        default:
 
            break;
 
    }
 

	
 
    return axis;
 
}
 

	
 
void ScrollZoomer::moveTo( const QPointF &pos )
 
{
 
    // QwtPlotZoomer::moveTo(pos);
 
    // return;
 

	
 
    double x = pos.x();
 
    double y = pos.y();
 

	
 
    if ( x < xMin )
 
        x = xMin;
 
    if ( x > xMax - zoomRect().width() )
 
        x = xMax - zoomRect().width();
 

	
 
    if ( y < zoomBase().top() )
 
        y = zoomBase().top();
 
    if ( y > zoomBase().bottom() - zoomRect().height() )
 
        y = zoomBase().bottom() - zoomRect().height();
 

	
 
    if ( x != zoomRect().left() || y != zoomRect().top() )
 
    {
 
        auto zs = zoomStack();
 
        zs[zoomRectIndex()].moveTo( x, y );
 
        setZoomStack(zs, zoomRectIndex());
 
        rescale();
 
    }
 
}
src/scrollzoomer.h
Show inline comments
 
@@ -41,36 +41,48 @@ public:
 

	
 
    void setHScrollBarPosition( ScrollBarPosition );
 
    void setVScrollBarPosition( ScrollBarPosition );
 

	
 
    ScrollBarPosition hScrollBarPosition() const;
 
    ScrollBarPosition vScrollBarPosition() const;
 

	
 
    QWidget* cornerWidget() const;
 
    virtual void setCornerWidget( QWidget * );
 

	
 
    virtual bool eventFilter( QObject *, QEvent * );
 

	
 
    void setXLimits(double min, double max);
 
    void setHViewSize(double size);
 
    virtual void setZoomBase(bool doReplot = true);
 
    virtual void rescale();
 

	
 
public Q_SLOTS:
 
    virtual void moveTo( const QPointF & );
 

	
 
protected:
 
    virtual ScrollBar *scrollBar( Qt::Orientation );
 
    virtual void updateScrollBars();
 
    virtual void layoutScrollBars( const QRect & );
 

	
 
private Q_SLOTS:
 
    void scrollBarMoved( Qt::Orientation o, double min, double max );
 

	
 
private:
 
    QRectF d_limits;
 
    double xMin, xMax;
 
    double hViewSize;
 

	
 
    bool needScrollBar( Qt::Orientation ) const;
 
    int oppositeAxis( int ) const;
 

	
 
    QWidget *d_cornerWidget;
 

	
 
    ScrollData *d_hScrollData;
 
    ScrollData *d_vScrollData;
 

	
 
    bool d_inZoom;
 
    bool d_alignCanvasToScales[ QwtPlot::axisCnt ];
 
    bool hscrollmove;
 
    bool vscrollmove;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

	
 
const char SettingGroup_MainWindow[] = "MainWindow";
 
const char SettingGroup_Port[] = "Port";
 
const char SettingGroup_DataFormat[] = "DataFormat";
 
const char SettingGroup_Binary[] = "DataFormat_Binary";
 
const char SettingGroup_ASCII[] = "DataFormat_ASCII";
 
const char SettingGroup_CustomFrame[] = "DataFormat_CustomFrame";
 
const char SettingGroup_Channels[] = "Channels";
 
const char SettingGroup_Plot[] = "Plot";
 
const char SettingGroup_Commands[] = "Commands";
 
const char SettingGroup_Record[] = "Record";
 
const char SettingGroup_UpdateCheck[] = "UpdateCheck";
 

	
 
// mainwindow setting keys
 
const char SG_MainWindow_Size[] = "size";
 
const char SG_MainWindow_Pos[] = "pos";
 
const char SG_MainWindow_ActivePanel[] = "activePanel";
 
const char SG_MainWindow_HidePanels[] = "hidePanels";
 
const char SG_MainWindow_Maximized[] = "maximized";
 
const char SG_MainWindow_State[] = "state";
 

	
 
// port setting keys
 
const char SG_Port_SelectedPort[] = "selectedPort";
 
const char SG_Port_BaudRate[] = "baudRate";
 
@@ -48,43 +49,50 @@ const char SG_Port_StopBits[] = "stopBit
 
const char SG_Port_FlowControl[] = "flowControl";
 

	
 
// data format panel keys
 
const char SG_DataFormat_Format[] = "format";
 

	
 
// binary stream reader keys
 
const char SG_Binary_NumOfChannels[] = "numOfChannels";
 
const char SG_Binary_NumberFormat[] = "numberFormat";
 
const char SG_Binary_Endianness[] = "endianness";
 

	
 
// ascii reader keys
 
const char SG_ASCII_NumOfChannels[] = "numOfChannels";
 
const char SG_ASCII_Delimiter[] = "delimiter";
 
const char SG_ASCII_CustomDelimiter[] = "customDelimiter";
 

	
 
// framed reader keys
 
const char SG_CustomFrame_NumOfChannels[] = "numOfChannels";
 
const char SG_CustomFrame_FrameStart[] = "frameStart";
 
const char SG_CustomFrame_FixedSize[] = "fixedSize";
 
const char SG_CustomFrame_FrameSize[] = "frameSize";
 
const char SG_CustomFrame_NumberFormat[] = "numberFormat";
 
const char SG_CustomFrame_Endianness[] = "endianness";
 
const char SG_CustomFrame_Checksum[] = "checksum";
 
const char SG_CustomFrame_DebugMode[] = "debugMode";
 

	
 
// channel manager keys
 
// channel info keys
 
const char SG_Channels_Channel[] = "channel";
 
const char SG_Channels_Name[] = "name";
 
const char SG_Channels_Color[] = "color";
 
const char SG_Channels_Visible[] = "visible";
 
const char SG_Channels_Gain[] = "gain";
 
const char SG_Channels_GainEn[] = "gainEnabled";
 
const char SG_Channels_Offset[] = "offset";
 
const char SG_Channels_OffsetEn[] = "offsetEnabled";
 

	
 
// plot settings keys
 
const char SG_Plot_NumOfSamples[] = "numOfSamples";
 
const char SG_Plot_PlotWidth[] = "plotWidth";
 
const char SG_Plot_IndexAsX[] = "indexAsX";
 
const char SG_Plot_XMax[] = "xMax";
 
const char SG_Plot_XMin[] = "xMin";
 
const char SG_Plot_AutoScale[] = "autoScale";
 
const char SG_Plot_YMax[] = "yMax";
 
const char SG_Plot_YMin[] = "yMin";
 
const char SG_Plot_DarkBackground[] = "darkBackground";
 
const char SG_Plot_Grid[] = "grid";
 
const char SG_Plot_MinorGrid[] = "minorGrid";
 
const char SG_Plot_Legend[] = "legend";
 
const char SG_Plot_MultiPlot[] = "multiPlot";
 
const char SG_Plot_Symbols[] = "symbols";
 
@@ -93,14 +101,19 @@ const char SG_Plot_Symbols[] = "symbols"
 
const char SG_Commands_Command[] = "command";
 
const char SG_Commands_Name[] = "name";
 
const char SG_Commands_Type[] = "type";
 
const char SG_Commands_Data[] = "data";
 

	
 
// record panel settings keys
 
const char SG_Record_AutoIncrement[]    = "autoIncrement";
 
const char SG_Record_RecordPaused[]     = "recordPaused";
 
const char SG_Record_StopOnClose[]      = "stopOnClose";
 
const char SG_Record_Header[]           = "header";
 
const char SG_Record_Separator[]        = "separator";
 
const char SG_Record_DisableBuffering[] = "disableBuffering";
 
const char SG_Record_Timestamp[]        = "timestamp";
 

	
 
// update check settings keys
 
const char SG_UpdateCheck_Periodic[]  = "periodicCheck";
 
const char SG_UpdateCheck_LastCheck[] = "lastCheck";
 

	
 
#endif // SETTING_DEFINES_H
src/sink.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtGlobal>
 
#include "sink.h"
 

	
 
void Sink::connectFollower(Sink* sink)
 
{
 
    Q_ASSERT(!followers.contains(sink));
 

	
 
    followers.append(sink);
 
    sink->setNumChannels(_numChannels, _hasX);
 
}
 

	
 
void Sink::disconnectFollower(Sink* sink)
 
{
 
    Q_ASSERT(followers.contains(sink));
 

	
 
    followers.removeOne(sink);
 
}
 

	
 
void Sink::feedIn(const SamplePack& data)
 
{
 
    for (auto sink : followers)
 
    {
 
        sink->feedIn(data);
 
    }
 
}
 

	
 
void Sink::setNumChannels(unsigned nc, bool x)
 
{
 
    _numChannels = nc;
 
    _hasX = x;
 
    for (auto sink : followers)
 
    {
 
        sink->setNumChannels(nc, x);
 
    }
 
}
 

	
 
void Sink::setSource(Source* s)
 
{
 
    Q_ASSERT((source == nullptr) != (s == nullptr));
 
    source = s;
 
}
 

	
 
const Source* Sink::connectedSource() const
 
{
 
    return source;
 
}
 

	
 
Source* Sink::connectedSource()
 
{
 
    return const_cast<Source*>(static_cast<const Sink&>(*this).connectedSource());
 
}
src/sink.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef SINK_H
 
#define SINK_H
 

	
 
#include <QList>
 
#include "samplepack.h"
 

	
 
class Source;
 

	
 
class Sink
 
{
 
public:
 
    /// Placeholder virtual destructor
 
    virtual ~Sink() {};
 

	
 
    /// Connects a sink to get any data that this sink
 
    /// gets. Connecting an already connected sink is an error.
 
    void connectFollower(Sink* sink);
 

	
 
    /// Disconnects a follower. Disconnecting an unconnected sink is
 
    /// an error.
 
    void disconnectFollower(Sink* sink);
 

	
 
    /// Returns the connected source. `nullptr` if it's not connected.
 
    const Source* connectedSource() const;
 
    Source* connectedSource();
 

	
 
protected:
 
    /// Entry point for incoming data. Re-implementations should
 
    /// call this function to feed followers.
 
    virtual void feedIn(const SamplePack& data);
 

	
 
    /// Is set by connected source. Re-implementations should call
 
    /// this function to update followers.
 
    virtual void setNumChannels(unsigned nc, bool x);
 

	
 
    /// Set by the connected source when its connected. When
 
    /// disconnecting it's set to `nullptr`.
 
    ///
 
    /// @note Previous source is disconnected.
 
    ///
 
    /// @important Trying to connect a source while its already
 
    /// connected is an error.
 
    void setSource(Source* s);
 

	
 
    friend Source;
 

	
 
private:
 
    QList<Sink*> followers;
 
    Source* source = nullptr;   ///< source that this sink is connected to
 
    bool _hasX;
 
    unsigned _numChannels;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 
@@ -16,32 +16,32 @@
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <stddef.h>
 
#include <QSaveFile>
 
#include <QTextStream>
 

	
 
#include "mainwindow.h"
 
#include "snapshot.h"
 
#include "snapshotview.h"
 

	
 
Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel) :
 
Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved) :
 
    QObject(parent),
 
    cInfoModel(infoModel),
 
    _showAction(this),
 
    _deleteAction("&Delete", this)
 
{
 
    _name = name;
 
    _saved = false;
 
    _saved = saved;
 

	
 
    view = NULL;
 
    mainWindow = parent;
 
    _showAction.setText(displayName());
 
    connect(&_showAction, &QAction::triggered, this, &Snapshot::show);
 

	
 
    _deleteAction.setToolTip(QString("Delete ") + _name);
 
    connect(&_deleteAction, &QAction::triggered, this, &Snapshot::onDeleteTriggered);
 
}
 

	
 
Snapshot::~Snapshot()
 
{
 
@@ -99,61 +99,72 @@ QString Snapshot::displayName()
 
    {
 
        return name() + "*";
 
    }
 
}
 

	
 
void Snapshot::setName(QString name)
 
{
 
    _name = name;
 
    _showAction.setText(_name);
 
    emit nameChanged(this);
 
}
 

	
 
unsigned Snapshot::numChannels() const
 
{
 
    return yData.size();
 
}
 

	
 
unsigned Snapshot::numSamples() const
 
{
 
    return yData[0]->size();
 
}
 

	
 
const ChannelInfoModel* Snapshot::infoModel() const
 
{
 
    return &cInfoModel;
 
}
 

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

	
 
QString Snapshot::channelName(unsigned channel)
 
{
 
    return cInfoModel.name(channel);
 
}
 

	
 
void Snapshot::save(QString fileName)
 
{
 
    // TODO: remove code duplication (MainWindow::onExportCsv)
 
    QSaveFile file(fileName);
 

	
 
    if (file.open(QIODevice::WriteOnly | QIODevice::Text))
 
    {
 
        QTextStream fileStream(&file);
 

	
 
        unsigned numOfChannels = data.size();
 
        unsigned numOfSamples = data[0].size();
 

	
 
        // print header
 
        for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
        for (unsigned int ci = 0; ci < numChannels(); ci++)
 
        {
 
            fileStream << channelName(ci);
 
            if (ci != numOfChannels-1) fileStream << ",";
 
            if (ci != numChannels()-1) fileStream << ",";
 
        }
 
        fileStream << '\n';
 

	
 
        // print rows
 
        for (unsigned int i = 0; i < numOfSamples; i++)
 
        for (unsigned int i = 0; i < numSamples(); i++)
 
        {
 
            for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
            for (unsigned int ci = 0; ci < numChannels(); ci++)
 
            {
 
                fileStream << data[ci][i].y();
 
                if (ci != numOfChannels-1) fileStream << ",";
 
                fileStream << yData[ci]->sample(i);
 
                if (ci != numChannels()-1) fileStream << ",";
 
            }
 
            fileStream << '\n';
 
        }
 

	
 
        if (!file.commit())
 
        {
 
            qCritical() << "File save error during snapshot save: " << file.error();
 
        }
 
        else
 
        {
 
            _saved = true;
 
            _showAction.setText(displayName());
src/snapshot.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

	
 
#ifndef SNAPSHOT_H
 
#define SNAPSHOT_H
 

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

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

	
 
class SnapshotView;
 
class MainWindow;
 

	
 
class Snapshot : public QObject
 
{
 
    Q_OBJECT
 

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

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

	
 
    QString name();
 
    QString displayName(); ///< `name()` plus '*' if snapshot is not saved
 
    unsigned numChannels() const; ///< number of channels in this snapshot
 
    unsigned numSamples() const;  ///< number of samples in every channel
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 
    void setName(QString name);
 
    QString channelName(unsigned channel);
 

	
 
    void save(QString fileName); ///< save snapshot data as CSV
 
    bool isSaved(); ///< snapshot has been saved at least once
 

	
 
signals:
 
    void deleteRequested(Snapshot*);
 
    void nameChanged(Snapshot*);
 

	
 
private:
src/snapshotmanager.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QTime>
 
#include <QMenuBar>
 
#include <QKeySequence>
 
#include <QFileDialog>
 
#include <QFile>
 
#include <QTextStream>
 
#include <QVector>
 
#include <QPointF>
 
#include <QIcon>
 
#include <QtDebug>
 

	
 
#include "mainwindow.h"
 
#include "snapshotmanager.h"
 

	
 
SnapshotManager::SnapshotManager(MainWindow* mainWindow,
 
                                 ChannelManager* channelMan) :
 
                                 Stream* stream) :
 
    _menu("&Snapshots"),
 
    _takeSnapshotAction("&Take Snapshot", this),
 
    loadSnapshotAction("&Load Snapshots", this),
 
    clearAction("&Clear Snapshots", this)
 
{
 
    _mainWindow = mainWindow;
 
    _channelMan = channelMan;
 
    _stream = stream;
 

	
 
    _takeSnapshotAction.setToolTip("Take a snapshot of current plot");
 
    _takeSnapshotAction.setShortcut(QKeySequence("F5"));
 
    _takeSnapshotAction.setIcon(QIcon::fromTheme("camera"));
 
    loadSnapshotAction.setToolTip("Load snapshots from CSV files");
 
    clearAction.setToolTip("Delete all snapshots");
 
    connect(&_takeSnapshotAction, SIGNAL(triggered(bool)),
 
            this, SLOT(takeSnapshot()));
 
    connect(&clearAction, SIGNAL(triggered(bool)),
 
            this, SLOT(clearSnapshots()));
 
    connect(&loadSnapshotAction, SIGNAL(triggered(bool)),
 
            this, SLOT(loadSnapshots()));
 

	
 
    updateMenu();
 
}
 

	
 
SnapshotManager::~SnapshotManager()
 
{
 
    for (auto snapshot : snapshots)
 
    {
 
        delete snapshot;
 
    }
 
}
 

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

	
 
    unsigned numOfChannels = _channelMan->numOfChannels();
 
    unsigned numOfSamples = _channelMan->numOfSamples();
 
    auto snapshot = new Snapshot(_mainWindow, name, *(_stream->infoModel()));
 

	
 
    for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    for (unsigned ci = 0; ci < _stream->numChannels(); ci++)
 
    {
 
        snapshot->data.append(QVector<QPointF>(numOfSamples));
 
        for (unsigned i = 0; i < numOfSamples; i++)
 
        {
 
            snapshot->data[ci][i] = QPointF(i, _channelMan->channelBuffer(ci)->sample(i));
 
        }
 
        snapshot->yData.append(new ReadOnlyBuffer(_stream->channel(ci)->yData()));
 
    }
 

	
 
    return snapshot;
 
}
 

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

	
 
void SnapshotManager::addSnapshot(Snapshot* snapshot, bool update_menu)
 
{
 
@@ -149,62 +143,68 @@ void SnapshotManager::loadSnapshotFromFi
 
    {
 
        qCritical() << "Couldn't open file: " << fileName;
 
        qCritical() << file.errorString();
 
        return;
 
    }
 

	
 
    // read first row as headlines and determine number of channels
 
    auto headLine = QString(file.readLine());
 
    QStringList channelNames = headLine.split(',');
 
    unsigned numOfChannels = channelNames.size();
 

	
 
    // read data
 
    QVector<QVector<QPointF>> data(numOfChannels);
 
    QVector<QVector<double>> data(numOfChannels);
 
    QTextStream ts(&file);
 
    QString line;
 
    unsigned lineNum = 1;
 
    while (file.canReadLine())
 
    while (ts.readLineInto(&line))
 
    {
 
        // parse line
 
        auto line = QString(file.readLine());
 
        auto split = line.split(',');
 

	
 
        if (split.size() != (int) numOfChannels)
 
        {
 
            qCritical() << "Parsing error at line " << lineNum
 
                        << ": number of columns is not consistent.";
 
            qCritical() << "Line " << lineNum << ": " << line;
 
            return;
 
        }
 

	
 
        for (unsigned ci = 0; ci < numOfChannels; ci++)
 
        {
 
            // parse column
 
            bool ok;
 
            double y = split[ci].toDouble(&ok);
 
            if (!ok)
 
            {
 
                qCritical() << "Parsing error at line " << lineNum
 
                            << ", column " << ci
 
                            << ": can't convert \"" << split[ci]
 
                            << "\" to double.";
 
                return;
 
            }
 
            data[ci].append(QPointF(lineNum-1, y));
 
            data[ci].append(y);
 
        }
 
        lineNum++;
 
    }
 

	
 
    ChannelInfoModel channelInfo(channelNames);
 

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

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

	
 
    addSnapshot(snapshot, false);
 
}
 

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

	
 
QAction* SnapshotManager::takeSnapshotAction()
 
{
 
    return &_takeSnapshotAction;
src/snapshotmanager.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef SNAPSHOTMANAGER_H
 
#define SNAPSHOTMANAGER_H
 

	
 
#include <QObject>
 
#include <QAction>
 
#include <QMenu>
 

	
 
#include "framebuffer.h"
 
#include "channelmanager.h"
 
#include "stream.h"
 
#include "snapshot.h"
 

	
 
class MainWindow;
 

	
 
class SnapshotManager : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    SnapshotManager(MainWindow* mainWindow, ChannelManager* channelMan);
 
    SnapshotManager(MainWindow* mainWindow, Stream* stream);
 
    ~SnapshotManager();
 

	
 
    QMenu* menu();
 
    QAction* takeSnapshotAction();
 

	
 
    /// Creates a dynamically allocated snapshot object but doesn't record it in snapshots list.
 
    /// @note Caller is responsible for deletion of the returned `Snapshot` object.
 
    Snapshot* makeSnapshot();
 
    Snapshot* makeSnapshot() const;
 

	
 
    bool isAllSaved(); ///< returns `true` if all snapshots are saved to a file
 

	
 
private:
 
    MainWindow* _mainWindow;
 
    ChannelManager* _channelMan;
 
    Stream* _stream;
 

	
 
    QList<Snapshot*> snapshots;
 

	
 
    QMenu _menu;
 
    QAction _takeSnapshotAction;
 
    QAction loadSnapshotAction;
 
    QAction clearAction;
 

	
 
    void addSnapshot(Snapshot* snapshot, bool update_menu=true);
 
    void updateMenu();
 

	
 
private slots:
src/snapshotview.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "snapshotview.h"
 
#include "ui_snapshotview.h"
 

	
 
SnapshotView::SnapshotView(MainWindow* parent, Snapshot* snapshot) :
 
    QMainWindow(parent),
 
    ui(new Ui::SnapshotView),
 
    renameDialog(this)
 
    renameDialog(this),
 
    plotMenu(parent->viewSettings())
 
{
 
    _snapshot = snapshot;
 

	
 
    ui->setupUi(this);
 

	
 
    plotMan = new PlotManager(ui->plotArea, snapshot->infoModel(), this);
 
    plotMan->setViewSettings(parent->viewSettings());
 
    plotMan = new PlotManager(ui->plotArea, &plotMenu, snapshot, this);
 

	
 
    ui->menuSnapshot->insertAction(ui->actionClose, snapshot->deleteAction());
 
    this->setWindowTitle(snapshot->displayName());
 

	
 
    // initialize curves
 
    unsigned numOfChannels = snapshot->data.size();
 
    unsigned numOfSamples = snapshot->data[0].size();
 
    for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    {
 
        plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
 
    }
 
    plotMan->setNumOfSamples(numOfSamples);
 
    // unsigned numOfChannels = snapshot->data.size();
 
    // unsigned numOfSamples = snapshot->data[0].size();
 
    // for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    // {
 
    //     plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
 
    // }
 
    // plotMan->setNumOfSamples(numOfSamples);
 
    // plotMan->setPlotWidth(numOfSamples);
 

	
 
    renameDialog.setWindowTitle("Rename Snapshot");
 
    renameDialog.setLabelText("Enter new name:");
 
    connect(ui->actionRename, &QAction::triggered,
 
            this, &SnapshotView::showRenameDialog);
 

	
 
    connect(ui->actionSave, &QAction::triggered,
 
            this, &SnapshotView::save);
 

	
 
    // add 'View' menu items
 
    for (auto a : plotMan->menuActions())
 
    {
 
        ui->menuView->addAction(a);
 
    }
 
    // add "View" menu
 
    menuBar()->insertMenu(NULL, &plotMenu);
 
}
 

	
 
SnapshotView::~SnapshotView()
 
{
 
    for (auto curve : curves)
 
    {
 
        delete curve;
 
    }
 
    delete plotMan;
 
    delete ui;
 
}
 

	
src/snapshotview.h
Show inline comments
 
@@ -22,45 +22,47 @@
 

	
 
#include <QMainWindow>
 
#include <QInputDialog>
 
#include <QFileDialog>
 
#include <QVector>
 
#include <QPointF>
 
#include <QPen>
 
#include <QCloseEvent>
 
#include <qwt_plot_curve.h>
 

	
 
#include "mainwindow.h"
 
#include "plotmanager.h"
 
#include "plotmenu.h"
 
#include "snapshot.h"
 

	
 
namespace Ui {
 
class SnapshotView;
 
}
 

	
 
class SnapshotView : public QMainWindow
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit SnapshotView(MainWindow* parent, Snapshot* snapshot);
 
    ~SnapshotView();
 

	
 
signals:
 
    void closed();
 

	
 
private:
 
    Ui::SnapshotView *ui;
 
    QList<QwtPlotCurve*> curves;
 
    Snapshot* _snapshot;
 
    QInputDialog renameDialog;
 
    PlotManager* plotMan;
 
    PlotMenu plotMenu;
 

	
 
    void closeEvent(QCloseEvent *event);
 

	
 
private slots:
 
    void showRenameDialog();
 
    void renameSnapshot(QString name);
 
    void save();
 
};
 

	
 
#endif // SNAPSHOTVIEW_H
src/snapshotview.ui
Show inline comments
 
@@ -17,42 +17,36 @@
 
   <layout class="QVBoxLayout" name="verticalLayout">
 
    <item>
 
     <widget class="QWidget" name="plotArea" native="true"/>
 
    </item>
 
   </layout>
 
  </widget>
 
  <widget class="QMenuBar" name="menubar">
 
   <property name="geometry">
 
    <rect>
 
     <x>0</x>
 
     <y>0</y>
 
     <width>544</width>
 
     <height>25</height>
 
     <height>24</height>
 
    </rect>
 
   </property>
 
   <widget class="QMenu" name="menuSnapshot">
 
    <property name="title">
 
     <string>&amp;Snapshot</string>
 
    </property>
 
    <addaction name="actionRename"/>
 
    <addaction name="actionSave"/>
 
    <addaction name="actionClose"/>
 
   </widget>
 
   <widget class="QMenu" name="menuView">
 
    <property name="title">
 
     <string>&amp;View</string>
 
    </property>
 
   </widget>
 
   <addaction name="menuSnapshot"/>
 
   <addaction name="menuView"/>
 
  </widget>
 
  <action name="actionSave">
 
   <property name="text">
 
    <string>&amp;Save as CSV</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Save snapshot as CSV file</string>
 
   </property>
 
  </action>
 
  <action name="actionRename">
 
   <property name="text">
 
    <string>&amp;Rename</string>
src/source.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtGlobal>
 

	
 
#include "source.h"
 

	
 
Source::~Source()
 
{
 
    for (auto sink : sinks)
 
    {
 
        sink->setSource(nullptr);
 
    }
 
}
 

	
 
void Source::connectSink(Sink* sink)
 
{
 
    Q_ASSERT(!sinks.contains(sink));
 

	
 
    auto prevSource = sink->connectedSource();
 
    if (prevSource != nullptr)
 
    {
 
        prevSource->disconnect(sink);
 
    }
 

	
 
    sinks.append(sink);
 
    sink->setSource(this);
 
    sink->setNumChannels(numChannels(), hasX());
 
}
 

	
 
void Source::disconnect(Sink* sink)
 
{
 
    Q_ASSERT(sinks.contains(sink));
 
    Q_ASSERT(sink->connectedSource() == this);
 

	
 
    sink->setSource(nullptr);
 
    sinks.removeOne(sink);
 
}
 

	
 
void Source::disconnectSinks()
 
{
 
    while (!sinks.isEmpty())
 
    {
 
        auto sink = sinks.takeFirst();
 
        sink->setSource(nullptr);
 
    }
 
}
 

	
 
void Source::feedOut(const SamplePack& data) const
 
{
 
    for (auto sink : sinks)
 
    {
 
        sink->feedIn(data);
 
    }
 
}
 

	
 
void Source::updateNumChannels() const
 
{
 
    for (auto sink : sinks)
 
    {
 
        sink->setNumChannels(numChannels(), hasX());
 
    }
 
}
src/source.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef SOURCE_H
 
#define SOURCE_H
 

	
 
#include <QList>
 

	
 
#include "sink.h"
 
#include "samplepack.h"
 

	
 
class Source
 
{
 
public:
 
    /// Virtual destructor. Must be called by implementors to notify sinks.
 
    virtual ~Source();
 

	
 
    /// Returns true if source has X data
 
    virtual bool hasX() const = 0;
 

	
 
    /// Returns number of channels
 
    virtual unsigned numChannels() const = 0;
 

	
 
    /// Connects a sink to this source.
 
    ///
 
    /// If `Sink` is already connected to a source, it's disconnected first.
 
    void connectSink(Sink* sink);
 

	
 
    /// Disconnects an already connected sink. Trying to disconnect an
 
    /// unconnected sink is an error.
 
    void disconnect(Sink* sink);
 

	
 
    /// Disconnects all connected sinks.
 
    void disconnectSinks();
 

	
 
protected:
 
    /// Feeds "in" given data to connected sinks
 
    virtual void feedOut(const SamplePack& data) const;
 

	
 
    /// Updates "number of channels" of connected sinks. Must be
 
    /// called when num. channels or hasX changes.
 
    void updateNumChannels() const;
 

	
 
private:
 
    QList<Sink*> sinks;
 
};
 

	
 
#endif // SOURCE_H
src/stream.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

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

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

	
 
    // create xdata buffer
 
    _hasx = x;
 
    if (x)
 
    {
 
        xData = new RingBuffer(ns);
 
    }
 
    else
 
    {
 
        xData = new IndexBuffer(ns);
 
    }
 

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

	
 
Stream::~Stream()
 
{
 
    for (auto ch : channels)
 
    {
 
        delete ch;
 
    }
 
    delete xData;
 
}
 

	
 
bool Stream::hasX() const
 
{
 
    return _hasx;
 
}
 

	
 
unsigned Stream::numChannels() const
 
{
 
    return channels.length();
 
}
 

	
 
unsigned Stream::numSamples() const
 
{
 
    return _numSamples;
 
}
 

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

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

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

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

	
 
void Stream::setNumChannels(unsigned nc, bool x)
 
{
 
    unsigned oldNum = numChannels();
 
    if (oldNum == nc && x == _hasx) return;
 

	
 
    // adjust the number of channels
 
    if (nc > oldNum)
 
    {
 
        for (unsigned i = oldNum; i < nc; i++)
 
        {
 
            auto c = new StreamChannel(i, xData, new RingBuffer(_numSamples), &_infoModel);
 
            channels.append(c);
 
        }
 
    }
 
    else if (nc < oldNum)
 
    {
 
        for (unsigned i = oldNum-1; i > nc-1; i--)
 
        {
 
            delete channels.takeLast();
 
        }
 
    }
 

	
 
    // change the xdata
 
    if (x != _hasx)
 
    {
 
        if (x)
 
        {
 
            xData = new RingBuffer(_numSamples);
 
        }
 
        else
 
        {
 
            xData = new IndexBuffer(_numSamples);
 
        }
 

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

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

	
 
    Sink::setNumChannels(nc, x);
 
}
 

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

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

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        // TODO: we could use some kind of map (int32, int64 would suffice) to speed things up
 
        bool gainEn = infoModel()->gainEn(ci);
 
        bool offsetEn = infoModel()->offsetEn(ci);
 
        if (gainEn || offsetEn)
 
        {
 
            double* mdata = mPack->data(ci);
 

	
 
            double gain = infoModel()->gain(ci);
 
            double offset = infoModel()->offset(ci);
 

	
 
            if (gainEn)
 
            {
 
                for (unsigned i = 0; i < ns; i++)
 
                {
 
                    mdata[i] *= gain;
 
                }
 
            }
 
            if (offsetEn)
 
            {
 
                for (unsigned i = 0; i < ns; i++)
 
                {
 
                    mdata[i] += offset;
 
                }
 
            }
 
        }
 
    }
 

	
 
    return mPack;
 
}
 

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

	
 
    if (_paused) return;
 

	
 
    unsigned ns = pack.numSamples();
 
    if (_hasx)
 
    {
 
        static_cast<RingBuffer*>(xData)->addSamples(pack.xData(), ns);
 
    }
 

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

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        auto buf = static_cast<RingBuffer*>(channels[ci]->yData());
 
        double* data = (mPack == nullptr) ? pack.data(ci) : mPack->data(ci);
 
        buf->addSamples(data, ns);
 
    }
 

	
 
    Sink::feedIn((mPack == nullptr) ? pack : *mPack);
 

	
 
    if (mPack != nullptr) delete mPack;
 
    emit dataAdded();
 
}
 

	
 
void Stream::pause(bool paused)
 
{
 
    _paused = paused;
 
}
 

	
 
void Stream::clear()
 
{
 
    for (auto c : channels)
 
    {
 
        static_cast<RingBuffer*>(c->yData())->clear();
 
    }
 
}
 

	
 
void Stream::setNumSamples(unsigned value)
 
{
 
    if (value == _numSamples) return;
 
    _numSamples = value;
 

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

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

	
 
void Stream::loadSettings(QSettings* settings)
 
{
 
    _infoModel.loadSettings(settings);
 
}
src/stream.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef STREAM_H
 
#define STREAM_H
 

	
 
#include <QObject>
 
#include <QModelIndex>
 
#include <QVector>
 
#include <QSettings>
 

	
 
#include "sink.h"
 
#include "source.h"
 
#include "channelinfomodel.h"
 
#include "streamchannel.h"
 
#include "framebuffer.h"
 

	
 
/**
 
 * Main waveform storage class. It consists of channels. Channels are
 
 * synchronized with each other.
 
 *
 
 * Implements `Sink` class for data entry. It's expected to be
 
 * connected to a `Device` source.
 
 */
 
class Stream : public QObject, public Sink
 
{
 
    Q_OBJECT
 

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

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

	
 
    unsigned numSamples() const;
 
    const StreamChannel* channel(unsigned index) const;
 
    StreamChannel* channel(unsigned index);
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 

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

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

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

	
 
public slots:
 
    // TODO: these won't be public
 
    // void setNumChannels(unsigned number);
 
    void setNumSamples(unsigned value);
 

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

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

	
 
private:
 
    unsigned _numSamples;
 
    bool _paused;
 

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

	
 
    ChannelInfoModel _infoModel;
 

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

	
 

	
 
#endif // STREAM_H
src/streamchannel.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "streamchannel.h"
 

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

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

	
 
unsigned StreamChannel::index() const {return _index;}
 
QString StreamChannel::name() const {return _info->name(_index);};
 
QColor StreamChannel::color() const {return _info->color(_index);};
 
bool StreamChannel::visible() const {return _info->isVisible(_index);};
 
const FrameBuffer* StreamChannel::xData() const {return _x;}
 
const FrameBuffer* StreamChannel::yData() const {return _y;}
 
FrameBuffer* StreamChannel::yData() {return _y;}
 
const ChannelInfoModel* StreamChannel::info() const {return _info;}
 
void StreamChannel::setX(const FrameBuffer* x) {_x = x;};
src/streamchannel.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef STREAMCHANNEL_H
 
#define STREAMCHANNEL_H
 

	
 
#include "framebuffer.h"
 
#include "channelinfomodel.h"
 

	
 
class StreamChannel
 
{
 
public:
 
    /**
 
     * Creates a stream channel.
 
     *
 
     * @param i index of the channel
 
     * @param x x axis buffer
 
     * @param y data buffer of this channel, takes ownership
 
     * @param info channel info model
 
     */
 
    StreamChannel(unsigned i,
 
                  const FrameBuffer* x,
 
                  FrameBuffer* y,
 
                  ChannelInfoModel* info);
 
    ~StreamChannel();
 

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

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

	
 
#endif // STREAMCHANNEL_H
src/updatecheckdialog.cpp
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "setting_defines.h"
 
#include "updatecheckdialog.h"
 
#include "ui_updatecheckdialog.h"
 

	
 
UpdateCheckDialog::UpdateCheckDialog(QWidget *parent) :
 
    QDialog(parent),
 
    ui(new Ui::UpdateCheckDialog)
 
{
 
    ui->setupUi(this);
 

	
 
    // by default start from yesterday, so that we check at first run
 
    lastCheck = QDate::currentDate().addDays(-1);
 

	
 
    connect(&updateChecker, &UpdateChecker::checkFailed,
 
            [this](QString errorMessage)
 
            {
 
                lastCheck = QDate::currentDate();
 
                ui->label->setText(QString("Update check failed.\n") + errorMessage);
 
            });
 

	
 
    connect(&updateChecker, &UpdateChecker::checkFinished,
 
            [this](bool found, QString newVersion, QString downloadUrl)
 
            {
 
                QString text;
 
                if (!found)
 
                {
 
                    text = "There is no update yet.";
 
                }
 
                else
 
                {
 
                    show();
 
#ifdef UPDATE_TYPE_PKGMAN
 
                    text = QString("There is a new version: %1. "
 
                                   "Use your package manager to update"
 
                                   " or click to <a href=\"%2\">download</a>.")\
 
                        .arg(newVersion).arg(downloadUrl);
 
#else
 
                    text = QString("Found update to version %1. Click to <a href=\"%2\">download</a>.")\
 
                        .arg(newVersion).arg(downloadUrl);
 
#endif
 
                }
 

	
 
                lastCheck = QDate::currentDate();
 
                ui->label->setText(text);
 
            });
 
}
 

	
 
UpdateCheckDialog::~UpdateCheckDialog()
 
{
 
    delete ui;
 
}
 

	
 
void UpdateCheckDialog::showEvent(QShowEvent *event)
 
{
 
    updateChecker.checkUpdate();
 
    ui->label->setText("Checking update...");
 
}
 

	
 
void UpdateCheckDialog::closeEvent(QShowEvent *event)
 
{
 
    if (updateChecker.isChecking()) updateChecker.cancelCheck();
 
}
 

	
 
void UpdateCheckDialog::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_UpdateCheck);
 
    settings->setValue(SG_UpdateCheck_Periodic, ui->cbPeriodic->isChecked());
 
    settings->setValue(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate));
 
    settings->endGroup();
 
}
 

	
 
void UpdateCheckDialog::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_UpdateCheck);
 
    ui->cbPeriodic->setChecked(settings->value(SG_UpdateCheck_Periodic,
 
                                               ui->cbPeriodic->isChecked()).toBool());
 
    auto lastCheckS = settings->value(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate)).toString();
 
    lastCheck = QDate::fromString(lastCheckS, Qt::ISODate);
 
    settings->endGroup();
 

	
 
    // start the periodic update if required
 
    if (ui->cbPeriodic->isChecked() && lastCheck < QDate::currentDate())
 
    {
 
        updateChecker.checkUpdate();
 
    }
 
}
src/updatecheckdialog.h
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef UPDATECHECKDIALOG_H
 
#define UPDATECHECKDIALOG_H
 

	
 
#include <QDialog>
 
#include <QDate>
 
#include <QSettings>
 
#include "updatechecker.h"
 

	
 
namespace Ui {
 
class UpdateCheckDialog;
 
}
 

	
 
class UpdateCheckDialog : public QDialog
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit UpdateCheckDialog(QWidget *parent = 0);
 
    ~UpdateCheckDialog();
 

	
 
    /// Stores update settings into a `QSettings`.
 
    void saveSettings(QSettings* settings);
 
    /// Loads update settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
private:
 
    Ui::UpdateCheckDialog *ui;
 
    UpdateChecker updateChecker;
 
    QDate lastCheck;
 

	
 
    void showEvent(QShowEvent *event);
 
    void closeEvent(QShowEvent *event);
 
};
 

	
 
#endif // UPDATECHECKDIALOG_H
src/updatecheckdialog.ui
Show inline comments
 
new file 100644
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>UpdateCheckDialog</class>
 
 <widget class="QDialog" name="UpdateCheckDialog">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>400</width>
 
    <height>148</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Check Update</string>
 
  </property>
 
  <layout class="QVBoxLayout" name="verticalLayout">
 
   <item>
 
    <widget class="QLabel" name="label">
 
     <property name="text">
 
      <string>Checking update...</string>
 
     </property>
 
     <property name="openExternalLinks">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QCheckBox" name="cbPeriodic">
 
     <property name="toolTip">
 
      <string>Updates will be checked only once a day at first start of the application</string>
 
     </property>
 
     <property name="text">
 
      <string>Check updates periodically</string>
 
     </property>
 
     <property name="checked">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QDialogButtonBox" name="buttonBox">
 
     <property name="orientation">
 
      <enum>Qt::Horizontal</enum>
 
     </property>
 
     <property name="standardButtons">
 
      <set>QDialogButtonBox::Close</set>
 
     </property>
 
    </widget>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections>
 
  <connection>
 
   <sender>buttonBox</sender>
 
   <signal>clicked(QAbstractButton*)</signal>
 
   <receiver>UpdateCheckDialog</receiver>
 
   <slot>close()</slot>
 
   <hints>
 
    <hint type="sourcelabel">
 
     <x>199</x>
 
     <y>125</y>
 
    </hint>
 
    <hint type="destinationlabel">
 
     <x>199</x>
 
     <y>73</y>
 
    </hint>
 
   </hints>
 
  </connection>
 
 </connections>
 
</ui>
src/updatechecker.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QJsonDocument>
 
#include <QJsonObject>
 
#include <QJsonArray>
 
#include <QJsonValue>
 
#include <QRegularExpression>
 
#include <algorithm>
 
#include <functional>
 

	
 
#include "updatechecker.h"
 

	
 
// This link returns the list of downloads in JSON format. Note that we only use
 
// the first page because results are sorted new to old.
 
const char BB_DOWNLOADS_URL[] = "https://api.bitbucket.org/2.0/repositories/hyozd/serialplot/downloads?fields=values.name,values.links.self.href";
 

	
 
UpdateChecker::UpdateChecker(QObject *parent) :
 
    QObject(parent), nam(this)
 
{
 
    activeReply = NULL;
 

	
 
    connect(&nam, &QNetworkAccessManager::finished,
 
            this, &UpdateChecker::onReqFinished);
 
}
 

	
 
bool UpdateChecker::isChecking() const
 
{
 
    return activeReply != NULL && !activeReply->isFinished();
 
}
 

	
 
void UpdateChecker::checkUpdate()
 
{
 
    if (isChecking()) return;
 

	
 
    auto req = QNetworkRequest(QUrl(BB_DOWNLOADS_URL));
 
    activeReply = nam.get(req);
 
}
 

	
 
void UpdateChecker::cancelCheck()
 
{
 
    if (activeReply != NULL) activeReply->abort();
 
}
 

	
 
void UpdateChecker::onReqFinished(QNetworkReply* reply)
 
{
 
    if (reply->error() != QNetworkReply::NoError)
 
    {
 
        emit checkFailed(QString("Network error: ") + reply->errorString());
 
    }
 
    else
 
    {
 
        QJsonParseError error;
 
        auto data = QJsonDocument::fromJson(reply->readAll(), &error);
 
        if (error.error != QJsonParseError::NoError)
 
        {
 
            emit checkFailed(QString("JSon parsing error: ") + error.errorString());
 
        }
 
        else
 
        {
 
            QList<FileInfo> files;
 
            if (!parseData(data, files))
 
            {
 
                // TODO: emit detailed data contents for logging
 
                emit checkFailed("Data parsing error.");
 
            }
 
            else
 
            {
 
                FileInfo updateFile;
 
                if (findUpdate(files, updateFile))
 
                {
 
                    emit checkFinished(
 
                        true, updateFile.version.toString(), updateFile.link);
 
                }
 
                else
 
                {
 
                    emit checkFinished(false, "", "");
 
                }
 
            }
 
        }
 
    }
 
    reply->deleteLater();
 
    activeReply = NULL;
 
}
 

	
 
bool UpdateChecker::parseData(const QJsonDocument& data, QList<FileInfo>& files) const
 
{
 
    /* Data is expected to be in this form:
 

	
 
    {
 
       "values": [
 
       {
 
         "name": "serialplot-0.9.1-x86_64.AppImage",
 
         "links": {
 
           "self": {
 
             "href": "https://api.bitbucket.org/2.0/repositories/hyOzd/serialplot/downloads/serialplot-0.9.1-x86_64.AppImage"
 
            }
 
          }
 
       }, ... ]
 
    }
 
    */
 

	
 
    if (!data.isObject()) return false;
 

	
 
    auto values = data.object()["values"];
 
    if (values == QJsonValue::Undefined || !values.isArray()) return false;
 

	
 
    for (auto value : values.toArray())
 
    {
 
        if (!value.isObject()) return false;
 

	
 
        auto name = value.toObject().value("name");
 
        if (name.isUndefined() || !name.isString())
 
             return false;
 

	
 
        auto links = value.toObject().value("links");
 
        if (links.isUndefined() || !links.isObject())
 
            return false;
 

	
 
        auto self = links.toObject().value("self");
 
        if (self.isUndefined() || !self.isObject())
 
            return false;
 

	
 
        auto href = self.toObject().value("href");
 
        if (href.isUndefined() || !href.isString())
 
            return false;
 

	
 
        FileInfo finfo;
 
        finfo.name = name.toString();
 
        finfo.link = href.toString();
 
        finfo.hasVersion = VersionNumber::extract(name.toString(), finfo.version);
 

	
 
        if (finfo.name.contains("amd64") ||
 
            finfo.name.contains("x86_64") ||
 
            finfo.name.contains("win64"))
 
        {
 
            finfo.arch = FileArch::amd64;
 
        }
 
        else if (finfo.name.contains("win32") ||
 
                 finfo.name.contains("i386"))
 
        {
 
            finfo.arch = FileArch::_i386;
 
        }
 
        else
 
        {
 
            finfo.arch = FileArch::unknown;
 
        }
 

	
 
        files += finfo;
 
    }
 

	
 
    return true;
 
}
 

	
 
bool UpdateChecker::findUpdate(const QList<FileInfo>& files, FileInfo& foundFile) const
 
{
 
    QList<FileInfo> fflist;
 

	
 
    // filter the file list according to extension and version number
 
    for (int i = 0; i < files.length(); i++)
 
    {
 
        // file type to look
 
#if defined(Q_OS_WIN)
 
        const char ext[] = ".exe";
 
#else  // of course linux
 
        const char ext[] = ".appimage";
 
#endif
 

	
 
        // file architecture to look
 
#if defined(Q_PROCESSOR_X86_64)
 
        const FileArch arch = FileArch::amd64;
 
#elif defined(Q_PROCESSOR_X86_32)
 
        const FileArch arch = FileArch::_i386;
 
#elif defined(Q_PROCESSOR_ARM)
 
        const FileArch arch = FileArch::arm;
 
#else
 
        #error Unknown architecture for update file detection.
 
#endif
 

	
 
        // filter the file list
 
        auto file = files[i];
 
        if (file.name.contains(ext, Qt::CaseInsensitive) &&
 
            file.arch == arch &&
 
            file.hasVersion && file.version > CurrentVersion)
 
        {
 
            fflist += file;
 
        }
 
    }
 

	
 
    // sort and find most up to date file
 
    if (!fflist.empty())
 
    {
 
        std::sort(fflist.begin(), fflist.end(),
 
                  [](const FileInfo& a, const FileInfo& b)
 
                  {
 
                      return a.version > b.version;
 
                  });
 

	
 
        foundFile = fflist[0];
 
        return true;
 
    }
 
    else
 
    {
 
        return false;
 
    }
 
}
src/updatechecker.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef UPDATECHECKER_H
 
#define UPDATECHECKER_H
 

	
 
#include <QObject>
 
#include <QNetworkAccessManager>
 
#include <QNetworkReply>
 
#include <QList>
 

	
 
#include "versionnumber.h"
 

	
 
class UpdateChecker : public QObject
 
{
 
    Q_OBJECT
 
public:
 
    explicit UpdateChecker(QObject *parent = 0);
 

	
 
    bool isChecking() const;
 

	
 
signals:
 
    void checkFinished(bool found, QString newVersion, QString downloadUrl);
 
    void checkFailed(QString errorMessage);
 

	
 
public slots:
 
    void checkUpdate();
 
    void cancelCheck();
 

	
 
private:
 
    enum class FileArch
 
    {
 
        unknown,
 
        _i386,
 
        amd64,
 
        arm
 
    };
 

	
 
    struct FileInfo
 
    {
 
        QString name;
 
        QString link;
 
        bool hasVersion;
 
        VersionNumber version;
 
        FileArch arch;
 
    };
 

	
 
    QNetworkAccessManager nam;
 
    QNetworkReply* activeReply;
 

	
 
    /// Parses json and creates a list of files
 
    bool parseData(const QJsonDocument& data, QList<FileInfo>& files) const;
 
    /// Finds the update file in the file list. Returns `-1` if no new version
 
    /// is found.
 
    bool findUpdate(const QList<FileInfo>& files, FileInfo& foundFile) const;
 

	
 
private slots:
 
    void onReqFinished(QNetworkReply* reply);
 
};
 

	
 
#endif // UPDATECHECKER_H
src/versionnumber.cpp
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QRegularExpression>
 

	
 
#include "versionnumber.h"
 

	
 
VersionNumber::VersionNumber(unsigned mj, unsigned mn, unsigned pt)
 
{
 
    major = mj;
 
    minor = mn;
 
    patch = pt;
 
}
 

	
 
QString VersionNumber::toString() const
 
{
 
    return QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
 
}
 

	
 
bool VersionNumber::extract(const QString& str, VersionNumber& number)
 
{
 
    QRegularExpression regexp("(?:[-_vV \\t]|^)(?<major>\\d+)"
 
                              "(?:\\.(?<minor>\\d+))(?:\\.(?<patch>\\d+))?[-_ \\t]?");
 
    auto match = regexp.match(str, 0, QRegularExpression::PartialPreferCompleteMatch);
 

	
 
    if (!(match.hasMatch() || match.hasPartialMatch())) return false;
 

	
 
    number.major = match.captured("major").toUInt();
 

	
 
    auto zeroIfNull = [](QString str) -> unsigned
 
        {
 
            if (str.isNull()) return 0;
 
            return str.toUInt();
 
        };
 

	
 
    number.minor = zeroIfNull(match.captured("minor"));
 
    number.patch = zeroIfNull(match.captured("patch"));
 

	
 
    return true;
 
}
 

	
 
bool operator==(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    return lhs.major == rhs.major &&
 
           lhs.minor == rhs.minor &&
 
           lhs.patch == rhs.patch;
 
}
 

	
 
bool operator<(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    if (lhs.major < rhs.major)
 
    {
 
        return true;
 
    }
 
    else if (lhs.major == rhs.major)
 
    {
 
        if (lhs.minor < rhs.minor)
 
        {
 
             return true;
 
        }
 
        else if (lhs.minor == rhs.minor)
 
        {
 
            if (lhs.patch < rhs.patch) return true;
 
        }
 
    }
 
    return false;
 
}
 

	
 
bool operator>(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    if (lhs.major > rhs.major)
 
    {
 
        return true;
 
    }
 
    else if (lhs.major == rhs.major)
 
    {
 
        if (lhs.minor > rhs.minor)
 
        {
 
             return true;
 
        }
 
        else if (lhs.minor == rhs.minor)
 
        {
 
            if (lhs.patch > rhs.patch) return true;
 
        }
 
    }
 
    return false;
 
}
src/versionnumber.h
Show inline comments
 
new file 100644
 
/*
 
  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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef VERSIONNUMBER_H
 
#define VERSIONNUMBER_H
 

	
 
#include <QString>
 

	
 
struct VersionNumber
 
{
 
    unsigned major = 0;
 
    unsigned minor = 0;
 
    unsigned patch = 0;
 

	
 
    VersionNumber(unsigned mj=0, unsigned mn=0, unsigned pt=0);
 

	
 
    /// Convert version number to string.
 
    QString toString() const;
 

	
 
    /// Extracts the version number from given string.
 
    static bool extract(const QString& str, VersionNumber& number);
 
};
 

	
 
bool operator==(const VersionNumber& lhs, const VersionNumber& rhs);
 
bool operator<(const VersionNumber& lhs, const VersionNumber& rhs);
 
bool operator>(const VersionNumber& lhs, const VersionNumber& rhs);
 

	
 
const VersionNumber CurrentVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
 

	
 
#endif // VERSIONNUMBER_H
src/zoomer.cpp
Show inline comments
 
@@ -19,24 +19,26 @@
 

	
 
#include "zoomer.h"
 
#include <qwt_plot.h>
 
#include <QtDebug>
 

	
 
#include <QMouseEvent>
 

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

	
 
    setTrackerMode(AlwaysOn);
 

	
 
    // set corner widget between the scrollbars with default background color
 
    auto cornerWidget = new QWidget();
 
    auto bgColor = cornerWidget->palette().color(QPalette::Window).name();
 
    auto styleSheet = QString("background-color:%1;").arg(bgColor);
 
    cornerWidget->setStyleSheet(styleSheet);
 
    ScrollZoomer::setCornerWidget(cornerWidget);
 
}
 

	
 
void Zoomer::zoom(int up)
 
{
 
    ScrollZoomer::zoom(up);
 

	
 
@@ -53,25 +55,25 @@ void Zoomer::zoom( const QRectF & rect)
 
    {
 
        this->setZoomBase(false);
 
    }
 

	
 
    ScrollZoomer::zoom(rect);
 
}
 

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

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

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

	
 
    QString sizeText = QString(" [%1, %2]").\
 
        arg(rect.width(), 0, 'g', 4).\
 
        arg(rect.height(), 0, 'g', 4);
 

	
 
    b.setText(b.text() + sizeText);
 

	
tests/CMakeLists.txt
Show inline comments
 
#
 
# Copyright © 2017 Hasan Yavuz Özderya
 
# Copyright © 2018 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
# Find the QtWidgets library
 
find_package(Qt5Widgets)
 
find_package(Qt5Test)
 

	
 
include_directories("../src")
 

	
 
add_executable(Test EXCLUDE_FROM_ALL
 
  test.cpp
 
  test_stream.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/indexbuffer.cpp
 
  ../src/linindexbuffer.cpp
 
  ../src/ringbuffer.cpp
 
  ../src/readonlybuffer.cpp
 
  ../src/stream.cpp
 
  ../src/streamchannel.cpp
 
  ../src/channelinfomodel.cpp  
 
  ../src/datachunk.cpp
 
  ../src/chunkedbuffer.cpp)
 
  ../src/chunkedbuffer.cpp
 
  )
 
add_test(NAME test1 COMMAND Test)
 
qt5_use_modules(Test Widgets)
 

	
 
qt5_wrap_ui(UI_FILES_T
 
  ../src/binarystreamreadersettings.ui
 
  ../src/asciireadersettings.ui
 
  ../src/framedreadersettings.ui
 
  ../src/demoreadersettings.ui
 
  ../src/numberformatbox.ui
 
  ../src/endiannessbox.ui
 
  )
 

	
 
# test for readers
 
add_executable(TestReaders EXCLUDE_FROM_ALL
 
  test_readers.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/abstractreader.cpp
 
  ../src/binarystreamreader.cpp
 
  ../src/binarystreamreadersettings.cpp
 
  ../src/asciireader.cpp
 
  ../src/asciireadersettings.cpp
 
  ../src/framedreader.cpp
 
  ../src/framedreadersettings.cpp
 
  ../src/demoreader.cpp
 
  ../src/demoreadersettings.cpp
 
  ../src/commandedit.cpp
 
  ../src/endiannessbox.cpp
 
  ../src/numberformatbox.cpp
 
  ../src/numberformat.cpp
 
  ${UI_FILES_T}
 
  )
 
qt5_use_modules(TestReaders Widgets Test)
 
add_test(NAME test_readers COMMAND TestReaders)
 

	
 
# test for recroder
 
add_executable(TestRecorder EXCLUDE_FROM_ALL
 
  test_recorder.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/datarecorder.cpp
 
)
 
qt5_use_modules(TestRecorder Widgets Test)
 
add_test(NAME test_recorder COMMAND TestRecorder)
 

	
 
set(CMAKE_CTEST_COMMAND ctest -V)
 
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
 
add_dependencies(check Test)
 
add_dependencies(check
 
  Test
 
  TestReaders
 
  TestRecorder
 
  )
tests/catch.hpp
Show inline comments
 
@@ -11533,13 +11533,12 @@ using Catch::Detail::Approx;
 

	
 
#ifdef __clang__
 
#    ifdef __ICC // icpc defines the __clang__ macro
 
#        pragma warning(pop)
 
#    else
 
#        pragma clang diagnostic pop
 
#    endif
 
#elif defined __GNUC__
 
#    pragma GCC diagnostic pop
 
#endif
 

	
 
#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
 

	
tests/test.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <time.h>
 
#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
 
#include "catch.hpp"
 

	
 
#include "samplepack.h"
 
#include "source.h"
 
#include "indexbuffer.h"
 
#include "linindexbuffer.h"
 
#include "ringbuffer.h"
 
#include "readonlybuffer.h"
 
#include "datachunk.h"
 
#include "chunkedbuffer.h"
 

	
 
#include "test_helpers.h"
 

	
 
TEST_CASE("samplepack with no X", "[memory]")
 
{
 
    SamplePack pack(100, 3, false);
 

	
 
    REQUIRE_FALSE(pack.hasX());
 
    REQUIRE(pack.numChannels() == 3);
 
    REQUIRE(pack.numSamples() == 100);
 

	
 
    double* chan0 = pack.data(0);
 
    double* chan1 = pack.data(1);
 
    double* chan2 = pack.data(2);
 

	
 
    REQUIRE(chan0 == chan1 - 100);
 
    REQUIRE(chan1 == chan2 - 100);
 
}
 

	
 
TEST_CASE("samplepack with X", "[memory]")
 
{
 
    SamplePack pack(100, 3, true);
 

	
 
    REQUIRE(pack.hasX());
 
    REQUIRE(pack.numChannels() == 3);
 
    REQUIRE(pack.numSamples() == 100);
 
    REQUIRE(pack.xData() != nullptr);
 
}
 

	
 
TEST_CASE("samplepack copy", "[memory]")
 
{
 
    SamplePack pack(10, 3, true);
 

	
 
    // fill test data
 
    for (int i = 0; i < 10; i++)
 
    {
 
        pack.xData()[i] = i;
 
        pack.data(0)[i] = i+5;
 
        pack.data(1)[i] = i*2;
 
        pack.data(2)[i] = i*3;
 
    }
 

	
 
    SamplePack other = pack;
 
    // compare
 
    for (int i = 0; i < 10; i++)
 
    {
 
        REQUIRE(other.xData()[i] == i);
 
        REQUIRE(other.data(0)[i] == i+5);
 
        REQUIRE(other.data(1)[i] == i*2);
 
        REQUIRE(other.data(2)[i] == i*3);
 
    }
 
}
 

	
 
TEST_CASE("sink", "[memory, stream]")
 
{
 
    TestSink sink;
 
    SamplePack pack(100, 3, false);
 

	
 
    sink.setNumChannels(3, false);
 
    REQUIRE(sink.numChannels() == 3);
 

	
 
    sink.feedIn(pack);
 
    REQUIRE(sink.totalFed == 100);
 
    sink.feedIn(pack);
 
    REQUIRE(sink.totalFed == 200);
 

	
 
    TestSink follower;
 

	
 
    sink.connectFollower(&follower);
 
    REQUIRE(follower.numChannels() == 3);
 
    REQUIRE(follower.hasX() == false);
 

	
 
    sink.feedIn(pack);
 
    REQUIRE(sink.totalFed == 300);
 
    REQUIRE(follower.totalFed == 100);
 

	
 
    sink.setNumChannels(2, true);
 
    REQUIRE(follower.numChannels() == 2);
 
    REQUIRE(follower.hasX() == true);
 
}
 

	
 
TEST_CASE("sink must be created unconnected", "[memory, stream]")
 
{
 
    TestSink sink;
 
    REQUIRE(sink.connectedSource() == NULL);
 
}
 

	
 
TEST_CASE("source", "[memory, stream]")
 
{
 
    TestSink sink;
 

	
 
    TestSource source(3, false);
 

	
 
    REQUIRE(source.numChannels() == 3);
 
    REQUIRE(source.hasX() == false);
 

	
 
    source.connectSink(&sink);
 
    REQUIRE(sink.numChannels() == 3);
 
    REQUIRE(sink.hasX() == false);
 

	
 
    source._setNumChannels(5, true);
 
    REQUIRE(sink.numChannels() == 5);
 
    REQUIRE(sink.hasX() == true);
 

	
 
    SamplePack pack(100, 5, true);
 
    source._feed(pack);
 
    REQUIRE(sink.totalFed == 100);
 

	
 
    source.disconnect(&sink);
 
    source._feed(pack);
 
    REQUIRE(sink.totalFed == 100);
 
}
 

	
 
TEST_CASE("source must set/unset sink 'source'", "[memory, stream]")
 
{
 
    TestSink sink;
 
    TestSource source(3, false);
 

	
 
    source.connectSink(&sink);
 
    REQUIRE(sink.connectedSource() == &source);
 

	
 
    source.disconnect(&sink);
 
    REQUIRE(sink.connectedSource() == NULL);
 
}
 

	
 
TEST_CASE("source disconnect all sinks", "[memory, stream]")
 
{
 
    TestSink sinks[3];
 
    TestSource source(3, false);
 

	
 
    // connect sinks
 
    for (int i = 0; i < 3; i++)
 
    {
 
        source.connectSink(&sinks[i]);
 
    }
 

	
 
    source.disconnectSinks();
 
    for (int i = 0; i < 3; i++)
 
    {
 
        REQUIRE(sinks[i].connectedSource() == NULL);
 
    }
 
}
 

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

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == i);
 
    }
 
    auto l = buf.limits();
 
    REQUIRE(l.start == 0);
 
    REQUIRE(l.end == 9);
 

	
 
    buf.resize(20);
 
    REQUIRE(buf.size() == 20);
 
    REQUIRE(buf.sample(15) == 15);
 
    l = buf.limits();
 
    REQUIRE(l.start == 0);
 
    REQUIRE(l.end == 19);
 
}
 

	
 
TEST_CASE("LinIndexBuffer", "[memory, buffer]")
 
{
 
    LinIndexBuffer buf(10, 0., 3.0);
 

	
 
    REQUIRE(buf.size() == 10);
 
    REQUIRE(buf.sample(0) == 0.);
 
    REQUIRE(buf.sample(9) == 3.0);
 
    REQUIRE(buf.sample(4) == Approx(1+1/3.));
 

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

	
 
    buf.resize(20);
 
    REQUIRE(buf.size() == 20);
 
    REQUIRE(buf.sample(0) == 0.);
 
    REQUIRE(buf.sample(9) == Approx(9.*3./19.));
 
    REQUIRE(buf.sample(4) == Approx(4.*3./19.));
 
    REQUIRE(buf.sample(19) == 3.0);
 

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

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

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

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

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

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

	
 
    buf.resize(15);
 
    REQUIRE(buf.size() == 15);
 
}
 

	
 
TEST_CASE("RingBuffer initial values should be 0", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 

	
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0.);
 
    }
 
}
 

	
 
TEST_CASE("RingBuffer data access", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i]);
 
    }
 

	
 
    buf.addSamples(values, 5);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i+5]);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i-5]);
 
    }
 
}
 

	
 
TEST_CASE("making RingBuffer bigger should keep end values", "[memory, buffer]")
 
{
 
    RingBuffer buf(5);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 5);
 
    buf.resize(10);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i-5]);
 
    }
 
}
 

	
 
TEST_CASE("making RingBuffer smaller should keep end values", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 
    buf.resize(5);
 

	
 
    REQUIRE(buf.size() == 5);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i+5]);
 
    }
 
}
 

	
 
TEST_CASE("RingBuffer limits", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 0.);
 

	
 
    buf.addSamples(values, 10);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 1.);
 
    REQUIRE(lim.end == 10.);
 

	
 
    buf.addSamples(&values[9], 1);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 2.);
 
    REQUIRE(lim.end == 10.);
 

	
 
    buf.addSamples(values, 9);
 
    buf.addSamples(values, 1);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 1.);
 
    REQUIRE(lim.end == 9.);
 
}
 

	
 
TEST_CASE("RingBuffer clear", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 
    buf.clear();
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0.);
 
    }
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 0.);
 
}
 

	
 
TEST_CASE("ReadOnlyBuffer", "[memory, buffer]")
 
{
 
    IndexBuffer source(10);
 

	
 
    ReadOnlyBuffer buf(&source);
 

	
 
    REQUIRE(buf.size() == 10);
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 9.);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == i);
 
    }
 
}
 

	
 
TEST_CASE("ReadOnlyBuffer sliced constructor", "[memory, buffer]")
 
{
 
    IndexBuffer source(10);
 

	
 
    ReadOnlyBuffer buf(&source, 5, 4);
 

	
 
    REQUIRE(buf.size() == 4);
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 5.);
 
    REQUIRE(lim.end == 8.);
 
    for (unsigned i = 0; i < 4; i++)
 
    {
 
        REQUIRE(buf.sample(i) == (i + 5));
 
    }
 
}
 

	
 
TEST_CASE("DataChunk created empty", "[memory]")
 
{
 
    DataChunk c(0, 1000);
 
    REQUIRE(c.size() == 0);
 
    REQUIRE(c.capacity() == 1000);
 
    REQUIRE(c.start() == 0);
 
    REQUIRE(c.end() == 0);
 
    REQUIRE_FALSE(c.isFull());
 
    REQUIRE(c.left() == 1000);
 
}
 

	
 
TEST_CASE("adding data to DataChunk", "[memory]")
tests/test_helpers.h
Show inline comments
 
new file 100644
 
#ifndef TEST_HELPERS_H
 
#define TEST_HELPERS_H
 

	
 
#include "source.h"
 
#include "sink.h"
 

	
 
class TestSink : public Sink
 
{
 
public:
 
    int totalFed;
 
    int _numChannels;
 
    bool _hasX;
 

	
 
    TestSink()
 
        {
 
            totalFed = 0;
 
            _numChannels = 0;
 
            _hasX = false;
 
        };
 

	
 
    void feedIn(const SamplePack& data)
 
        {
 
            REQUIRE(data.numChannels() == numChannels());
 

	
 
            totalFed += data.numSamples();
 

	
 
            Sink::feedIn(data);
 
        };
 

	
 
    void setNumChannels(unsigned nc, bool x)
 
        {
 
            _numChannels = nc;
 
            _hasX = x;
 

	
 
            Sink::setNumChannels(nc, x);
 
        };
 

	
 
    virtual unsigned numChannels() const
 
        {
 
            return _numChannels;
 
        };
 

	
 
    virtual bool hasX() const
 
        {
 
            return _hasX;
 
        };
 
};
 

	
 
class TestSource : public Source
 
{
 
public:
 
    int _numChannels;
 
    bool _hasX;
 

	
 
    TestSource(unsigned nc, bool x)
 
        {
 
            _numChannels = nc;
 
            _hasX = x;
 
        };
 

	
 
    virtual unsigned numChannels() const
 
        {
 
            return _numChannels;
 
        };
 

	
 
    virtual bool hasX() const
 
        {
 
            return _hasX;
 
        };
 

	
 
    void _feed(const SamplePack& data) const
 
        {
 
            feedOut(data);
 
        };
 

	
 
    void _setNumChannels(unsigned nc, bool x)
 
        {
 
            _numChannels = nc;
 
            _hasX = x;
 

	
 
            updateNumChannels();
 
        };
 
};
 

	
 

	
 
#endif // TEST_HELPERS_H
tests/test_readers.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
// This tells Catch to provide a main() - only do this in one cpp file per executable
 
#define CATCH_CONFIG_RUNNER
 
#include "catch.hpp"
 

	
 
#include <QSignalSpy>
 
#include <QBuffer>
 
#include "binarystreamreader.h"
 
#include "asciireader.h"
 
#include "framedreader.h"
 
#include "demoreader.h"
 

	
 
#include "test_helpers.h"
 

	
 
static const int READYREAD_TIMEOUT = 10; // milliseconds
 

	
 
TEST_CASE("reading data with BinaryStreamReader", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    BinaryStreamReader bs(&bufferDev);
 
    bs.enable(true);
 

	
 
    TestSink sink;
 
    bs.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const char data[] = {0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write(data, 4);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 4);
 
}
 

	
 
TEST_CASE("disabled BinaryStreamReader shouldn't read", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    BinaryStreamReader bs(&bufferDev); // disabled by default
 

	
 
    TestSink sink;
 
    bs.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const char data[] = {0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write(data, 4);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    // readyRead isn't signaled because there are no connections to it
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
TEST_CASE("reading data with AsciiReader", "[reader, ascii]")
 
{
 
    QBuffer bufferDev;
 
    AsciiReader reader(&bufferDev);
 
    reader.enable(true);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    // inject data to the buffer
 
    bufferDev.open(QIODevice::ReadWrite);
 
    bufferDev.write("0,1,3\n0,1,3\n0,1,3\n0,1,3\n");
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink._numChannels == 3);
 
    REQUIRE(sink._hasX == false);
 
    REQUIRE(sink.totalFed == 3);
 
}
 

	
 
TEST_CASE("AsciiReader shouldn't read when disabled", "[reader, ascii]")
 
{
 
    QBuffer bufferDev;
 
    AsciiReader reader(&bufferDev); // disabled by default
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    // inject data to the buffer
 
    bufferDev.open(QIODevice::ReadWrite);
 
    bufferDev.write("0,1,3\n0,1,3\n0,1,3\n0,1,3\n");
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
TEST_CASE("reading data with FramedReader", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    FramedReader reader(&bufferDev);
 
    reader.enable(true);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write((const char*) data, 7);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 4);
 
}
 

	
 
TEST_CASE("FramedReader shouldn't read when disabled", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    FramedReader reader(&bufferDev);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write((const char*) data, 7);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
TEST_CASE("Generating data with DemoReader", "[reader, demo]")
 
{
 
    QBuffer bufferDev;          // not actually used
 
    DemoReader demoReader(&bufferDev);
 
    demoReader.enable(true);
 

	
 
    TestSink sink;
 
    demoReader.connectSink(&sink);
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    // we need to wait somehow, we are not actually looking for signals
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(1000)); // we need some time for demoreader to produce data
 
    REQUIRE(sink.totalFed >= 9);
 
}
 

	
 
TEST_CASE("DemoReader shouldn't generate data when paused", "[reader, demo]")
 
{
 
    QBuffer bufferDev;          // not actually used
 
    DemoReader demoReader(&bufferDev); // paused by default
 

	
 
    TestSink sink;
 
    demoReader.connectSink(&sink);
 
    REQUIRE(sink._numChannels == 1);
 

	
 
    // we need to wait somehow, we are not actually looking for signals
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(1000)); // we need some time for demoreader to produce data
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
// Note: this is added because `QApplication` must be created for widgets
 
#include <QApplication>
 
int main(int argc, char* argv[])
 
{
 
    QApplication a(argc, argv);
 

	
 
    int result = Catch::Session().run( argc, argv );
 

	
 
    return result;
 
}
tests/test_recorder.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
 
#include "catch.hpp"
 

	
 
#include <QDir>
 
#include "datarecorder.h"
 
#include "test_helpers.h"
 

	
 
#define TEST_FILE_NAME   "sp_test_recording.csv"
 

	
 
TEST_CASE("test recording single channel", "[recorder]")
 
{
 
    DataRecorder rec;
 
    TestSource source(1, false);
 

	
 
    // temporary file, remove if exists
 
    auto fileName = QDir::tempPath() + QString("/" TEST_FILE_NAME);
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 

	
 
    // connect source → sink
 
    source.connectSink(&rec);
 

	
 
    // prepare data
 
    QStringList channelNames({"Channel 1"});
 
    SamplePack samples(5, 1);
 
    for (int i = 0; i < 5; i++)
 
    {
 
        samples.data(0)[i] = i+1;
 
    }
 

	
 
    // test
 
    rec.startRecording(fileName, ",", channelNames, false);
 
    source._feed(samples);
 
    rec.stopRecording();
 

	
 
    // read file contents back
 
    QFile recordFile(fileName);
 
    REQUIRE(recordFile.open(QIODevice::ReadOnly | QIODevice::Text));
 
    // NOTE: mind the extra parantheses, otherwise 'catch' macros fail to compile
 
    REQUIRE((recordFile.readLine() == "Channel 1\n"));
 
    for (int i = 0; i < 5; i++)
 
        REQUIRE((recordFile.readLine() == QString("%1\n").arg(i+1)));
 

	
 
    // cleanup
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 
}
 

	
 
TEST_CASE("test recording multiple channels", "[recorder]")
 
{
 
    DataRecorder rec;
 
    TestSource source(3, false);
 

	
 
    // temporary file, remove if exists
 
    auto fileName = QDir::tempPath() + QString("/" TEST_FILE_NAME);
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 

	
 
    // connect source → sink
 
    source.connectSink(&rec);
 

	
 
    // prepare data
 
    QStringList channelNames({"Channel 1", "Channel 2", "Channel 3"});
 
    SamplePack samples(5, 3);
 
    for (int ci = 0; ci < 3; ci++)
 
    {
 
        for (int i = 0; i < 5; i++)
 
        {
 
            samples.data(ci)[i] = (ci+1)*(i+1);
 
        }
 
    }
 

	
 
    // test
 
    rec.startRecording(fileName, ",", channelNames, false);
 
    source._feed(samples);
 
    rec.stopRecording();
 

	
 
    // read file contents back
 
    QFile recordFile(fileName);
 
    REQUIRE(recordFile.open(QIODevice::ReadOnly | QIODevice::Text));
 
    // NOTE: mind the extra parantheses, otherwise 'catch' macros fail to compile
 
    REQUIRE((recordFile.readLine() == "Channel 1,Channel 2,Channel 3\n"));
 
    REQUIRE((recordFile.readLine() == "1,2,3\n"));
 
    REQUIRE((recordFile.readLine() == "2,4,6\n"));
 
    REQUIRE((recordFile.readLine() == "3,6,9\n"));
 
    REQUIRE((recordFile.readLine() == "4,8,12\n"));
 
    REQUIRE((recordFile.readLine() == "5,10,15\n"));
 

	
 
    // cleanup
 
    if (QFile::exists(fileName)) QFile::remove(fileName);
 
}
tests/test_stream.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "stream.h"
 

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

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

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

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

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

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

	
 
TEST_CASE("changing stream number of channels via sink", "[memory, stream, sink]")
 
{
 
    Stream s;
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

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

	
 
    // increase nc value, add X
 
    so._setNumChannels(5, true);
 

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

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

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

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

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

	
 
TEST_CASE("adding data to a stream with no X", "[memory, stream, data, sink]")
 
{
 
    Stream s(3, false, 10);
 

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

	
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // test
 
    so._feed(pack);
 

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

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

	
 
TEST_CASE("adding data to a stream with X", "[memory, stream, data, sink]")
 
{
 
    Stream s(3, true, 10);
 

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

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

	
 
    // test
 
    so._feed(pack);
 

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

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

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

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

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

	
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // test
 
    s.pause(true);
 
    so._feed(pack);
 

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

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

	
 
TEST_CASE("clear stream data", "[memory, stream, pause]")
 
{
 
    Stream s(3, false, 10);
 

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

	
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // test
 
    so._feed(pack);
 
    s.clear();
 

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

	
 
        for (unsigned i = 0; i < 10; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
        }
 
    }
 
}
0 comments (0 inline, 0 general)