Changeset - 71441196d1c5
[Not reviewed]
Merge longmem
3 54 7
Hasan Yavuz ÖZDERYA - 6 years ago 2020-01-29 14:59:28
hy@ozderya.net
Merge with default
64 files changed with 1694 insertions and 433 deletions:
0 comments (0 inline, 0 general)
CMakeLists.txt
Show inline comments
 
#
 
# Copyright © 2018 Hasan Yavuz Özderya
 
# Copyright © 2019 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
cmake_minimum_required(VERSION 3.2.2)
 

	
 
project(serialplot)
 

	
 
set(PROGRAM_NAME ${CMAKE_PROJECT_NAME} CACHE STRING "Output program name")
 
set(PROGRAM_DISPLAY_NAME "SerialPlot" CACHE STRING "Display name (menus etc) of the program")
 

	
 
# Find includes in corresponding build directories
 
set(CMAKE_INCLUDE_CURRENT_DIR ON)
 

	
 
# Instruct CMake to run moc automatically when needed.
 
set(CMAKE_AUTOMOC ON)
 

	
 
# add local path for cmake modules
 
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
 

	
 
# Find the QtWidgets library
 
find_package(Qt5Widgets)
 

	
 
# If set, cmake will download Qwt over SVN, build and use it as a static library.
 
set(BUILD_QWT true CACHE BOOL "Download and build Qwt automatically.")
 
if (BUILD_QWT)
 
  include(BuildQwt)
 
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
 
  src/datatextview.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/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/stream.cpp
 
  src/streamchannel.cpp
 
  src/channelinfomodel.cpp
 
  src/ringbuffer.cpp
 
  src/ringbuffer.cpp
 
  src/indexbuffer.cpp
 
  src/linindexbuffer.cpp
 
  src/readonlybuffer.cpp
 
  src/framebufferseries.cpp
 
  src/numberformatbox.cpp
 
  src/endiannessbox.cpp
 
  src/abstractreader.cpp
 
  src/binarystreamreader.cpp
 
  src/binarystreamreadersettings.cpp
 
  src/asciireader.cpp
 
  src/asciireadersettings.cpp
 
  src/demoreader.cpp
 
  src/demoreadersettings.cpp
 
  src/framedreader.cpp
 
  src/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
 
  src/ledwidget.cpp
 
  src/datatextview.cpp
 
  src/bpslabel.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 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.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}\\\" ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROGRAM_NAME=\\\"${PROGRAM_NAME}\\\" ")
 

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

	
 
# for windows put libraries to install directory
 
if (WIN32)
 
  file(GLOB WINDOWS_INSTALL_LIBRARIES
 
    "${CMAKE_BINARY_DIR}/windows_install_libraries/*.*")
 
  install(FILES ${WINDOWS_INSTALL_LIBRARIES} DESTINATION bin)
 
endif (WIN32)
 

	
 
# prepare menu item and icon
 
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/misc/program_name.desktop.in"
 
  "${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop")
 
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/misc/program_name.png"
 
  "${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png" COPYONLY)
 

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

	
 
include(ExternalProject)
 

	
 
ExternalProject_Add(QWT
 
  PREFIX qwt
 
  SVN_REPOSITORY svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1
 
  # SVN_REPOSITORY svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1
 
  URL https://sourceforge.net/projects/qwt/files/qwt/6.1.4/qwt-6.1.4.tar.bz2
 
  # disable QwtDesigner plugin and enable static build
 
  PATCH_COMMAND sed -i -r -e "s/QWT_CONFIG\\s*\\+=\\s*QwtDesigner/#&/"
 
                          -e "s/QWT_CONFIG\\s*\\+=\\s*QwtDll/#&/"
 
                          -e "s/QWT_CONFIG\\s*\\+=\\s*QwtSvg/#&/"
 
                          -e "s/QWT_CONFIG\\s*\\+=\\s*QwtOpenGL/#&/"
 
						  -e "s|QWT_INSTALL_PREFIX\\s*=.*|QWT_INSTALL_PREFIX = <INSTALL_DIR>|"
 
                             <SOURCE_DIR>/qwtconfig.pri
 
  UPDATE_COMMAND ""
 
  CONFIGURE_COMMAND qmake <SOURCE_DIR>/qwt.pro
 
  )
 

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

	
 
## First, we look for local Qwt installations, if this fails we will
 
## look into system locations
 

	
 
file(GLOB qwt_glob_dir "/usr/local/qwt*/" "c:/Qwt*")
 

	
 
if(qwt_glob_dir)
 
  foreach(qwt_path ${qwt_glob_dir})
 
@@ -45,78 +45,96 @@ if(qwt_roots)
 
	if(qwt_version_string)
 
	  string(REGEX REPLACE "[^\"]*\"([0-9.]+)\".*" "\\1"
 
		qwt_version_string ${qwt_version_string})
 

	
 
	  if(Qwt_FIND_VERSION)
 
		if( (qwt_version_string VERSION_EQUAL Qwt_FIND_VERSION) OR
 
		  (qwt_version_string VERSION_GREATER Qwt_FIND_VERSION))
 
		  set(QWT_VERSION ${qwt_version_string})
 
		  set(QWT_ROOT ${qwt_root})
 
		  break() # found an appropriate version
 
		endif()
 
	  else(Qwt_FIND_VERSION)
 
		set(QWT_ROOT ${qwt_root})
 
		set(QWT_VERSION ${qwt_version_string})
 
		break() # version is not specified stop at the first qwt_root
 
	  endif(Qwt_FIND_VERSION)
 
	else(qwt_version_string)
 
	  message(WARNING "Couldn't find version string in qwt_global.h file.")
 
	endif(qwt_version_string)
 
  endforeach(qwt_root ${qwt_roots})
 
endif(qwt_roots)
 

	
 
if(QWT_ROOT)
 
  set(QWT_INCLUDE_DIR "${QWT_ROOT}/include")
 
  find_library(QWT_LIBRARY "qwt-qt5"
 
	PATHS "${QWT_ROOT}/lib")
 
  find_library(QWT_LIBRARY NAMES "qwt-qt5" "qwt" PATHS "${QWT_ROOT}/lib")
 
else (QWT_ROOT)
 
  ## Look into system locations
 
  find_path(QWT_INCLUDE_DIR qwt_plot.h PATHS /usr/include/qwt)
 
  # try extracting version information
 
  if (QWT_INCLUDE_DIR)
 
	unset(qwt_version_string)
 
	file(STRINGS "${QWT_INCLUDE_DIR}/qwt_global.h" qwt_version_string
 
	  REGEX "#define[ \t]+QWT_VERSION_STR")
 
	if(qwt_version_string)
 
	  string(REGEX REPLACE "[^\"]*\"([0-9.]+)\".*" "\\1"
 
		qwt_version_string ${qwt_version_string})
 
	  if(Qwt_FIND_VERSION)
 
		if( (qwt_version_string VERSION_EQUAL Qwt_FIND_VERSION) OR
 
		  (qwt_version_string VERSION_GREATER Qwt_FIND_VERSION))
 
		  set(QWT_VERSION ${qwt_version_string})
 
		else ()
 
		  set(QWT_INCLUDE_DIR "NOTFOUND")
 
		endif()
 
	  endif(Qwt_FIND_VERSION)
 
	endif(qwt_version_string)
 
  endif (QWT_INCLUDE_DIR)
 
  # look into system locations for lib file
 
  find_library(QWT_LIBRARY "qwt-qt5" PATHS /usr/lib)
 
  find_library(QWT_LIBRARY NAMES "qwt-qt5" "qwt" PATHS /usr/lib)
 
endif(QWT_ROOT)
 

	
 
# set version variables
 
if(QWT_VERSION)
 
  string(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1"
 
	QWT_MAJOR_VERSION ${QWT_VERSION})
 
  string(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2"
 
	QWT_MINOR_VERSION ${QWT_VERSION})
 
  string(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\3"
 
	QWT_PATCH_VERSION ${QWT_VERSION})
 
endif(QWT_VERSION)
 

	
 
# check Qwt library 'Qt' version
 
if (QWT_LIBRARY)
 
  include(GetPrerequisites)
 
  GET_PREREQUISITES(${QWT_LIBRARY} qwt_lib_deps 0 0 "" "")
 
  set(qwt_is_qt5 FALSE)
 
  foreach (dep ${qwt_lib_deps})
 
    if (${dep} MATCHES "libQt5Gui")
 
      set(qwt_is_qt5 TRUE)
 
    endif()
 
  endforeach ()
 
  if (NOT qwt_is_qt5)
 
    message(WARNING "Found qwt library (${QWT_LIBRARY}) isn't compiled with Qt5!")
 
    LIST_PREREQUISITES(${QWT_LIBRARY})
 
  endif()
 
endif (QWT_LIBRARY)
 

	
 
# set QWT_FOUND
 
if(QWT_INCLUDE_DIR AND QWT_LIBRARY)
 
if(QWT_INCLUDE_DIR AND QWT_LIBRARY AND qwt_is_qt5)
 
  set(QWT_INCLUDE_DIRS ${QWT_INCLUDE_DIR})
 
  set(QWT_LIBRARIES ${QWT_LIBRARY})
 
  set(QWT_FOUND true)
 
else()
 
  set(QWT_FOUND false)
 
endif()
 

	
 
# errors
 
if(NOT QWT_FOUND)
 
  unset(QWT_INCLUDE_DIR CACHE)
 
  unset(QWT_LIBRARY CACHE)
 

	
 
  if(Qwt_FIND_QUIET)
 
	message(WARNING "Couldn't find Qwt.")
 
  elseif(Qwt_FIND_REQUIRED)
 
	message(FATAL_ERROR "Couldn't find Qwt.")
 
  endif(Qwt_FIND_QUIET)
 
endif(NOT QWT_FOUND)
serialplot.pro
Show inline comments
 
#
 
# Copyright © 2015 Hasan Yavuz Özderya
 
# Copyright © 2019 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
#-------------------------------------------------
 
#
 
# Project created by QtCreator 2015-03-04T08:20:06
 
#
 
#-------------------------------------------------
 

	
 
QT       += core gui serialport
 
QT       += core gui serialport network svg
 

	
 
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 

	
 
TARGET = serialplot
 
TEMPLATE = app
 

	
 
CONFIG += qwt
 
# LIBS += -lqwt # enable this line if qwt pri files aren't installed
 

	
 
DEFINES += PROGRAM_NAME="\\\"serialplot\\\""
 

	
 
DEFINES += VERSION_MAJOR=10 VERSION_MINOR=0 VERSION_PATCH=0 VERSION_STRING=\\\"10.0.0\\\"
 

	
 
SOURCES += \
 
    src/main.cpp\
 
    src/main.cpp \
 
    src/mainwindow.cpp \
 
    src/portcontrol.cpp \
 
    src/plot.cpp \
 
    src/zoomer.cpp \
 
    src/scrollzoomer.cpp \
 
    src/scrollbar.cpp \
 
    src/hidabletabwidget.cpp \
 
    src/framebuffer.cpp \
 
    src/scalepicker.cpp \
 
    src/scalezoomer.cpp \
 
    src/portlist.cpp \
 
    src/snapshot.cpp \
 
    src/snapshotview.cpp \
 
    src/snapshotmanager.cpp \
 
    src/snapshot.cpp \
 
    src/plotsnapshotoverlay.cpp \
 
    src/commandpanel.cpp \
 
    src/commandwidget.cpp \
 
    src/commandedit.cpp \
 
    src/dataformatpanel.cpp \
 
    src/plotcontrolpanel.cpp \
 
    src/recordpanel.cpp \
 
    src/datarecorder.cpp \
 
    src/tooltipfilter.cpp \
 
    src/sneakylineedit.cpp \
 
    src/channelmanager.cpp \
 
    src/stream.cpp \
 
    src/streamchannel.cpp \
 
    src/channelinfomodel.cpp \
 
    src/ringbuffer.cpp \
 
    src/indexbuffer.cpp \
 
    src/linindexbuffer.cpp \
 
    src/readonlybuffer.cpp \
 
    src/framebufferseries.cpp \
 
    src/plotcontrolpanel.cpp \
 
    src/numberformatbox.cpp \
 
    src/endiannessbox.cpp \
 
    src/framedreadersettings.cpp \
 
    src/abstractreader.cpp \
 
    src/binarystreamreader.cpp \
 
    src/binarystreamreadersettings.cpp \
 
    src/asciireader.cpp \
 
    src/asciireadersettings.cpp \
 
    src/asciireader.cpp \
 
    src/demoreader.cpp \
 
    src/demoreadersettings.cpp \
 
    src/framedreader.cpp \
 
    src/framedreadersettings.cpp \
 
    src/plotmanager.cpp \
 
    src/plotmenu.cpp \
 
    src/barplot.cpp \
 
    src/barchart.cpp \
 
    src/barscaledraw.cpp \
 
    src/numberformat.cpp \
 
    src/recordpanel.cpp \
 
    src/updatechecker.cpp \
 
    src/versionnumber.cpp \
 
    src/updatecheckdialog.cpp \
 
    src/demoreadersettings.cpp
 
    src/samplepack.cpp \
 
    src/source.cpp \
 
    src/sink.cpp \
 
    src/samplecounter.cpp \
 
    src/ledwidget.cpp \
 
    src/datatextview.cpp \
 
    src/bpslabel.cpp
 

	
 
HEADERS += \
 
    src/mainwindow.h \
 
    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 \
 
    src/portlist.h \
 
    src/snapshotview.h \
 
    src/snapshotmanager.h \
 
    src/snapshot.h \
 
    src/plotsnapshotoverlay.h \
 
    src/commandpanel.h \
 
    src/commandwidget.h \
 
    src/commandedit.h \
 
    src/dataformatpanel.h \
 
    src/tooltipfilter.h \
 
    src/sneakylineedit.h \
 
    src/channelmanager.h \
 
    src/framebufferseries.h \
 
    src/plotcontrolpanel.h \
 
    src/numberformatbox.h \
 
    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/updatechecker.h \
 
    src/updatecheckdialog.h \
 
    src/demoreadersettings.h
 
    src/demoreadersettings.h \
 
    src/datatextview.h \
 
    src/bpslabel.h \
 
    src/barchart.h \
 
    src/barplot.h \
 
    src/barscaledraw.h \
 
    src/channelinfomodel.h \
 
    src/datarecorder.h \
 
    src/defines.h \
 
    src/indexbuffer.h \
 
    src/ledwidget.h \
 
    src/linindexbuffer.h \
 
    src/plotmenu.h \
 
    src/readonlybuffer.h \
 
    src/ringbuffer.h \
 
    src/samplecounter.h \
 
    src/samplepack.h \
 
    src/scrollbar.h \
 
    src/scrollzoomer.h \
 
    src/sink.h \
 
    src/source.h \
 
    src/streamchannel.h \
 
    src/stream.h \
 
    src/version.h \
 
    src/versionnumber.h \
 
    src/zoomer.h
 

	
 
FORMS += \
 
    src/mainwindow.ui \
 
    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/updatecheckdialog.ui \
 
    src/demoreadersettings.ui
 
    src/demoreadersettings.ui \
 
    src/datatextview.ui
 

	
 
INCLUDEPATH += qmake/ src/
 

	
 
CONFIG += c++11
 

	
 
RESOURCES += misc/icons.qrc
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "abstractreader.h"
 

	
 
AbstractReader::AbstractReader(QIODevice* device, QObject* parent) :
 
    QObject(parent)
 
{
 
    _device = device;
 
    bytesRead = 0;
 
}
 

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

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

	
 
void AbstractReader::onDataReady()
 
{
 
    bytesRead += readData();
 
}
 

	
 
unsigned AbstractReader::getBytesRead()
 
{
 
    unsigned r = bytesRead;
 
    bytesRead = 0;
 
    return r;
 
}
src/abstractreader.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef ABSTRACTREADER_H
 
#define ABSTRACTREADER_H
 

	
 
#include <QObject>
 
#include <QIODevice>
 
#include <QWidget>
 
#include <QTimer>
 
@@ -28,47 +28,63 @@
 
#include "source.h"
 

	
 
/**
 
 * All reader classes must inherit this class.
 
 */
 
class AbstractReader : public QObject, public Source
 
{
 
    Q_OBJECT
 
public:
 
    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;
 

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

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

	
 
    /// Read and 'zero' the byte counter
 
    unsigned getBytesRead();
 

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

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

	
 
protected:
 
    /// Reader should read from this device in `readData()` function.
 
    QIODevice* _device;
 

	
 
    /// Reader should check this variable to determine if reading is
 
    /// paused in `readData()`
 
    bool paused;
 

	
 
protected slots:
 
    /// all derived readers has to override this function
 
    virtual void onDataReady() = 0;
 
    /**
 
     * Called when `readyRead` is signaled by the device. This is
 
     * where the implementors should read the data and return the
 
     * exact number of bytes read from the device.
 
     */
 
    virtual unsigned readData() = 0;
 

	
 
private:
 
    unsigned bytesRead;
 

	
 
private slots:
 
    void onDataReady();
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <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)
 

	
 
@@ -49,107 +49,109 @@ AsciiReader::AsciiReader(QIODevice* devi
 
    connect(&_settingsWidget, &AsciiReaderSettings::delimiterChanged,
 
            [this](QChar d)
 
            {
 
                delimiter = d;
 
            });
 
}
 

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

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

	
 
void AsciiReader::enable(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        firstReadAfterEnable = true;
 
        QObject::connect(_device, &QIODevice::readyRead,
 
                         this, &AsciiReader::onDataReady);
 
    }
 
    else
 
    {
 
        QObject::disconnect(_device, 0, this, 0);
 
        disconnectSinks();
 
    }
 

	
 
    AbstractReader::enable(enabled);
 
}
 

	
 
void AsciiReader::onDataReady()
 
unsigned AsciiReader::readData()
 
{
 
    unsigned numBytesRead = 0;
 

	
 
    while(_device->canReadLine())
 
    {
 
        QString line = QString(_device->readLine());
 
        QByteArray bytes = _device->readLine();
 
        QString line = QString(bytes);
 
        numBytesRead += bytes.size();
 

	
 
        // discard only once when we just started reading
 
        if (firstReadAfterEnable)
 
        {
 
            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;
 
        }
 

	
 
        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);
 
                }
 
            }
 

	
 
            Q_ASSERT(samples->numChannels() == _numChannels);
 

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

	
 
    return numBytesRead;
 
}
 

	
 
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)
 
        {
 
            qWarning() << "Data parsing error for channel: " << ci;
src/asciireader.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef ASCIIREADER_H
 
#define ASCIIREADER_H
 

	
 
#include <QSettings>
 
#include <QString>
 

	
 
#include "samplepack.h"
 
@@ -29,35 +29,37 @@
 

	
 
class AsciiReader : public AbstractReader
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit AsciiReader(QIODevice* device, QObject *parent = 0);
 
    QWidget* settingsWidget();
 
    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);
 

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

	
 
    bool firstReadAfterEnable = false;
 

	
 
    unsigned readData() override;
 

	
 
private slots:
 
    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 © 2017 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QRegularExpressionValidator>
 
#include <QRegularExpression>
 

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

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

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

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

	
 
    ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS);
 

	
 
    connect(ui->rbComma, &QAbstractButton::toggled,
 
            this, &AsciiReaderSettings::delimiterToggled);
 
    connect(ui->rbSpace, &QAbstractButton::toggled,
 
            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;
 
}
 

	
src/binarystreamreader.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtEndian>
 
#include <QtDebug>
 

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

	
 
BinaryStreamReader::BinaryStreamReader(QIODevice* device, QObject* parent) :
 
@@ -86,92 +86,100 @@ void BinaryStreamReader::onNumberFormatC
 
            sampleSize = 4;
 
            readSample = &BinaryStreamReader::readSampleAs<quint32>;
 
            break;
 
        case NumberFormat_int32:
 
            sampleSize = 4;
 
            readSample = &BinaryStreamReader::readSampleAs<qint32>;
 
            break;
 
        case NumberFormat_float:
 
            sampleSize = 4;
 
            readSample = &BinaryStreamReader::readSampleAs<float>;
 
            break;
 
        case NumberFormat_INVALID:
 
            Q_ASSERT(1); // never
 
            break;
 
    }
 
}
 

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

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

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

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

	
 
    if (bytesAvailable < packageSize) return;
 
    if (bytesAvailable < packageSize) return totalRead;
 

	
 
    int numOfPackagesToRead =
 
    unsigned numOfPackagesToRead =
 
        (bytesAvailable - (bytesAvailable % packageSize)) / packageSize;
 
    unsigned numBytesToRead = numOfPackagesToRead * packageSize;
 

	
 
    totalRead += numBytesToRead;
 

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

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

	
 
    return totalRead;
 
}
 

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

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

	
 
    if (_settingsWidget.endianness() == LittleEndian)
 
    {
 
        data = qFromLittleEndian(data);
 
    }
 
    else
 
    {
 
        data = qFromBigEndian(data);
 
    }
 

	
 
    return double(data);
 
}
 

	
 
void BinaryStreamReader::saveSettings(QSettings* settings)
 
{
 
    _settingsWidget.saveSettings(settings);
 
}
src/binarystreamreader.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BINARYSTREAMREADER_H
 
#define BINARYSTREAMREADER_H
 

	
 
#include <QSettings>
 

	
 
#include "abstractreader.h"
 
#include "binarystreamreadersettings.h"
 
@@ -39,31 +39,32 @@ public:
 
    unsigned numChannels() const;
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
private:
 
    BinaryStreamReaderSettings _settingsWidget;
 
    unsigned _numChannels;
 
    unsigned sampleSize;
 
    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();
 

	
 
    unsigned readData() override;
 

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

	
 
#endif // BINARYSTREAMREADER_H
src/binarystreamreadersettings.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "binarystreamreadersettings.h"
 
#include "ui_binarystreamreadersettings.h"
 

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

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

	
 
    ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS);
 

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

	
 
    connect(ui->nfBox, SIGNAL(selectionChanged(NumberFormat)),
 
            this, SIGNAL(numberFormatChanged(NumberFormat)));
 

	
 
    connect(ui->pbSkipByte, SIGNAL(clicked()), this, SIGNAL(skipByteRequested()));
 
    connect(ui->pbSkipSample, SIGNAL(clicked()), this, SIGNAL(skipSampleRequested()));
 
}
 

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

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

	
src/bpslabel.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "bpslabel.h"
 

	
 
const char* BPS_TOOLTIP = "bits per second";
 
const char* BPS_TOOLTIP_ERR = "Maximum baud rate may be reached!";
 

	
 
BPSLabel::BPSLabel(PortControl* portControl,
 
                   DataFormatPanel* dataFormatPanel,
 
                   QWidget *parent) :
 
    QLabel(parent)
 
{
 
    _portControl = portControl;
 
    _dataFormatPanel = dataFormatPanel;
 
    prevBytesRead = 0;
 

	
 
    setText("0bps");
 
    setToolTip(tr(BPS_TOOLTIP));
 

	
 
    connect(&bpsTimer, &QTimer::timeout,
 
            this, &BPSLabel::onBpsTimeout);
 

	
 
    connect(portControl, &PortControl::portToggled,
 
            this, &BPSLabel::onPortToggled);
 
}
 

	
 
void BPSLabel::onBpsTimeout()
 
{
 
    uint64_t curBytesRead = _dataFormatPanel->bytesRead();
 
    uint64_t bytesRead = curBytesRead - prevBytesRead;
 
    prevBytesRead = curBytesRead;
 

	
 
    unsigned bits = bytesRead * 8;
 
    unsigned maxBps = _portControl->maxBitRate();
 
    QString str;
 
    if (bits >= maxBps)
 
    {
 
        // TODO: an icon for bps warning
 
        str = QString(tr("!%1/%2bps")).arg(bits).arg(maxBps);
 
        setToolTip(tr(BPS_TOOLTIP_ERR));
 
    }
 
    else
 
    {
 
        str = QString(tr("%1bps")).arg(bits);
 
        setToolTip(tr(BPS_TOOLTIP));
 
    }
 
    setText(str);
 
}
 

	
 
void BPSLabel::onPortToggled(bool open)
 
{
 
    if (open)
 
    {
 
        bpsTimer.start(1000);
 
    }
 
    else
 
    {
 
        bpsTimer.stop();
 
        // if not cleared last displayed value is stuck
 
        setText("0bps");
 
        setToolTip(tr(BPS_TOOLTIP));
 
    }
 
}
src/bpslabel.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef BPSLABEL_H
 
#define BPSLABEL_H
 

	
 
#include <QLabel>
 
#include <QTimer>
 

	
 
#include "portcontrol.h"
 
#include "dataformatpanel.h"
 

	
 
/**
 
 * Displays bits per second read from device.
 
 *
 
 * Displays a warning if maximum bit rate is reached.
 
 */
 
class BPSLabel : public QLabel
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit BPSLabel(PortControl* portControl,
 
                      DataFormatPanel* dataFormatPanel,
 
                      QWidget *parent = 0);
 

	
 
private:
 
    PortControl* _portControl;
 
    DataFormatPanel* _dataFormatPanel;
 
    QTimer bpsTimer;
 

	
 
    uint64_t prevBytesRead;
 

	
 
private slots:
 
    void onBpsTimeout();
 
    void onPortToggled(bool open);
 
};
 

	
 
#endif // BPSLABEL_H
src/dataformatpanel.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#include <QRadioButton>
 
#include <QtDebug>
 

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

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

	
 
    serialPort = port;
 
    paused = false;
 
    readerBeforeDemo = nullptr;
 
    _bytesRead = 0;
 

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

	
 
    // 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);
 
            });
 
}
 

	
 
@@ -109,48 +110,54 @@ bool DataFormatPanel::isDemoEnabled() co
 
{
 
    return currentReader == &demoReader;
 
}
 

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

	
 
    // re-connect signals
 
    disconnect(currentReader, 0, this, 0);
 

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

	
 
    reader->pause(paused);
 

	
 
    currentReader = reader;
 
    emit sourceChanged(currentReader);
 
}
 

	
 
uint64_t DataFormatPanel::bytesRead()
 
{
 
    _bytesRead += currentReader->getBytesRead();
 
    return _bytesRead;
 
}
 

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

	
 
    // save selected data format (current reader)
 
    QString format;
 
    AbstractReader* selectedReader = isDemoEnabled() ? readerBeforeDemo : currentReader;
 
    if (selectedReader == &bsReader)
 
    {
 
        format = "binary";
 
    }
 
    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 © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DATAFORMATPANEL_H
 
#define DATAFORMATPANEL_H
 

	
 
#include <stdint.h>
 
#include <QWidget>
 
#include <QButtonGroup>
 
#include <QTimer>
 
#include <QSerialPort>
 
#include <QList>
 
#include <QSettings>
 
#include <QtGlobal>
 

	
 
#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, QWidget* parent = 0);
 
    ~DataFormatPanel();
 

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

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

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

	
 
private:
 
    Ui::DataFormatPanel *ui;
 

	
 
    QSerialPort* serialPort;
 

	
 
    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;
 
    uint64_t _bytesRead;
 

	
 
    DemoReader demoReader;
 
    AbstractReader* readerBeforeDemo;
 

	
 
    bool isDemoEnabled() const;
 
};
 

	
 
#endif // DATAFORMATPANEL_H
src/datatextview.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "datatextview.h"
 
#include "ui_datatextview.h"
 

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

	
 
class DataTextViewSink : public Sink
 
{
 
public:
 
    DataTextViewSink(DataTextView* textView)
 
    {
 
        _textView = textView;
 
    }
 

	
 
protected:
 
    virtual void feedIn(const SamplePack& data) override
 
    {
 
        _textView->addData(data);
 
    };
 

	
 
private:
 
    DataTextView* _textView;
 
};
 

	
 
DataTextView::DataTextView(Stream* stream, QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::DataTextView)
 
{
 
    _stream = stream;
 
    ui->setupUi(this);
 
    sink = new DataTextViewSink(this);
 

	
 
    connect(ui->cbEnable, &QCheckBox::toggled, [this](bool checked)
 
            {
 
                if (checked)
 
                {
 
                    _stream->connectFollower(sink);
 
                }
 
                else
 
                {
 
                    _stream->disconnectFollower(sink);
 
                }
 
            });
 

	
 
    ui->textView->setMaximumBlockCount(ui->spNumLines->value());
 
    connect(ui->spNumLines, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged),
 
            [this](int value)
 
            {
 
                ui->textView->setMaximumBlockCount(value);
 
            });
 

	
 
    connect(ui->pbClear, &QPushButton::clicked, ui->textView, &QPlainTextEdit::clear);
 
}
 

	
 
DataTextView::~DataTextView()
 
{
 
    delete sink;
 
    delete ui;
 
}
 

	
 
void DataTextView::addData(const SamplePack& data)
 
{
 
    for (unsigned int i = 0; i < data.numSamples(); i++)
 
    {
 
        QString str;
 
        for (unsigned ci = 0; ci < data.numChannels(); ci++)
 
        {
 
            str += QString::number(data.data(ci)[i], 'f', ui->spDecimals->value());
 
            if (ci != data.numChannels()-1) str += " ";
 
        }
 
        ui->textView->appendPlainText(str);
 
    }
 
}
 

	
 
void DataTextView::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    settings->setValue(SG_TextView_NumLines, ui->spNumLines->value());
 
    settings->setValue(SG_TextView_Decimals, ui->spDecimals->value());
 
    settings->endGroup();
 
}
 

	
 
void DataTextView::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    ui->spNumLines->setValue(
 
        settings->value(SG_TextView_NumLines, ui->spNumLines->value()).toInt());
 
    ui->spDecimals->setValue(
 
        settings->value(SG_TextView_Decimals, ui->spDecimals->value()).toInt());
 
    settings->endGroup();
 
}
src/datatextview.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DATATEXTVIEW_H
 
#define DATATEXTVIEW_H
 

	
 
#include <QWidget>
 

	
 
#include "stream.h"
 

	
 
namespace Ui {
 
class DataTextView;
 
}
 

	
 
class DataTextViewSink;
 

	
 
class DataTextView : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit DataTextView(Stream* stream, QWidget *parent = 0);
 
    ~DataTextView();
 

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

	
 
protected:
 
    void addData(const SamplePack& data);
 

	
 
    friend DataTextViewSink;
 

	
 
private:
 
    Ui::DataTextView *ui;
 
    DataTextViewSink* sink;
 
    Stream* _stream;
 
};
 

	
 
#endif // DATATEXTVIEW_H
src/datatextview.ui
Show inline comments
 
new file 100644
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>DataTextView</class>
 
 <widget class="QWidget" name="DataTextView">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>451</width>
 
    <height>212</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QHBoxLayout" name="horizontalLayout">
 
   <item>
 
    <layout class="QVBoxLayout" name="verticalLayout">
 
     <item>
 
      <widget class="QCheckBox" name="cbEnable">
 
       <property name="toolTip">
 
        <string>Enable display of plotted data as text.</string>
 
       </property>
 
       <property name="text">
 
        <string>Enable</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QLabel" name="label">
 
       <property name="text">
 
        <string>Num. Lines:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QSpinBox" name="spNumLines">
 
       <property name="minimum">
 
        <number>1</number>
 
       </property>
 
       <property name="maximum">
 
        <number>10000</number>
 
       </property>
 
       <property name="value">
 
        <number>1000</number>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QLabel" name="label_2">
 
       <property name="text">
 
        <string>Decimals:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QSpinBox" name="spDecimals">
 
       <property name="maximum">
 
        <number>9</number>
 
       </property>
 
       <property name="value">
 
        <number>6</number>
 
       </property>
 
      </widget>
 
     </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>
 
     <item>
 
      <widget class="QPushButton" name="pbClear">
 
       <property name="text">
 
        <string>Clear</string>
 
       </property>
 
      </widget>
 
     </item>
 
    </layout>
 
   </item>
 
   <item>
 
    <widget class="QPlainTextEdit" name="textView">
 
     <property name="readOnly">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections/>
 
</ui>
src/defines.h
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
const char* BUG_REPORT_URL = "https://bitbucket.org/hyOzd/serialplot/issues/new";
 
#ifndef DEFINES_H
 
#define DEFINES_H
 

	
 
const char BUG_REPORT_URL[] = "https://bitbucket.org/hyOzd/serialplot/issues/new";
 

	
 
/// Maximum number of channels that can be set by user
 
const unsigned MAX_NUM_CHANNELS = 64;
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <math.h>
 

	
 
#include "demoreader.h"
 

	
 
#ifndef M_PI
 
#define M_PI 3.14159265358979323846
 
#endif
 
@@ -32,66 +32,68 @@ DemoReader::DemoReader(QIODevice* device
 
    _numChannels = _settingsWidget.numChannels();
 
    connect(&_settingsWidget, &DemoReaderSettings::numChannelsChanged,
 
            this, &DemoReader::onNumChannelsChanged);
 

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

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

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

	
 
    AbstractReader::enable(enabled);
 
}
 

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

	
 
void DemoReader::setNumChannels(unsigned value)
 
{
 
    _settingsWidget.setNumChannels(value);
 
}
 

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

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

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

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef DEMOREADER_H
 
#define DEMOREADER_H
 

	
 
#include <QTimer>
 

	
 
#include "abstractreader.h"
 
#include "demoreadersettings.h"
 
@@ -34,31 +34,32 @@
 
 *
 
 * This reader should not be enabled when port is open!
 
 */
 
class DemoReader : public AbstractReader
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit DemoReader(QIODevice* device, QObject* parent = 0);
 

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

	
 
public slots:
 
    void setNumChannels(unsigned value);
 

	
 
private:
 
    DemoReaderSettings _settingsWidget;
 

	
 
    unsigned _numChannels;
 
    QTimer timer;
 
    int count;
 

	
 
    unsigned readData() override;
 

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

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

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#include "utils.h"
 
#include "defines.h"
 

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

	
 
    ui->spNumChannels->setMaximum(MAX_NUM_CHANNELS);
 

	
 
    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/floatswap.h
Show inline comments
 
#include <QtGlobal>
 

	
 
#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
 

	
 
template <> inline float qbswap<float>(float source)
 
{
 
    float result;
 
    char* s = (char*) &source;
 
    char* t = (char*) &result;
 
    t[0] = s[3];
 
    t[1] = s[2];
 
    t[2] = s[1];
 
    t[3] = s[0];
 
    return result;
 
}
 

	
 
#endif
src/framebuffer.h
Show inline comments
 
@@ -40,25 +40,45 @@ public:
 
    virtual double sample(unsigned i) const = 0;
 
    /// Returns minimum and maximum of the buffer values.
 
    virtual Range limits() const = 0;
 
};
 

	
 
/// 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;
 
};
 

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

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

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

	
 
#endif // FRAMEBUFFER_H
src/framebufferseries.cpp
Show inline comments
 
/*
 
  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(const FrameBuffer* buffer)
 
FrameBufferSeries::FrameBufferSeries(const XFrameBuffer* x, const FrameBuffer* y)
 
{
 
    xAsIndex = true;
 
    _xmin = 0;
 
    _xmax = 1;
 
    _buffer = buffer;
 
    _x = x;
 
    _y = y;
 

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

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

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

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

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

	
 
    return rect.normalized();
 
}
 

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

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

	
 
    int_index_start = std::max(int_index_start, 0);
 
    int_index_end = std::min((int) _buffer->size(), int_index_end);
 
    if (int_index_end == XFrameBuffer::OUT_OF_RANGE)
 
    {
 
        int_index_end = _x->size()-1;
 
    }
 
    else if (int_index_end < (int)_x->size()-1)
 
    {
 
        int_index_end += 1;
 
    }
 
}
src/framebufferseries.h
Show inline comments
 
@@ -14,46 +14,43 @@
 
  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 FRAMEBUFFERSERIES_H
 
#define FRAMEBUFFERSERIES_H
 

	
 
#include <QPointF>
 
#include <QRectF>
 
#include <qwt_series_data.h>
 

	
 
#include "framebuffer.h"
 

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

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

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

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

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

	
 
#endif // FRAMEBUFFERSERIES_H
src/framedreader.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2020 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, QObject* parent) :
 
@@ -138,153 +138,161 @@ void FramedReader::checkSettings()
 
    // 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(_numChannels * sampleSize);
 

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

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

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

	
 
void FramedReader::onFrameSizeChanged(unsigned value)
 
{
 
    if (value == 0)
 
    {
 
        hasSizeByte = true;
 
    }
 
    else
 
    {
 
        hasSizeByte = false;
 
        frameSize = value;
 
    }
 
    checkSettings();
 
    reset();
 
}
 

	
 
void FramedReader::onDataReady()
 
unsigned FramedReader::readData()
 
{
 
    if (settingsInvalid) return;
 
    unsigned numBytesRead = 0;
 

	
 
    if (settingsInvalid) return numBytesRead;
 

	
 
    // loop until we run out of bytes or more bytes is required
 
    unsigned bytesAvailable;
 
    while ((bytesAvailable = _device->bytesAvailable()))
 
    {
 
        if (!gotSync) // read sync word
 
        {
 
            char c;
 
            _device->getChar(&c);
 
            numBytesRead++;
 
            if (c == syncWord[sync_i]) // correct sync byte?
 
            {
 
                sync_i++;
 
                if (sync_i == (unsigned) syncWord.length())
 
                {
 
                    gotSync = true;
 
                }
 
            }
 
            else
 
            {
 
                if (debugModeEnabled) qCritical() << "Missed " << sync_i+1 << "th sync byte.";
 
            }
 
        }
 
        else if (hasSizeByte && !gotSize) // skipped if fixed frame size
 
        {
 
            frameSize = 0;
 
            _device->getChar((char*) &frameSize);
 
            numBytesRead++;
 

	
 
            if (frameSize == 0) // check size
 
            {
 
                qCritical() << "Frame size is 0!";
 
                reset();
 
            }
 
            else if (frameSize % (_numChannels * sampleSize) != 0)
 
            {
 
                qCritical() <<
 
                    QString("Frame size is not multiple of %1 (#channels * sample size)!") \
 
                    .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))
 
            {
 
                break;
 
            }
 
            else // read data bytes and checksum
 
            {
 
                readFrameDataAndCheck();
 
                numBytesRead += checksumEnabled ? frameSize+1 : frameSize;
 
                reset();
 
            }
 
        }
 
    }
 

	
 
    return numBytesRead;
 
}
 

	
 
void FramedReader::reset()
 
{
 
    sync_i = 0;
 
    gotSync = false;
 
    gotSize = false;
 
    if (hasSizeByte) frameSize = 0;
 
    calcChecksum = 0;
 
}
 

	
 
// 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));
 
        _device->read(checksumEnabled ? frameSize+1 : frameSize);
 
        return;
 
    }
 

	
 
    // a package is 1 set of samples for all channels
 
    unsigned numOfPackagesToRead = frameSize / (_numChannels * sampleSize);
 
    SamplePack samples(numOfPackagesToRead, _numChannels);
 
    for (unsigned i = 0; i < numOfPackagesToRead; i++)
 
    {
 
        for (unsigned int ci = 0; ci < _numChannels; ci++)
 
        {
 
            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);
 
    }
 

	
src/framedreader.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef FRAMEDREADER_H
 
#define FRAMEDREADER_H
 

	
 
#include <QSettings>
 

	
 
#include "abstractreader.h"
 
#include "framedreadersettings.h"
 
@@ -58,34 +58,35 @@ private:
 
    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();
 

	
 
    // read state related members
 
    unsigned sync_i; /// sync byte index to be read next
 
    bool gotSync;    /// indicates if sync word is captured
 
    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();
 

	
 
    unsigned readData() override;
 

	
 
private slots:
 
    void onDataReady() override;
 

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

	
 
#endif // FRAMEDREADER_H
src/framedreadersettings.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QButtonGroup>
 

	
 
#include "utils.h"
 
#include "defines.h"
 
#include "setting_defines.h"
 
#include "framedreadersettings.h"
 
#include "ui_framedreadersettings.h"
 

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

	
 
    ui->leSyncWord->setMode(false); // hex mode
 
    ui->leSyncWord->setText("AA BB");
 
    ui->spNumOfChannels->setMaximum(MAX_NUM_CHANNELS);
 

	
 
    connect(ui->cbChecksum, &QCheckBox::toggled,
 
            [this](bool enabled)
 
            {
 
                emit checksumChanged(enabled);
 
            });
 

	
 
    connect(ui->cbDebugMode, &QCheckBox::toggled,
 
            this, &FramedReaderSettings::debugModeChanged);
 

	
 
    connect(ui->rbFixedSize, &QRadioButton::toggled,
 
            ui->spSize, &QWidget::setEnabled);
 

	
 
    connect(ui->rbFixedSize, &QRadioButton::toggled,
 
            [this](bool checked)
 
            {
 
                emit frameSizeChanged(frameSize());
 
            });
 

	
 
    // Note: if directly connected we get a runtime warning on incompatible signal arguments
 
    connect(ui->spSize, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged),
 
            [this](int value)
 
            {
 
                emit frameSizeChanged(value);
src/indexbuffer.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

	
 
  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.};
 
}
 

	
 
int IndexBuffer::findIndex(double value) const
 
{
 
    if (value < 0 || value > size() - 1)
 
    {
 
        return OUT_OF_RANGE;
 
    }
 
    else
 
    {
 
        return value;
 
    }
 
}
src/indexbuffer.h
Show inline comments
 
@@ -5,39 +5,40 @@
 

	
 
  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
 
class IndexBuffer : public XFrameBuffer
 
{
 
public:
 
    IndexBuffer(unsigned n);
 

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

	
 
private:
 
    unsigned _size;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "ledwidget.h"
 
#include <QPainter>
 
#include <QPainterPath>
 
#include <QRadialGradient>
 

	
 
LedWidget::LedWidget(QWidget* parent) : QWidget(parent),
 
    m_color(107,223,51),
 
    m_on(true)
 
{
 
    resize(20,20);
 
}
 

	
 
QSize LedWidget::sizeHint() const
 
{
 
    return QSize(20,20);
 
}
 

	
 
QSize LedWidget::minimumSizeHint() const
 
{
 
    return QSize(10, 10);
 
}
 

	
 
void LedWidget::setColor(QColor ledColor)
 
{
 
    if (m_color == ledColor) return;
 
    m_color = ledColor;
 
    update();
 
    emit colorChanged(m_color);
 
}
 

	
 
bool LedWidget::isOn()
 
{
 
    return m_on;
 
}
 

	
 
void LedWidget::setOn(bool on)
 
{
 
    if (on == m_on) return;
 
    m_on = on;
 
    update();
 
    emit onChanged(on);
 
}
 

	
 
void LedWidget::turnOn()
 
{
 
    setOn(true);
 
}
 

	
 
void LedWidget::turnOff()
 
{
 
    setOn(false);
 
}
 

	
 
void LedWidget::toggle()
 
{
 
    setOn(!m_on);
 
}
 

	
 
void LedWidget::paintEvent(QPaintEvent* event)
 
{
 
    Q_UNUSED(event)
 

	
 
    const qreal r = std::min(width(), height()) / 2; // maximum radius including glow
 
    const qreal glowOffset = std::max(2., r/5.);
 
    const qreal borderOffset = std::max(1., r/10.);
 
    const qreal shineOffset = std::max(1., r/20.);
 
    const QPointF center(width()/2, height()/2);
 

	
 
    const qreal gr = r;
 
    const qreal br = gr - glowOffset;   // border shape radius
 
    const qreal ir = br - borderOffset; // inner fill radius
 
    const qreal sr = ir - shineOffset;
 

	
 
    QColor borderColor(130,130,130);
 
    QColor shineColor(255, 255, 255, m_on ? 200 : 50);
 
    QColor fillColor(m_color);
 

	
 
    QPainter painter(this);
 
    painter.setRenderHints(QPainter::Antialiasing);
 

	
 
    // draw border
 
    painter.setPen(Qt::NoPen);
 
    painter.setBrush(borderColor);
 
    painter.drawEllipse(center, br, br);
 

	
 
    // draw infill
 
    if (!m_on) fillColor = fillColor.darker();
 
    painter.setBrush(fillColor);
 
    painter.drawEllipse(center, ir, ir);
 

	
 
    // draw glow
 
    if (m_on)
 
    {
 
        QColor glowColor(m_color);
 
        glowColor.setAlphaF(0.5);
 
        QRadialGradient glowGradient(center, gr, center);
 
        glowGradient.setColorAt(0, glowColor);
 
        glowGradient.setColorAt((r-glowOffset)/r, glowColor);
 
        glowGradient.setColorAt(1, Qt::transparent);
 
        painter.setBrush(glowGradient);
 
        painter.drawEllipse(center, gr, gr);
 
    }
 

	
 
    // draw shine
 
    QRadialGradient shineGradient(center, sr, center-QPoint(sr/2,sr/2));
 
    shineGradient.setColorAt(0, shineColor);
 
    shineGradient.setColorAt(1, Qt::transparent);
 
    painter.setBrush(shineGradient);
 
    painter.drawEllipse(center, sr, sr);
 
}
src/ledwidget.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef LEDWIDGET_H
 
#define LEDWIDGET_H
 

	
 
#include <QWidget>
 
#include <QSize>
 
#include <QColor>
 

	
 
class LedWidget : public QWidget
 
{
 
    Q_OBJECT
 

	
 
    Q_PROPERTY(QColor color MEMBER m_color WRITE setColor NOTIFY colorChanged)
 
    Q_PROPERTY(bool on READ isOn WRITE setOn NOTIFY onChanged)
 

	
 
public:
 
    explicit LedWidget(QWidget *parent = 0);
 

	
 
    void setColor(QColor ledColor);
 
    bool isOn();
 

	
 
    QSize sizeHint() const Q_DECL_OVERRIDE;
 
    QSize minimumSizeHint() const Q_DECL_OVERRIDE;
 

	
 
signals:
 
    void colorChanged(QColor ledColor);
 
    void onChanged(bool on);
 

	
 
public slots:
 
    void setOn(bool on);
 
    void turnOn();
 
    void turnOff();
 
    void toggle();
 

	
 
protected:
 
    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
 

	
 
private:
 
    QColor m_color;
 
    bool m_on;
 
};
 

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

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QtGlobal>
 
#include <algorithm>
 

	
 
#include "linindexbuffer.h"
 

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

	
 
    _size = n;
 
    setLimits(lim);
 
}
 

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

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

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

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

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

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

	
 
void LinIndexBuffer::setLimits(Range lim)
 
{
 
    _limits = lim;
 
    _step = (lim.end - lim.start) / (_size-1);
 
}
src/linindexbuffer.h
Show inline comments
 
@@ -5,45 +5,47 @@
 

	
 
  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
 
class LinIndexBuffer : public XFrameBuffer
 
{
 
public:
 
    LinIndexBuffer(unsigned n, Range lim);
 
    LinIndexBuffer(unsigned n, double min, double max) :
 
        LinIndexBuffer(n, {min, max}) {};
 

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

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

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

	
 
#endif
src/main.cpp
Show inline comments
 
/*
 
  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 <QApplication>
 
#include <QtGlobal>
 
#include <iostream>
 

	
 
#include "mainwindow.h"
 
#include "tooltipfilter.h"
 
#include "version.h"
 

	
 
MainWindow* pMainWindow;
 
MainWindow* pMainWindow = nullptr;
 

	
 
void messageHandler(QtMsgType type, const QMessageLogContext &context,
 
                    const QString &msg)
 
{
 
    // TODO: don't call MainWindow::messageHandler if window is destroyed
 
    pMainWindow->messageHandler(type, context, msg);
 
    QString logString;
 

	
 
    switch (type)
 
    {
 
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
 
        case QtInfoMsg:
 
            logString = "[Info] " + msg;
 
            break;
 
#endif
 
        case QtDebugMsg:
 
            logString = "[Debug] " + msg;
 
            break;
 
        case QtWarningMsg:
 
            logString = "[Warning] " + msg;
 
            break;
 
        case QtCriticalMsg:
 
            logString = "[Error] " + msg;
 
            break;
 
        case QtFatalMsg:
 
            logString = "[Fatal] " + msg;
 
            break;
 
    }
 

	
 
    std::cerr << logString.toStdString() << std::endl;
 

	
 
    if (pMainWindow != nullptr)
 
    {
 
        // TODO: don't call MainWindow::messageHandler if window is destroyed
 
        pMainWindow->messageHandler(type, logString, msg);
 
    }
 

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

	
 
int main(int argc, char *argv[])
 
{
 
    QApplication a(argc, argv);
 
    QApplication::setApplicationName(PROGRAM_NAME);
 
    QApplication::setApplicationVersion(VERSION_STRING);
 

	
 
    qInstallMessageHandler(messageHandler);
 
    MainWindow w;
 
    pMainWindow = &w;
 

	
 
    qInstallMessageHandler(messageHandler);
 

	
 
    ToolTipFilter ttf;
 
    a.installEventFilter(&ttf);
 

	
 
    // log application information
 
    qDebug() << "SerialPlot" << VERSION_STRING;
 
    qDebug() << "Revision" << VERSION_REVISION;
 

	
 
    w.show();
 

	
 
    return a.exec();
 
}
src/mainwindow.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "mainwindow.h"
 
#include "ui_mainwindow.h"
 
#include <QByteArray>
 
#include <QApplication>
 
#include <QFileDialog>
 
#include <QMessageBox>
 
#include <QFile>
 
#include <QTextStream>
 
#include <QMenu>
 
#include <QDesktopServices>
 
#include <QMap>
 
#include <QtDebug>
 
#include <QCommandLineParser>
 
#include <QFileInfo>
 
#include <qwt_plot.h>
 
#include <limits.h>
 
#include <cmath>
 
#include <iostream>
 
#include <cstdlib>
 

	
 
#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"},
 
        {4, "Record"},
 
        {5, "Log"}
 
        {5, "TextView"},
 
        {6, "Log"}
 
    });
 

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

	
 
    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->insertTab(5, &textView, "Text View");
 
    ui->tabWidget->setCurrentIndex(0);
 
    auto tbPortControl = portControl.toolBar();
 
    addToolBar(tbPortControl);
 
    addToolBar(recordPanel.toolbar());
 

	
 
    ui->plotToolBar->addAction(snapshotMan.takeSnapshotAction());
 
    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
 
    ui->menuBar->insertMenu(ui->menuSecondary->menuAction(), &plotMenu);
 
    plotMenu.addSeparator();
 
    QMenu* tbMenu = plotMenu.addMenu("Toolbars");
 
@@ -148,171 +155,187 @@ MainWindow::MainWindow(QWidget *parent) 
 

	
 
    QObject::connect(ui->actionLoadSettings, &QAction::triggered,
 
                     this, &MainWindow::onLoadSettings);
 

	
 
    ui->actionQuit->setShortcutContext(Qt::ApplicationShortcut);
 

	
 
    QObject::connect(ui->actionQuit, &QAction::triggered,
 
                     this, &MainWindow::close);
 

	
 
    // port control signals
 
    QObject::connect(&portControl, &PortControl::portToggled,
 
                     this, &MainWindow::onPortToggled);
 

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

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

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

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

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

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

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

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

	
 
    QObject::connect(ui->actionPause, &QAction::triggered,
 
                     &stream, &Stream::pause);
 

	
 
    QObject::connect(ui->actionPause, &QAction::triggered,
 
                     [this](bool enabled)
 
                     {
 
                         if (enabled && !recordPanel.recordPaused())
 
                         {
 
                             dataFormatPanel.pause(true);
 
                         }
 
                         else
 
                         {
 
                             dataFormatPanel.pause(false);
 
                         }
 
                     });
 

	
 
    QObject::connect(&recordPanel, &RecordPanel::recordPausedChanged,
 
                     [this](bool enabled)
 
                     {
 
                         if (ui->actionPause->isChecked() && enabled)
 
                         {
 
                             dataFormatPanel.pause(false);
 
                         }
 
                     });
 

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

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

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

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

	
 
    // init bps (bits per second) counter
 
    ui->statusBar->addPermanentWidget(&bpsLabel);
 

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

	
 
    bpsLabel.setMinimumWidth(70);
 
    bpsLabel.setAlignment(Qt::AlignRight);
 
    spsLabel.setMinimumWidth(70);
 
    spsLabel.setAlignment(Qt::AlignRight);
 

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

	
 
    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");
 
    QSettings settings(PROGRAM_NAME, PROGRAM_NAME);
 
    loadAllSettings(&settings);
 

	
 
    handleCommandLineOptions(*QApplication::instance());
 

	
 
    // 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
 
    // the very first run.
 
    connect(commandPanel.newCommandAction(), &QAction::triggered, [this]()
 
            {
 
                this->ui->tabWidget->setCurrentWidget(&commandPanel);
 
                this->ui->tabWidget->showTabs();
 
            });
 
}
 

	
 
MainWindow::~MainWindow()
 
{
 
    if (serialPort.isOpen())
 
    {
 
        serialPort.close();
 
    }
 

	
 
    delete plotMan;
 

	
 
    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::Cancel);
 
        if (clickedButton == QMessageBox::Cancel)
 
        {
 
            event->ignore();
 
            return;
 
        }
 
    }
 

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

	
 
    if (settings.status() != QSettings::NoError)
 
    {
 
        QString errorText;
 

	
 
        if (settings.status() == QSettings::AccessError)
 
        {
 
            QString file = settings.fileName();
 
            errorText = QString("Serialplot cannot save settings due to access error. \
 
This happens if you have run serialplot as root (with sudo for ex.) previously. \
 
Try fixing the permissions of file: %1, or just delete it.").arg(file);
 
        }
 
        else
 
        {
 
            errorText = QString("Serialplot cannot save settings due to unknown error: %1").\
 
                arg(settings.status());
 
        }
 

	
 
        auto button = QMessageBox::critical(
 
            NULL,
 
            "Failed to save settings!", errorText,
 
            QMessageBox::Cancel | QMessageBox::Ok);
 
@@ -325,48 +348,53 @@ Try fixing the permissions of file: %1, 
 
    }
 

	
 
    QMainWindow::closeEvent(event);
 
}
 

	
 
void MainWindow::setupAboutDialog()
 
{
 
    Ui_AboutDialog uiAboutDialog;
 
    uiAboutDialog.setupUi(&aboutDialog);
 

	
 
    QObject::connect(uiAboutDialog.pbAboutQt, &QPushButton::clicked,
 
                     [](){ QApplication::aboutQt();});
 

	
 
    QString aboutText = uiAboutDialog.lbAbout->text();
 
    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);
 

	
 
    if (!open)
 
    {
 
        spsLabel.setText("0sps");
 
    }
 
}
 

	
 
void MainWindow::onSourceChanged(Source* source)
 
{
 
    source->connectSink(&stream);
 
    source->connectSink(&sampleCounter);
 
}
 

	
 
void MainWindow::clearPlot()
 
{
 
    stream.clear();
 
    plotMan->replot();
 
}
 

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

	
 
void MainWindow::onSpsChanged(float sps)
 
{
 
    int precision = sps < 1. ? 3 : 0;
 
@@ -444,111 +472,85 @@ 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 plotMenu.viewSettings();
 
}
 

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

	
 
    switch (type)
 
    {
 
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
 
        case QtInfoMsg:
 
            logString = "[Info] " + msg;
 
            break;
 
#endif
 
        case QtDebugMsg:
 
            logString = "[Debug] " + msg;
 
            break;
 
        case QtWarningMsg:
 
            logString = "[Warning] " + msg;
 
            break;
 
        case QtCriticalMsg:
 
            logString = "[Error] " + msg;
 
            break;
 
        case QtFatalMsg:
 
            logString = "[Fatal] " + msg;
 
            break;
 
    }
 

	
 
    if (ui != NULL) ui->ptLog->appendPlainText(logString);
 
    std::cerr << logString.toStdString() << std::endl;
 
    if (ui != NULL)
 
        ui->ptLog->appendPlainText(logString);
 

	
 
    if (type != QtDebugMsg && ui != NULL)
 
    {
 
        ui->statusBar->showMessage(msg, 5000);
 
    }
 

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

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

	
 
void MainWindow::loadAllSettings(QSettings* settings)
 
{
 
    loadMWSettings(settings);
 
    portControl.loadSettings(settings);
 
    dataFormatPanel.loadSettings(settings);
 
    stream.loadSettings(settings);
 
    plotControlPanel.loadSettings(settings);
 
    plotMenu.loadSettings(settings);
 
    commandPanel.loadSettings(settings);
 
    recordPanel.loadSettings(settings);
 
    textView.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
 
    settings->setValue(SG_MainWindow_HidePanels,
 
                       ui->tabWidget->hideAction.isChecked());
 
    // save window maximized state
 
    settings->setValue(SG_MainWindow_Maximized,
 
                       bool(windowState() & Qt::WindowMaximized));
 
    // save toolbar/dockwidgets state
 
    settings->setValue(SG_MainWindow_State, saveState());
 
    settings->endGroup();
 
}
 

	
 
void MainWindow::loadMWSettings(QSettings* settings)
 
@@ -584,24 +586,77 @@ void MainWindow::loadMWSettings(QSetting
 

	
 
void MainWindow::onSaveSettings()
 
{
 
    QString fileName = QFileDialog::getSaveFileName(
 
        this, tr("Save Settings"), QString(), "INI (*.ini)");
 

	
 
    if (!fileName.isNull()) // user canceled
 
    {
 
        QSettings settings(fileName, QSettings::IniFormat);
 
        saveAllSettings(&settings);
 
    }
 
}
 

	
 
void MainWindow::onLoadSettings()
 
{
 
    QString fileName = QFileDialog::getOpenFileName(
 
        this, tr("Load Settings"), QString(), "INI (*.ini)");
 

	
 
    if (!fileName.isNull()) // user canceled
 
    {
 
        QSettings settings(fileName, QSettings::IniFormat);
 
        loadAllSettings(&settings);
 
    }
 
}
 

	
 
void MainWindow::handleCommandLineOptions(const QCoreApplication &app)
 
{
 
    QCommandLineParser parser;
 
    parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsCompactedShortOptions);
 
    parser.setApplicationDescription("Small and simple software for plotting data from serial port in realtime.");
 
    parser.addHelpOption();
 
    parser.addVersionOption();
 

	
 
    QCommandLineOption configOpt({"c", "config"}, "Load configuration from file.", "filename");
 
    QCommandLineOption portOpt({"p", "port"}, "Set port name.", "port name");
 
    QCommandLineOption baudrateOpt({"b" ,"baudrate"}, "Set port baud rate.", "baud rate");
 
    QCommandLineOption openPortOpt({"o", "open"}, "Open serial port.");
 

	
 
    parser.addOption(configOpt);
 
    parser.addOption(portOpt);
 
    parser.addOption(baudrateOpt);
 
    parser.addOption(openPortOpt);
 

	
 
    parser.process(app);
 

	
 
    if (parser.isSet(configOpt))
 
    {
 
        QString fileName = parser.value(configOpt);
 
        QFileInfo fileInfo(fileName);
 

	
 
        if (fileInfo.exists() && fileInfo.isFile())
 
        {
 
            QSettings settings(fileName, QSettings::IniFormat);
 
            loadAllSettings(&settings);
 
        }
 
        else
 
        {
 
            qCritical() << "Configuration file not exist. Closing application.";
 
            std::exit(1);
 
        }
 
    }
 

	
 
    if (parser.isSet(portOpt))
 
    {
 
        portControl.selectPort(parser.value(portOpt));
 
    }
 

	
 
    if (parser.isSet(baudrateOpt))
 
    {
 
        portControl.selectBaudrate(parser.value(baudrateOpt));
 
    }
 

	
 
    if (parser.isSet(openPortOpt))
 
    {
 
        portControl.openPort();
 
    }
 
}
src/mainwindow.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef MAINWINDOW_H
 
#define MAINWINDOW_H
 

	
 
#include <QMainWindow>
 
#include <QButtonGroup>
 
#include <QLabel>
 
#include <QString>
 
#include <QVector>
 
#include <QList>
 
#include <QSerialPort>
 
#include <QSignalMapper>
 
#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 "stream.h"
 
#include "snapshotmanager.h"
 
#include "plotmanager.h"
 
#include "plotmenu.h"
 
#include "updatecheckdialog.h"
 
#include "samplecounter.h"
 
#include "datatextview.h"
 
#include "bpslabel.h"
 

	
 
namespace Ui {
 
class MainWindow;
 
}
 

	
 
class MainWindow : public QMainWindow
 
{
 
    Q_OBJECT
 

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

	
 
    PlotViewSettings viewSettings() const;
 

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

	
 
private:
 
    Ui::MainWindow *ui;
 

	
 
    QDialog aboutDialog;
 
    void setupAboutDialog();
 

	
 
    QSerialPort serialPort;
 
    PortControl portControl;
 

	
 
    unsigned int numOfSamples;
 

	
 
    QList<QwtPlotCurve*> curves;
 
    // ChannelManager channelMan;
 
    Stream stream;
 
    PlotManager* plotMan;
 
    QWidget* secondaryPlot;
 
    SnapshotManager snapshotMan;
 
    SampleCounter sampleCounter;
 

	
 
    QLabel spsLabel;
 
    CommandPanel commandPanel;
 
    DataFormatPanel dataFormatPanel;
 
    RecordPanel recordPanel;
 
    PlotControlPanel plotControlPanel;
 
    PlotMenu plotMenu;
 
    DataTextView textView;
 
    UpdateCheckDialog updateCheckDialog;
 
    BPSLabel bpsLabel;
 

	
 
    void handleCommandLineOptions(const QCoreApplication &app);
 

	
 
    /// Returns true if demo is running
 
    bool isDemoRunning();
 
    /// 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 onSourceChanged(Source* source);
 
    void onNumOfSamplesChanged(int value);
src/plot.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QRectF>
 
#include <QKeySequence>
 
#include <QColor>
 
#include <qwt_symbol.h>
 
#include <qwt_plot_curve.h>
 
#include <math.h>
 
#include <algorithm>
 
@@ -70,48 +70,53 @@ Plot::Plot(QWidget* parent) :
 
    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::setDispChannels(QVector<const StreamChannel*> channels)
 
{
 
    zoomer.setDispChannels(channels);
 
}
 

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

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

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

	
 
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);
src/plot.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef PLOT_H
 
#define PLOT_H
 

	
 
#include <QColor>
 
#include <QList>
 
#include <QAction>
 
#include <qwt_plot.h>
 
@@ -29,48 +29,51 @@
 
#include <qwt_plot_legenditem.h>
 
#include <qwt_plot_textlabel.h>
 

	
 
#include "zoomer.h"
 
#include "scalezoomer.h"
 
#include "plotsnapshotoverlay.h"
 

	
 
class Plot : public QwtPlot
 
{
 
    Q_OBJECT
 

	
 
    friend class PlotManager;
 

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

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

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

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

	
 
    /**
 
     * 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:
src/plotcontrolpanel.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

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

	
 
#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 = 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
 
@@ -141,51 +141,51 @@ PlotControlPanel::PlotControlPanel(QWidg
 
    {
 
        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
 
    {
 
        int rmax = pow(2, nbits)-1;
 
        ui->cbRangePresets->addItem(
 
            QString().sprintf("Unsigned %d bits %d to +%d", nbits, 0, rmax),
 
            QVariant::fromValue(Range{0, double(rmax)}));
 
    }
 
    ui->cbRangePresets->addItem("-1 to +1", QVariant::fromValue(Range{-1, +1}));
 
    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);
 
    ui->pbColorSel->setDisabled(true);
 
    setSelectorColor(QColor(0,0,0,0));
 
    connect(ui->pbColorSel, &QPushButton::clicked, this, &PlotControlPanel::onColorSelect);
 

	
 
    // reset buttons
 
    resetAct.setToolTip(tr("Reset channel names and colors"));
 
    resetMenu.addAction(&resetNamesAct);
 
    resetMenu.addAction(&resetColorsAct);
 
    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();
 
@@ -220,48 +220,72 @@ bool PlotControlPanel::askNSConfirmation
 
    auto text = tr("Setting number of samples to a too big value "
 
                   "(>%1) can seriously impact the performance of "
 
                   "the application and cause freezes. Are you sure you "
 
                   "want to change the number of samples to %2?")
 
        .arg(QString::number(NUMSAMPLES_CONFIRM_AT), QString::number(value));
 

	
 
    // TODO: parent the mainwindow
 
    QMessageBox mb(QMessageBox::Warning,
 
                   tr("Confirm Number of Samples"),
 
                   text,
 
                   QMessageBox::Apply | QMessageBox::Cancel,
 
                   this);
 

	
 
    auto cb = new QCheckBox("Don't show this again.");
 
    connect(cb, &QCheckBox::stateChanged, [this](int state)
 
            {
 
                warnNumOfSamples = (state == Qt::Unchecked);
 
            });
 

	
 
    mb.setCheckBox(cb);
 

	
 
    return mb.exec() == QMessageBox::Apply;
 
}
 

	
 
void PlotControlPanel::setSelectorColor(QColor color)
 
{
 
    ui->pbColorSel->setStyleSheet(QString("background-color: %1;").arg(color.name()));
 
}
 

	
 
void PlotControlPanel::onColorSelect()
 
{
 
    auto selection = ui->tvChannelInfo->selectionModel()->currentIndex();
 
    // no selection
 
    if (!selection.isValid()) return;
 

	
 
    // current color
 
    auto model = ui->tvChannelInfo->model();
 
    QColor color = model->data(selection, Qt::ForegroundRole).value<QColor>();
 

	
 
    // show dialog
 
    color = QColorDialog::getColor(color, this);
 

	
 
    if (color.isValid())        // color is set to invalid if user cancels
 
    {
 
        ui->tvChannelInfo->model()->setData(selection, color, Qt::ForegroundRole);
 
    }
 
}
 

	
 
void PlotControlPanel::onAutoScaleChecked(bool checked)
 
{
 
    if (checked)
 
    {
 
        ui->lYmin->setEnabled(false);
 
        ui->lYmax->setEnabled(false);
 
        ui->spYmin->setEnabled(false);
 
        ui->spYmax->setEnabled(false);
 

	
 
        emit yScaleChanged(true); // autoscale
 
    }
 
    else
 
    {
 
        ui->lYmin->setEnabled(true);
 
        ui->lYmax->setEnabled(true);
 
        ui->spYmin->setEnabled(true);
 
        ui->spYmax->setEnabled(true);
 

	
 
        emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
 
    }
 
}
 

	
 
void PlotControlPanel::onYScaleChanged()
 
{
 
@@ -352,99 +376,81 @@ double PlotControlPanel::plotWidth() con
 
        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())
 
                {
 
                    ui->colorSelector->setEnabled(true);
 
                    ui->pbColorSel->setEnabled(true);
 
                    auto model = ui->tvChannelInfo->model();
 
                    color = model->data(current, Qt::ForegroundRole).value<QColor>();
 
                }
 
                else
 
                {
 
                    ui->colorSelector->setDisabled(true);
 
                    ui->pbColorSel->setDisabled(true);
 
                }
 

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

	
 
    connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::selectionChanged,
 
            [this](const QItemSelection & selected, const QItemSelection & deselected)
 
            {
 
                if (!selected.length())
 
                {
 
                    ui->colorSelector->setDisabled(true);
 

	
 
                    // temporarily block signals because `setColor` emits `colorChanged`
 
                    bool wasBlocked = ui->colorSelector->blockSignals(true);
 
                    ui->colorSelector->setColor(QColor(0,0,0,0));
 
                    ui->colorSelector->blockSignals(wasBlocked);
 
                    ui->pbColorSel->setDisabled(true);
 
                    setSelectorColor(QColor(0,0,0,0));
 
                }
 
            });
 

	
 
    connect(ui->colorSelector, &color_widgets::ColorSelector::colorChanged,
 
            [this](QColor color)
 
            {
 
                auto index = ui->tvChannelInfo->selectionModel()->currentIndex();
 
                ui->tvChannelInfo->model()->setData(index, color, Qt::ForegroundRole);
 
            });
 

	
 
    connect(model, &QAbstractItemModel::dataChanged,
 
            [this](const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles = QVector<int> ())
 
            {
 
                auto current = ui->tvChannelInfo->selectionModel()->currentIndex();
 

	
 
                // no current selection
 
                if (!current.isValid()) return;
 

	
 
                auto mod = ui->tvChannelInfo->model();
 
                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);
 
                setSelectorColor(color);
 
            });
 

	
 
    // reset actions
 
    connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos);
 
    connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames);
 
    connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors);
 
    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();
src/plotcontrolpanel.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef PLOTCONTROLPANEL_H
 
#define PLOTCONTROLPANEL_H
 

	
 
#include <QWidget>
 
#include <QSettings>
 
#include <QAction>
 
#include <QMenu>
 
@@ -58,35 +58,39 @@ public:
 
    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,
 
        hideAllAct, resetGainsAct, resetOffsetsAct;
 
    QMenu resetMenu;
 
    QStyledItemDelegate* delegate;
 

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

	
 
    /// Set the color displayed by color selector button
 
    void setSelectorColor(QColor color);
 

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

	
 
#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>704</width>
 
    <height>195</height>
 
    <height>220</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="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>
 
@@ -48,67 +48,76 @@
 
        </property>
 
        <property name="maximumSize">
 
         <size>
 
          <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">
 
         <widget class="QPushButton" name="pbColorSel">
 
          <property name="sizePolicy">
 
           <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
 
           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
 
            <horstretch>0</horstretch>
 
            <verstretch>0</verstretch>
 
           </sizepolicy>
 
          </property>
 
          <property name="minimumSize">
 
           <size>
 
            <width>20</width>
 
            <height>20</height>
 
           </size>
 
          </property>
 
          <property name="maximumSize">
 
           <size>
 
            <width>20</width>
 
            <height>20</height>
 
           </size>
 
          </property>
 
          <property name="styleSheet">
 
           <string notr="true">background-color: rgb(70, 141, 255);</string>
 
          </property>
 
          <property name="text">
 
           <string/>
 
          </property>
 
          <property name="flat">
 
           <bool>false</bool>
 
          </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>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>
 
@@ -390,35 +399,27 @@
 
         <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>
 
  </layout>
 
 </widget>
 
 <customwidgets>
 
  <customwidget>
 
   <class>color_widgets::ColorSelector</class>
 
   <extends>QWidget</extends>
 
   <header>color_selector.hpp</header>
 
   <container>1</container>
 
  </customwidget>
 
 </customwidgets>
 
 <resources/>
 
 <connections/>
 
</ui>
src/plotmanager.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

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

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

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

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

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

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

	
 
}
 

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

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

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

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

	
 
    // TODO: remove when snapshot view supports multi display
 
    checkNoVisChannels();
 
}
 

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

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

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

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

	
 
    // 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());
 
}
 
@@ -127,49 +127,49 @@ PlotManager::~PlotManager()
 
    {
 
        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());
 
            addCurve(_stream->channel(i)->name(), _stream->channel(i)->xData(), _stream->channel(i)->yData());
 
        }
 
    }
 
    else if(numOfChannels < oldNum)
 
    {
 
        removeCurves(oldNum - numOfChannels);
 
    }
 

	
 
    replot();
 
}
 

	
 
void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
 
                                       const QModelIndex &bottomRight,
 
                                       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();
 
        QColor color = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::ForegroundRole).value<QColor>();
 
        bool visible = topLeft.sibling(ci, ChannelInfoModel::COLUMN_VISIBILITY).data(Qt::CheckStateRole).toBool();
 

	
 
        curves[ci]->setTitle(name);
 
@@ -196,82 +196,88 @@ void PlotManager::onChannelInfoChanged(c
 
    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();
 
    }
 

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

	
 
    // setup new layout
 
    setupLayout(isMulti);
 

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

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

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

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

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

	
 
    if (multiPlot)
 
    {
 
@@ -311,85 +317,108 @@ Plot* PlotManager::addPlotWidget()
 

	
 
    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->setPlotWidth(_plotWidth);
 
    if (_xAxisAsIndex)
 
    {
 
        plot->setXAxis(0, _numOfSamples);
 
    }
 
    else
 
    {
 
        plot->setXAxis(_xMin, _xMax);
 
    }
 

	
 
    return plot;
 
}
 

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

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

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

	
 
    // create the plot for the curve if we are on multi display
 
    Plot* plot;
 
    if (isMulti)
 
    {
 
        // create a new plot widget
 
        plot = addPlotWidget();
 
    }
 
    else
 
    {
 
        plot = plotWidgets[0];
 
    }
 

	
 
    if (_stream != nullptr)     // not displaying snapshot
 
    {
 
        QVector<const StreamChannel*> dispChannels;
 
        if (isMulti)
 
        {
 
            dispChannels = QVector<const StreamChannel*>(1, _stream->channel(index));
 
        }
 
        else
 
        {
 
            dispChannels = _stream->allChannels();
 
        }
 
        plot->setDispChannels(dispChannels);
 
    }
 

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

	
 
void PlotManager::removeCurves(unsigned number)
 
{
 
    if (_stream != nullptr)     // not displaying snapshot
 
    {
 
        if (! isMulti)
 
        {
 
            QVector<const StreamChannel*> dispChannels;
 
            dispChannels = _stream->allChannels();
 
            plotWidgets[0]->setDispChannels(dispChannels);
 
        }
 
    }
 

	
 
    for (unsigned i = 0; i < number; i++)
 
    {
 
        if (!curves.isEmpty())
 
        {
 
            delete curves.takeLast();
 
            if (isMulti) // delete corresponding widget as well
 
            {
 
                delete plotWidgets.takeLast();
 
            }
 
        }
 
    }
 
}
 

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

	
 
Plot* PlotManager::plotWidget(unsigned curveIndex)
 
{
 
    if (isMulti)
 
    {
 
        return plotWidgets[curveIndex];
 
    }
 
@@ -460,52 +489,55 @@ void PlotManager::setSymbols(Plot::ShowS
 
{
 
    showSymbols = shown;
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->setSymbols(shown);
 
    }
 
}
 

	
 
void PlotManager::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax)
 
{
 
    _autoScaled = autoScaled;
 
    _yMin = yAxisMin;
 
    _yMax = yAxisMax;
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->setYAxis(autoScaled, yAxisMin, yAxisMax);
 
    }
 
}
 

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

	
 
    int ci = 0;
 
    for (auto curve : curves)
 
    {
 
        FrameBufferSeries* series = static_cast<FrameBufferSeries*>(curve->data());
 
        series->setXAxis(asIndex, xMin, xMax);
 
        series->setX(_stream->channel(ci)->xData());
 
        ci++;
 
    }
 
    for (auto plot : plotWidgets)
 
    {
 
        if (asIndex)
 
        {
 
            plot->setXAxis(0, _numOfSamples);
 
        }
 
        else
 
        {
 
            plot->setXAxis(xMin, xMax);
 
        }
 
    }
 
    replot();
 
}
 

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

	
 
void PlotManager::setNumOfSamples(unsigned value)
src/plotmanager.h
Show inline comments
 
@@ -20,90 +20,90 @@
 
#ifndef PLOTMANAGER_H
 
#define PLOTMANAGER_H
 

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

	
 
#include <qwt_plot_curve.h>
 
#include "plot.h"
 
#include "framebufferseries.h"
 
#include "stream.h"
 
#include "snapshot.h"
 
#include "plotmenu.h"
 

	
 
class PlotManager : public QObject
 
{
 
    Q_OBJECT
 

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

	
 
public slots:
 
    /// Enable/Disable multiple plot display
 
    void setMulti(bool enabled);
 
    /// Update all plot widgets
 
    void replot();
 
    /// Enable display of a "DEMO" label on each plot
 
    void showDemoIndicator(bool show = true);
 
    /// 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;
 
    Plot* emptyPlot;  ///< for displaying when all channels are hidden
 
    const Stream* _stream;       ///< attached stream, can be `NULL`
 
    const Stream* _stream;       ///< attached stream, can be `nullptr`
 
    const ChannelInfoModel* infoModel;
 
    bool isDemoShown;
 
    bool _autoScaled;
 
    double _yMin;
 
    double _yMax;
 
    bool _xAxisAsIndex;
 
    double _xMin;
 
    double _xMax;
 
    unsigned _numOfSamples;
 
    double _plotWidth;
 
    Plot::ShowSymbols showSymbols;
 

	
 
    /// 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);
 
    /// Check and make sure "no visible channels" text is shown
 
    void checkNoVisChannels();
src/plotmenu.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <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),
 
@@ -45,68 +45,68 @@ PlotMenu::PlotMenu(QWidget* parent) :
 
    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),
 
    connect(&setSymbolsAutoAct, SELECT<bool>::OVERLOAD_OF(&QAction::toggled),
 
            [this](bool checked)
 
            {
 
                if (checked) emit symbolShowChanged(Plot::ShowSymbolsAuto);
 
            });
 

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

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

	
 
    // add symbol actions to same group so that they appear as radio buttons
 
    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);
 
@@ -181,44 +181,41 @@ void PlotMenu::saveSettings(QSettings* s
 
    }
 
    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
 
    else if (!showSymbolsStr.isEmpty())
 
    {
 
        qCritical() << "Invalid symbol setting:" << showSymbolsStr;
 
    }
 

	
 
    settings->endGroup();
 
}
src/portcontrol.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "portcontrol.h"
 
#include "ui_portcontrol.h"
 

	
 
#include <QSerialPortInfo>
 
#include <QKeySequence>
 
#include <QLabel>
 
#include <QMap>
 
@@ -58,61 +58,61 @@ PortControl::PortControl(QSerialPort* po
 
    QObject::connect(&openAction, &QAction::triggered,
 
                     this, &PortControl::openActionTriggered);
 

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

	
 
    // setup toolbar
 
    portToolBar.addWidget(&tbPortList);
 
    portToolBar.addAction(&loadPortListAction);
 
    portToolBar.addAction(&openAction);
 

	
 
    // setup port selection widgets
 
    tbPortList.setMinimumWidth(TBPORTLIST_MINWIDTH);
 
    tbPortList.setModel(&portList);
 
    ui->cbPortList->setModel(&portList);
 
    QObject::connect(ui->cbPortList,
 
                     SELECT<int>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::onCbPortListActivated);
 
    QObject::connect(&tbPortList,
 
                     SELECT<int>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::onTbPortListActivated);
 
    QObject::connect(ui->cbPortList,
 
                     SELECT<const QString&>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::selectPort);
 
                     this, &PortControl::selectListedPort);
 
    QObject::connect(&tbPortList,
 
                     SELECT<const QString&>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::selectPort);
 
                     this, &PortControl::selectListedPort);
 

	
 
    // setup buttons
 
    ui->pbOpenPort->setDefaultAction(&openAction);
 
    ui->pbReloadPorts->setDefaultAction(&loadPortListAction);
 

	
 
    // setup baud rate selection widget
 
    QObject::connect(ui->cbBaudRate,
 
                     SELECT<const QString&>::OVERLOAD_OF(&QComboBox::activated),
 
                     this, &PortControl::selectBaudRate);
 
                     this, &PortControl::_selectBaudRate);
 

	
 
    // setup parity selection buttons
 
    parityButtons.addButton(ui->rbNoParity, (int) QSerialPort::NoParity);
 
    parityButtons.addButton(ui->rbEvenParity, (int) QSerialPort::EvenParity);
 
    parityButtons.addButton(ui->rbOddParity, (int) QSerialPort::OddParity);
 

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

	
 
    // setup data bits selection buttons
 
    dataBitsButtons.addButton(ui->rb8Bits, (int) QSerialPort::Data8);
 
    dataBitsButtons.addButton(ui->rb7Bits, (int) QSerialPort::Data7);
 
    dataBitsButtons.addButton(ui->rb6Bits, (int) QSerialPort::Data6);
 
    dataBitsButtons.addButton(ui->rb5Bits, (int) QSerialPort::Data5);
 

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

	
 
    // setup stop bits selection buttons
 
    stopBitsButtons.addButton(ui->rb1StopBit, (int) QSerialPort::OneStop);
 
    stopBitsButtons.addButton(ui->rb2StopBit, (int) QSerialPort::TwoStop);
 

	
 
@@ -177,49 +177,49 @@ PortControl::~PortControl()
 
}
 

	
 
void PortControl::loadPortList()
 
{
 
    QString currentSelection = ui->cbPortList->currentData(PortNameRole).toString();
 
    portList.loadPortList();
 
    int index = portList.indexOf(currentSelection);
 
    if (index >= 0)
 
    {
 
        ui->cbPortList->setCurrentIndex(index);
 
        tbPortList.setCurrentIndex(index);
 
    }
 
}
 

	
 
void PortControl::loadBaudRateList()
 
{
 
    ui->cbBaudRate->clear();
 

	
 
    for (auto baudRate : QSerialPortInfo::standardBaudRates())
 
    {
 
        ui->cbBaudRate->addItem(QString::number(baudRate));
 
    }
 
}
 

	
 
void PortControl::selectBaudRate(QString baudRate)
 
void PortControl::_selectBaudRate(QString baudRate)
 
{
 
    if (serialPort->isOpen())
 
    {
 
        if (!serialPort->setBaudRate(baudRate.toInt()))
 
        {
 
            qCritical() << "Can't set baud rate!";
 
        }
 
    }
 
}
 

	
 
void PortControl::selectParity(int parity)
 
{
 
    if (serialPort->isOpen())
 
    {
 
        if(!serialPort->setParity((QSerialPort::Parity) parity))
 
        {
 
            qCritical() << "Can't set parity option!";
 
        }
 
    }
 
}
 

	
 
void PortControl::selectDataBits(int dataBits)
 
{
 
    if (serialPort->isOpen())
 
@@ -268,73 +268,80 @@ void PortControl::togglePort()
 
        // 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);
 
        if (portIndex < 0) // not in list, add to model and update the selections
 
        {
 
            portList.appendRow(new PortListItem(portText));
 
            ui->cbPortList->setCurrentIndex(portList.rowCount()-1);
 
            tbPortList.setCurrentIndex(portList.rowCount()-1);
 
            portName = portText;
 
        }
 
        else
 
        {
 
            // get the port name from the data field
 
            portName = static_cast<PortListItem*>(portList.item(portIndex))->portName();
 
        }
 

	
 
        serialPort->setPortName(ui->cbPortList->currentData(PortNameRole).toString());
 

	
 
        // open port
 
        if (serialPort->open(QIODevice::ReadWrite))
 
        {
 
            // set port settings
 
            selectBaudRate(ui->cbBaudRate->currentText());
 
            _selectBaudRate(ui->cbBaudRate->currentText());
 
            selectParity((QSerialPort::Parity) parityButtons.checkedId());
 
            selectDataBits((QSerialPort::DataBits) dataBitsButtons.checkedId());
 
            selectStopBits((QSerialPort::StopBits) stopBitsButtons.checkedId());
 
            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)
 
void PortControl::selectListedPort(QString portName)
 
{
 
    // portName may be coming from combobox
 
    portName = portName.split(" ")[0];
 

	
 
    QSerialPortInfo portInfo(portName);
 
    if (portInfo.isNull())
 
    {
 
        qWarning() << "Device doesn't exists:" << portName;
 
    }
 

	
 
    // has selection actually changed
 
    if (portName != serialPort->portName())
 
    {
 
        // if another port is already open, close it by toggling
 
        if (serialPort->isOpen())
 
        {
 
            togglePort();
 

	
 
            // open new selection by toggling
 
            togglePort();
 
        }
 
    }
 
}
 

	
 
QString PortControl::selectedPortName()
 
{
 
    QString portText = ui->cbPortList->currentText();
 
    int portIndex = portList.indexOf(portText);
 
    if (portIndex < 0) // not in the list yet
 
    {
 
        // return the displayed name as port name
 
        return portText;
 
    }
 
    else
 
@@ -345,177 +352,252 @@ QString PortControl::selectedPortName()
 
}
 

	
 
QToolBar* PortControl::toolBar()
 
{
 
    return &portToolBar;
 
}
 

	
 
void PortControl::openActionTriggered(bool checked)
 
{
 
    togglePort();
 
}
 

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

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

	
 
void PortControl::onPortError(QSerialPort::SerialPortError error)
 
{
 
#ifdef Q_OS_UNIX
 
    // For suppressing "Invalid argument" errors that happens with pseudo terminals
 
    auto isPtsInvalidArgErr = [this] () -> bool {
 
        return serialPort->portName().contains("pts/") && serialPort->errorString().contains("Invalid argument");
 
    };
 
#endif
 

	
 
    switch(error)
 
    {
 
        case QSerialPort::NoError :
 
            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:
 
#ifdef Q_OS_UNIX
 
            // Qt 5.5 gives "Invalid argument" with 'UnsupportedOperationError'
 
            if (isPtsInvalidArgErr())
 
                break;
 
#endif
 
            qCritical() << "Operation is not supported.";
 
            break;
 
        case QSerialPort::TimeoutError:
 
            qCritical() << "A timeout error occurred.";
 
            break;
 
        case QSerialPort::UnknownError:
 
#ifdef Q_OS_UNIX
 
            // Qt 5.2 gives "Invalid argument" with 'UnknownError'
 
            if (isPtsInvalidArgErr())
 
                break;
 
#endif
 
            qCritical() << "Unknown error! Error: " << serialPort->errorString();
 
            break;
 
        default:
 
            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";
 
    }
 
    else if (flowControlButtons.checkedId() == QSerialPort::SoftwareControl)
 
    {
 
        return "software";
 
    }
 
    else // no parity
 
    {
 
        return "none";
 
    }
 
}
 

	
 
void PortControl::selectPort(QString portName)
 
{
 
    int portIndex = portList.indexOfName(portName);
 
    if (portIndex < 0) // not in list, add to model and update the selections
 
    {
 
        portList.appendRow(new PortListItem(portName));
 
        portIndex = portList.rowCount()-1;
 
    }
 

	
 
    ui->cbPortList->setCurrentIndex(portIndex);
 
    tbPortList.setCurrentIndex(portIndex);
 

	
 
    selectListedPort(portName);
 
}
 

	
 
void PortControl::selectBaudrate(QString baudRate)
 
{
 
    int baudRateIndex = ui->cbBaudRate->findText(baudRate);
 
    if (baudRateIndex < 0)
 
    {
 
        ui->cbBaudRate->setCurrentText(baudRate);
 
    }
 
    else
 
    {
 
        ui->cbBaudRate->setCurrentIndex(baudRateIndex);
 
    }
 
    _selectBaudRate(baudRate);
 
}
 

	
 
void PortControl::openPort()
 
{
 
    if (!serialPort->isOpen())
 
    {
 
        openAction.trigger();
 
    }
 
}
 

	
 
unsigned PortControl::maxBitRate() const
 
{
 
    float baud = serialPort->baudRate();
 
    float dataBits = serialPort->dataBits();
 
    float parityBits = serialPort->parity() == QSerialPort::NoParity ? 0 : 1;
 

	
 
    float stopBits;
 
    if (serialPort->stopBits() == QSerialPort::OneAndHalfStop)
 
    {
 
        stopBits = 1.5;
 
    }
 
    else
 
    {
 
        stopBits = serialPort->stopBits();
 
    }
 

	
 
    float frame_size = 1 /* start bit */ + dataBits + parityBits + stopBits;
 

	
 
    return float(baud) / frame_size;
 
}
 

	
 
void PortControl::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Port);
 
    settings->setValue(SG_Port_SelectedPort, selectedPortName());
 
    settings->setValue(SG_Port_BaudRate, ui->cbBaudRate->currentText());
 
    settings->setValue(SG_Port_Parity, currentParityText());
 
    settings->setValue(SG_Port_DataBits, dataBitsButtons.checkedId());
 
    settings->setValue(SG_Port_StopBits, stopBitsButtons.checkedId());
 
    settings->setValue(SG_Port_FlowControl, currentFlowControlText());
 
    settings->endGroup();
 
}
 

	
 
void PortControl::loadSettings(QSettings* settings)
 
{
 
    // make sure the port is closed
 
    if (serialPort->isOpen()) togglePort();
 

	
 
    settings->beginGroup(SettingGroup_Port);
 

	
 
    // set port name if it exists in the current list otherwise ignore
 
    QString portName = settings->value(SG_Port_SelectedPort, QString()).toString();
 
    if (!portName.isEmpty())
 
    {
 
        int index = portList.indexOfName(portName);
 
        if (index > -1) ui->cbPortList->setCurrentIndex(index);
 
    }
 

	
 
    // load baud rate setting if it exists in baud rate list
 
    QString baudSetting = settings->value(
 
        SG_Port_BaudRate, ui->cbBaudRate->currentText()).toString();
 
    int baudIndex = ui->cbBaudRate->findText(baudSetting);
 
    if (baudIndex > -1) ui->cbBaudRate->setCurrentIndex(baudIndex);
 

	
 
    // load parity setting
 
    QString parityText =
 
        settings->value(SG_Port_Parity, currentParityText()).toString();
 
    QSerialPort::Parity paritySetting = paritySettingMap.key(
 
        parityText, (QSerialPort::Parity) parityButtons.checkedId());
 
    parityButtons.button(paritySetting)->setChecked(true);
 

	
 
    // load number of bits
 
    int dataBits = settings->value(SG_Port_Parity, dataBitsButtons.checkedId()).toInt();
 
    int dataBits = settings->value(SG_Port_DataBits, dataBitsButtons.checkedId()).toInt();
 
    if (dataBits >=5 && dataBits <= 8)
 
    {
 
        dataBitsButtons.button((QSerialPort::DataBits) dataBits)->setChecked(true);
 
    }
 

	
 
    // load stop bits
 
    int stopBits = settings->value(SG_Port_StopBits, stopBitsButtons.checkedId()).toInt();
 
    if (stopBits == QSerialPort::OneStop)
 
    {
 
        ui->rb1StopBit->setChecked(true);
 
    }
 
    else if (stopBits == QSerialPort::TwoStop)
 
    {
 
        ui->rb2StopBit->setChecked(true);
 
    }
 

	
 
    // load flow control
 
    QString flowControlSetting =
 
        settings->value(SG_Port_FlowControl, currentFlowControlText()).toString();
 
    if (flowControlSetting == "hardware")
 
    {
 
        ui->rbHardwareControl->setChecked(true);
 
    }
 
    else if (flowControlSetting == "software")
src/portcontrol.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#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:
 
    explicit PortControl(QSerialPort* port, QWidget* parent = 0);
 
    ~PortControl();
 

	
 
    QSerialPort* serialPort;
 
    QToolBar* toolBar();
 

	
 
    void selectPort(QString portName);
 
    void selectBaudrate(QString baudRate);
 
    void openPort();
 
    /// Returns maximum bit rate for current baud rate
 
    unsigned maxBitRate() const;
 

	
 
    /// Stores port settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads port settings from a `QSettings`. If open serial port is closed.
 
    void loadSettings(QSettings* settings);
 

	
 
private:
 
    Ui::PortControl *ui;
 

	
 
    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:
 
private slots:
 
    void loadPortList();
 
    void loadBaudRateList();
 
    void togglePort();
 
    void selectPort(QString portName);
 
    void selectListedPort(QString portName);
 

	
 
    void selectBaudRate(QString baudRate);
 
    void _selectBaudRate(QString baudRate);
 
    void selectParity(int parity); // parity must be one of QSerialPort::Parity
 
    void selectDataBits(int dataBits); // bits must be one of QSerialPort::DataBits
 
    void selectStopBits(int stopBits); // stopBits must be one of QSerialPort::StopBits
 
    void selectFlowControl(int flowControl); // flowControl must be one of QSerialPort::FlowControl
 

	
 
private slots:
 
    void openActionTriggered(bool checked);
 
    void onCbPortListActivated(int index);
 
    void onTbPortListActivated(int index);
 
    void onPortError(QSerialPort::SerialPortError error);
 
    void updatePinLeds(void);
 

	
 
signals:
 
    void portToggled(bool open);
 
};
 

	
 
#endif // PORTCONTROL_H
src/setting_defines.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef SETTING_DEFINES_H
 
#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_TextView[] = "TextView";
 
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";
 
const char SG_Port_Parity[] = "parity";
 
const char SG_Port_DataBits[] = "dataBits";
 
const char SG_Port_StopBits[] = "stopBits";
 
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";
 
@@ -91,29 +92,33 @@ const char SG_Plot_AutoScale[] = "autoSc
 
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";
 

	
 
// command setting keys
 
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";
 

	
 
// text view settings keys
 
const char SG_TextView_NumLines[] = "numLines";
 
const char SG_TextView_Decimals[] = "decimals";
 

	
 
// update check settings keys
 
const char SG_UpdateCheck_Periodic[]  = "periodicCheck";
 
const char SG_UpdateCheck_LastCheck[] = "lastCheck";
 

	
 
#endif // SETTING_DEFINES_H
src/snapshot.h
Show inline comments
 
@@ -7,61 +7,63 @@
 
  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 SNAPSHOT_H
 
#define SNAPSHOT_H
 

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

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

	
 
class SnapshotView;
 
class MainWindow;
 

	
 
class Snapshot : public QObject
 
{
 
    Q_OBJECT
 

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

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

	
 
    QString name();
 
    QString displayName(); ///< `name()` plus '*' if snapshot is not saved
 
    unsigned numChannels() const; ///< number of channels in this snapshot
 
    unsigned numSamples() const;  ///< number of samples in every channel
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 
    void setName(QString name);
 
    QString channelName(unsigned channel);
 

	
 
    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:
 
    QString _name;
 
    ChannelInfoModel cInfoModel;
 
    QAction _showAction;
src/snapshotmanager.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QTime>
 
#include <QMenuBar>
 
#include <QKeySequence>
 
#include <QFileDialog>
 
#include <QFile>
 
#include <QTextStream>
 
#include <QVector>
 
@@ -50,48 +50,49 @@ SnapshotManager::SnapshotManager(MainWin
 
            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() const
 
{
 
    QString name = QTime::currentTime().toString("'Snapshot ['HH:mm:ss']'");
 
    auto snapshot = new Snapshot(_mainWindow, name, *(_stream->infoModel()));
 

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

	
 
    return snapshot;
 
}
 

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

	
 
void SnapshotManager::addSnapshot(Snapshot* snapshot, bool update_menu)
 
{
 
    snapshots.append(snapshot);
 
    QObject::connect(snapshot, &Snapshot::deleteRequested,
 
                     this, &SnapshotManager::deleteSnapshot);
 
    if (update_menu) updateMenu();
 
}
 

	
 
void SnapshotManager::updateMenu()
 
{
 
    _menu.clear();
 
    _menu.addAction(&_takeSnapshotAction);
 
    _menu.addAction(&loadSnapshotAction);
 
@@ -135,86 +136,95 @@ void SnapshotManager::loadSnapshots()
 

	
 
    updateMenu();
 
}
 

	
 
void SnapshotManager::loadSnapshotFromFile(QString fileName)
 
{
 
    QFile file(fileName);
 
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
 
    {
 
        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<double>> data(numOfChannels);
 
    QTextStream ts(&file);
 
    QString line;
 
    unsigned lineNum = 1;
 

	
 
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
 
    while (ts.readLineInto(&line))
 
    {
 
#else
 
    while (true)
 
    {
 
        line = ts.readLine();
 
        if (line.isNull()) break;
 
#endif
 
        // parse line
 
        auto split = line.split(',');
 

	
 
        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(y);
 
        }
 
        lineNum++;
 
    }
 

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

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

	
 
    addSnapshot(snapshot, false);
 
}
 

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

	
 
QAction* SnapshotManager::takeSnapshotAction()
 
{
 
    return &_takeSnapshotAction;
 
}
 

	
 
bool SnapshotManager::isAllSaved()
 
{
 
    for (auto snapshot : snapshots)
 
    {
 
        if (!snapshot->isSaved()) return false;
 
    }
 
    return true;
 
}
src/stream.cpp
Show inline comments
 
/*
 
  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"
 
#include "linindexbuffer.h"
 

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

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

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

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

	
 
Stream::~Stream()
 
{
 
    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));
 
}
 

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

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

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

	
 
void Stream::setNumChannels(unsigned nc, bool x)
 
{
 
    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);
 
            // TODO: implement XRingBuffer (binary search)
 
            Q_ASSERT(false);
 
        }
 
        else
 
        {
 
            xData = new IndexBuffer(_numSamples);
 
            xData = makeXBuffer();
 
        }
 

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

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

	
 
    Sink::setNumChannels(nc, x);
 
}
 

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

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

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

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        // TODO: we could use some kind of map (int32, int64 would suffice) to speed things up
 
        bool gainEn = infoModel()->gainEn(ci);
 
        bool offsetEn = infoModel()->offsetEn(ci);
 
        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;
 
@@ -170,79 +199,99 @@ const SamplePack* Stream::applyGainOffse
 
            }
 
            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);
 
        // TODO: implement XRingBuffer (binary search)
 
        Q_ASSERT(false);
 
        // static_cast<RingBuffer*>(xData)->addSamples(pack.xData(), ns);
 
    }
 

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

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        auto buf = static_cast<RingBuffer*>(channels[ci]->yData());
 
        double* data = (mPack == nullptr) ? pack.data(ci) : mPack->data(ci);
 
        buf->addSamples(data, ns);
 
    }
 

	
 
    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::setXAxis(bool asIndex, double min, double max)
 
{
 
    xAsIndex = asIndex;
 
    xMin = min;
 
    xMax = max;
 

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

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

	
 
void Stream::loadSettings(QSettings* settings)
 
{
 
    _infoModel.loadSettings(settings);
 
}
src/stream.h
Show inline comments
 
@@ -27,91 +27,100 @@
 

	
 
#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(unsigned nc = 1, bool x = false, unsigned ns = 2);
 
    ~Stream();
 

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

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

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

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

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

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

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

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

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

	
 
private:
 
    unsigned _numSamples;
 
    bool _paused;
 

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

	
 
    ChannelInfoModel _infoModel;
 

	
 
    bool xAsIndex;
 
    double xMin, xMax;
 

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

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

	
 

	
 
#endif // STREAM_H
src/streamchannel.cpp
Show inline comments
 
/*
 
  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 <limits>
 
#include "streamchannel.h"
 

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

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

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

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

	
 
    if (index >= 0)
 
    {
 
        // can't do estimation for last sample
 
        if (index == (int) _x->size() - 1)
 
        {
 
            return _y->sample(index);
 
        }
 
        else
 
        {
 
            // calculate middle of the line
 
            double prev_x = _x->sample(index);
 
            double next_x = _x->sample(index+1);
 
            double ratio = (x - prev_x) / (next_x - prev_x);
 
            double prev_y = _y->sample(index);
 
            double next_y = _y->sample(index+1);
 
            return ratio * (next_y - prev_y) + prev_y;
 
        }
 
    }
 
    else
 
    {
 
        return std::numeric_limits<double>::quiet_NaN();
 
    }
 
}
src/streamchannel.h
Show inline comments
 
@@ -14,47 +14,55 @@
 
  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,
 
                  const XFrameBuffer* x,
 
                  FrameBuffer* y,
 
                  ChannelInfoModel* info);
 
    ~StreamChannel();
 

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

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

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

	
 
#endif // STREAMCHANNEL_H
src/updatechecker.cpp
Show inline comments
 
@@ -97,49 +97,49 @@ void UpdateChecker::onReqFinished(QNetwo
 
    }
 
    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"];
 
    auto values = data.object().value("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();
src/zoomer.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2020 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

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

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

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

	
 
    setTrackerMode(AlwaysOn);
 

	
 
    // set corner widget between the scrollbars with default background color
 
    auto cornerWidget = new QWidget();
 
    auto bgColor = cornerWidget->palette().color(QPalette::Window).name();
 
    auto styleSheet = QString("background-color:%1;").arg(bgColor);
 
    cornerWidget->setStyleSheet(styleSheet);
 
    ScrollZoomer::setCornerWidget(cornerWidget);
 
}
 

	
 
void Zoomer::zoom(int up)
 
{
 
    ScrollZoomer::zoom(up);
 

	
 
    if(zoomRectIndex() == 0)
 
    {
 
        emit unzoomed();
 
    }
 
}
 

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

	
 
    ScrollZoomer::zoom(rect);
 
}
 

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

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

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

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

	
 
    QString sizeText = QString(" [%1, %2]").\
 
        arg(rect.width(), 0, 'g', 4).\
 
        arg(rect.height(), 0, 'g', 4);
 

	
 
    b.setText(b.text() + sizeText);
 

	
 
    return b;
 
}
 

	
 
void Zoomer::drawRubberBand(QPainter* painter) const
 
{
 
    const double FILL_ALPHA = 0.2;
 

	
 
    QColor color = painter->pen().color();
 
    color.setAlphaF(FILL_ALPHA);
 
    painter->setBrush(color);
 

	
 
    ScrollZoomer::drawRubberBand(painter);
 
}
 

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

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

	
 
QList<const StreamChannel*> Zoomer::visChannels() const
 
{
 
    QList<const StreamChannel*> result;
 

	
 
    for (unsigned ci = 0; ci < (unsigned) dispChannels.length(); ci++)
 
    {
 
        if (dispChannels[ci]->visible())
 
            result.append(dispChannels[ci]);
 
    }
 

	
 
    return result;
 
}
 

	
 
const double ValueLabelHeight = 12; // TODO: calculate
 

	
 
struct ChannelValue
 
{
 
    const StreamChannel* ch;
 
    double value;
 
    double y;
 
    double top() const {return y;};
 
    double bottom() const {return y + ValueLabelHeight;};
 
};
 

	
 
static void layoutValues(QList<ChannelValue>& values)
 
{
 
    typedef ChannelValue LayItem;
 
    typedef QList<LayItem*> LayItemList;
 

	
 
    struct LayGroup
 
    {
 
        struct VRange {double top, bottom;};
 
        LayItemList items;
 
        LayGroup(LayItem* initialItem) {items.append(initialItem);}
 
        unsigned numItems() const {return items.size();}
 
        double top() const {return items.first()->top();}
 
        double bottom() const {return items.last()->bottom();}
 
        VRange vRange() const {return {top(), bottom()};}
 
        double overlap(const LayGroup* otrGroup) const
 
            {
 
                auto myr = vRange();
 
                auto otr = otrGroup->vRange();
 

	
 
                double a = myr.bottom - otr.top;
 
                double b = otr.bottom - myr.top;
 
                if (a > 0 and b > 0)
 
                {
 
                    return std::min(a, b);
 
                }
 
                return 0;
 
            }
 
        void moveBy(double y) {for (auto it : items) it->y += y;}
 
        void join(LayGroup* other)
 
            {
 
                // assumes other group is below this one and they overlap
 
                double ovr_h = overlap(other);
 

	
 
                // groups are moved less if they have more items and vice versa
 
                double ratio = double(numItems()) / double(numItems() + other->numItems());
 
                double self_off = ovr_h * (1. - ratio);
 

	
 
                // make sure we don't go out of screen (above) after the shift
 
                double final_top = top() - self_off;
 
                if (final_top < 0)
 
                    self_off += final_top;
 

	
 
                // move groups
 
                moveBy(-self_off);                // up
 
                other->moveBy(ovr_h - self_off);  // down
 

	
 
                // finalize the merge by gettin items from other
 
                do
 
                {
 
                    items.append(other->items.takeFirst());
 
                } while (!other->items.isEmpty());
 
            }
 
    };
 

	
 
    // create initial groups (1 group per item)
 
    QList<LayGroup*> groups;
 
    for (auto& val : values)
 
        groups.append(new LayGroup(&val));
 

	
 
    // sort groups according to their items position
 
    struct {
 
        bool operator()(LayGroup* a, LayGroup* b) const
 
            {
 
                return a->top() < b->top();
 
            }
 
    } compTops;
 

	
 
    std::sort(groups.begin(), groups.end(), compTops);
 

	
 
    // do spacing
 
    bool somethingOverlaps = true;
 
    while (somethingOverlaps and groups.size() > 1)
 
    {
 
        somethingOverlaps = false;
 
        for (int i = 0; i < groups.size() - 1; i++)
 
        {
 
            auto a = groups[i];
 
            auto b = groups[i + 1];
 

	
 
            // make sure nothing is over the top
 
            if (a->top() < 0)
 
                a->moveBy(-a->top());
 

	
 
            // join if groups overlap
 
            if (a->overlap(b))
 
            {
 
                somethingOverlaps = true;
 
                a->join(b);
 
                delete groups.takeAt(i + 1);
 
                break;
 
            }
 
        }
 
    }
 

	
 
    // cleanup
 
    do
 
    {
 
        delete groups.takeFirst();
 
    } while (!groups.isEmpty());
 
};
 

	
 
void Zoomer::drawValues(QPainter* painter) const
 
{
 
    auto tpos = trackerPosition();
 
    if (tpos.x() < 0) return;   // cursor not on window
 

	
 
    // find Y values for current cursor X position
 
    double x = invTransform(tpos).x();
 
    auto channels = visChannels();
 
    QList<ChannelValue> values;
 
    for (auto ch : channels)
 
    {
 
        double value = ch->findValue(x);
 
        if (!std::isnan(value))
 
        {
 
            auto point = transform(QPointF(x, value));
 
            values.append({ch, value, double(point.y())});
 
        }
 
    }
 

	
 
    // TODO should keep?
 
    if (values.isEmpty())
 
    {
 
        return;
 
    }
 

	
 
    layoutValues(values);
 

	
 
    painter->save();
 

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

	
 
    // draw sample values
 
    for (auto value : values)
 
    {
 
        double val = value.value;
 
        auto ch = value.ch;
 

	
 
        auto point = transform(QPointF(x, val));
 

	
 
        painter->setBrush(ch->color());
 
        painter->setPen(Qt::NoPen);
 
        painter->drawEllipse(point, VALUE_POINT_DIAM, VALUE_POINT_DIAM);
 

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

	
 
    painter->restore();
 
}
 

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

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

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

	
 
void Zoomer::widgetMousePressEvent(QMouseEvent* mouseEvent)
 
{
 
    if (mouseEvent->modifiers() & Qt::ControlModifier)
 
    {
 
        is_panning = true;
 
        parentWidget()->setCursor(Qt::ClosedHandCursor);
 
        pan_point = invTransform(mouseEvent->pos());
 
    }
 
    else
 
    {
 
        ScrollZoomer::widgetMousePressEvent(mouseEvent);
 
    }
 
}
 

	
 
void Zoomer::widgetMouseMoveEvent(QMouseEvent* mouseEvent)
 
{
 
    if (is_panning)
 
    {
 
        auto cur_point = invTransform(mouseEvent->pos());
 
        auto delta = cur_point - pan_point;
 
        moveBy(-delta.x(), -delta.y());
 
        pan_point = invTransform(mouseEvent->pos());
 
    }
 
    else
src/zoomer.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2019 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef ZOOMER_H
 
#define ZOOMER_H
 

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

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

	
 
class Zoomer : public ScrollZoomer
 
{
 
    Q_OBJECT
 

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

	
 
signals:
 
    void unzoomed();
 

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

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

	
 
    /// Get a list of visible channels
 
    QList<const StreamChannel*> visChannels() const;
 
    /// Draw sample values
 
    void drawValues(QPainter* painter) const;
 
    /// Returns trackerRect for value tracker
 
    QRect valueTrackerRect(const QFont& font) const;
 
};
 

	
 
#endif // ZOOMER_H
tests/test.cpp
Show inline comments
 
@@ -203,48 +203,56 @@ TEST_CASE("LinIndexBuffer", "[memory, bu
 
    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);
 

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

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

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

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

	
 
    buf.resize(15);
 
    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.);
 
    }
 
}
tests/test_stream.cpp
Show inline comments
 
@@ -6,97 +6,100 @@
 
  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.numChannels() == 1);
 
    REQUIRE(!s.hasX());
 
    REQUIRE(s.numSamples() == 0);
 
    REQUIRE(s.numSamples() == 2);
 
}
 

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

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

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

	
 
TEST_CASE("changing stream number of channels via sink", "[memory, stream, sink]")
 
{
 
    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);
 
    }
 

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

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

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

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

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

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

	
 
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++)
 
@@ -106,99 +109,102 @@ TEST_CASE("adding data to a stream with 
 
    }
 

	
 
    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);
 
        }
 
    }
 
}
 

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

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

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

	
 
    // test
 
    so._feed(pack);
 

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

	
 
        for (unsigned i = 0; i < 5; i++)
 
        {
 
            REQUIRE(y->sample(i) == 0);
 
        }
 
        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);
 
    }
 
}
 
#endif
 

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

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

	
 
    TestSource so(3, false);
 
    so.connectSink(&s);
 

	
 
    // test
 
    s.pause(true);
 
    so._feed(pack);
 

	
 
    for (unsigned ci = 0; ci < 3; ci++)
 
    {
0 comments (0 inline, 0 general)