Changeset - cd91a52b2383
[Not reviewed]
Merge control-signals
0 43 12
Hasan Yavuz ÖZDERYA - 8 years ago 2017-08-26 06:19:16
hy@ozderya.net
Merge with default
55 files changed with 2386 insertions and 366 deletions:
0 comments (0 inline, 0 general)
.hgtags
Show inline comments
 
@@ -6,6 +6,8 @@ 11a1ee6b27d1cd655193cfdc032aab76ad7a743c
 
878ad8de497561098a63a0d21d36b103cea0719c v0.5.1
 
e545d9bf398aa5728e0ae0236f6c014d9fe55320 v0.6
 
b4d0a38444d31872633e474d89ffc15cd0fe42f0 v0.7
 
27b0354ca2c5ea7b3870156417ce7e04e799bbf7 v0.7.1
 
fd5f1eb480ec372b49df58b497458de05c30057c v0.8.0
 
9c9a11cd15fd094e2b2b65dc51805fd8fd1d2460 v0.8.1
 
4cf9a1ee1f107a38e03dbe17c4f2882c43d827c9 v0.9.0
 
ef003f7af8f37f760c22dae776f5ff8e1b526deb v0.9.1
CMakeLists.txt
Show inline comments
 
@@ -14,13 +14,13 @@
 
# 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 2.8.11)
 
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")
 

	
 
@@ -35,42 +35,42 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PAT
 

	
 
# 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.")
 
# Find QWT or use static manually provided by user
 
set(QWT_USE_STATIC false CACHE BOOL "Use a static version of Qwt provided by user.")
 
set(QWT_STATIC_LIBRARY "" CACHE FILEPATH "Path to the static Qwt library, libqwt.a.")
 
set(QWT_STATIC_INCLUDE "" CACHE PATH "Path to the Qwt include directory when building Qwt static.")
 

	
 
if (BUILD_QWT)
 
  include(BuildQwt)
 
else (BUILD_QWT)
 
  if (QWT_USE_STATIC)
 
	set(QWT_LIBRARY ${QWT_STATIC_LIBRARY})
 
	set(QWT_INCLUDE_DIR ${QWT_STATIC_INCLUDE})
 
  else (QWT_USE_STATIC)
 
	find_package(Qwt 6.1 REQUIRED)
 
  endif (QWT_USE_STATIC)
 
    find_package(Qwt 6.1 REQUIRED)
 
endif (BUILD_QWT)
 

	
 
include(BuildQColorWidgets)
 
# 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} ${QCW_INCLUDE_DIR} ${LEDWIDGET_INCLUDE_DIR})
 
include_directories("./src"
 
  ${QWT_INCLUDE_DIR}
 
  ${QTCOLORWIDGETS_INCLUDE_DIRS}
 
  ${LEDWIDGET_INCLUDE_DIR}
 
  )
 

	
 
# flags
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QCW_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
 
@@ -82,12 +82,13 @@ qt5_wrap_ui(UI_FILES
 
  src/recordpanel.ui
 
  src/numberformatbox.ui
 
  src/endiannessbox.ui
 
  src/binarystreamreadersettings.ui
 
  src/asciireadersettings.ui
 
  src/framedreadersettings.ui
 
  src/updatecheckdialog.ui
 
  )
 

	
 
if (WIN32)
 
  qt5_add_resources(RES_FILES misc/icons.qrc misc/winicons.qrc)
 
else (WIN32)
 
  qt5_add_resources(RES_FILES misc/icons.qrc)
 
@@ -131,30 +132,41 @@ add_executable(${PROGRAM_NAME} WIN32
 
  src/asciireadersettings.cpp
 
  src/demoreader.cpp
 
  src/framedreader.cpp
 
  src/framedreadersettings.cpp
 
  src/plotmanager.cpp
 
  src/numberformat.cpp
 
  src/updatechecker.cpp
 
  src/versionnumber.cpp
 
  src/updatecheckdialog.cpp
 
  misc/windows_icon.rc
 
  ${UI_FILES}
 
  ${RES_FILES}
 
  )
 

	
 
# Use the Widgets module from Qt 5.
 
target_link_libraries(${PROGRAM_NAME} ${QWT_LIBRARY} ${QCW_LIBRARY} ${LEDWIDGET_LIBRARY})
 
qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort)
 
target_link_libraries(${PROGRAM_NAME}
 
  ${QWT_LIBRARY}
 
  ${QTCOLORWIDGETS_LIBRARIES}
 
  ${LEDWIDGET_LIBRARY}
 
  )
 
qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Network)
 

	
 
# external project dependencies
 
if (BUILD_QWT)
 
  add_dependencies(${PROGRAM_NAME} QWT)
 
endif (BUILD_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)
 
@@ -164,40 +176,24 @@ if(COMPILER_SUPPORTS_CXX11)
 
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()
 

	
 
# version number
 
set(MAJOR_VERSION 0 CACHE INT "Program major version number.")
 
set(MINOR_VERSION 8 CACHE INT "Program minor version number.")
 
set(PATCH_VERSION 1 CACHE INT "Program patch version number.")
 
set(VERSION_STRING "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}")
 

	
 
# get revision number from mercurial
 
find_program(MERCURIAL hg)
 
# default version
 
set(VERSION_STRING "0.9.1")
 
set(VERSION_REVISION "0")
 

	
 
if (MERCURIAL)
 
  execute_process(COMMAND ${MERCURIAL} id -i
 
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
 
    RESULT_VARIABLE MERCURIAL_RESULT
 
    OUTPUT_VARIABLE VERSION_REVISION
 
    OUTPUT_STRIP_TRAILING_WHITESPACE)
 
  if(NOT MERCURIAL_RESULT EQUAL 0)
 
    set(VERSION_SCM_REVISION false)
 
  endif(NOT MERCURIAL_RESULT EQUAL 0)
 
endif (MERCURIAL)
 
# get revision number from mercurial and parse version string
 
include(GetVersion)
 

	
 
if (NOT VERSION_REVISION)
 
  set(VERSION_REVISION "0")
 
endif (NOT VERSION_REVISION)
 

	
 
message("SCM revision: ${VERSION_REVISION}")
 

	
 
# configure version file
 
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/version.h")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_STRING=\\\"${VERSION_STRING}\\\" ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MAJOR=${VERSION_MAJOR} ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_MINOR=${VERSION_MINOR} ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_PATCH=${VERSION_PATCH} ")
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_REVISION=\\\"${VERSION_REVISION}\\\" ")
 

	
 
# add make run target
 
add_custom_target(run
 
    COMMAND ${PROGRAM_NAME}
 
    DEPENDS ${PROGRAM_NAME}
 
    WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
 
@@ -210,26 +206,25 @@ install(TARGETS ${PROGRAM_NAME} DESTINAT
 
if (WIN32)
 
  file(GLOB WINDOWS_INSTALL_LIBRARIES
 
    "${CMAKE_BINARY_DIR}/windows_install_libraries/*.*")
 
  install(FILES ${WINDOWS_INSTALL_LIBRARIES} DESTINATION bin)
 
endif (WIN32)
 

	
 
# install menu item and icon
 
# 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)
 

	
 
set(DESKTOP_FILE ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop)
 
set(ICON_FILE ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png)
 

	
 
# install menu item and icon
 
if (UNIX)
 
  # first copy files to share/serialplot/
 
  install(FILES
 
    ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop
 
    DESTINATION share/applications/)
 
  install(FILES
 
    ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png
 
	DESTINATION share/icons/hicolor/256x256/apps/)
 
  install(FILES ${DESKTOP_FILE} DESTINATION share/applications/)
 
  install(FILES ${ICON_FILE} DESTINATION share/icons/hicolor/256x256/apps/)
 
endif (UNIX)
 

	
 
# uninstalling
 
configure_file(
 
  "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
 
  "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
 
@@ -238,26 +233,28 @@ configure_file(
 
if (UNIX)
 
  add_custom_target(uninstall
 
    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
 
endif (UNIX)
 

	
 
# packaging
 
include(BuildLinuxAppImage)
 

	
 
if (UNIX)
 
  set(CPACK_GENERATOR "DEB")
 
elseif (WIN32)
 
  set(CPACK_GENERATOR "NSIS")
 
endif (UNIX)
 

	
 
include(InstallRequiredSystemLibraries)
 

	
 
set(CPACK_PACKAGE_NAME "${PROGRAM_NAME}")
 
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Small and simple software for plotting data from serial port")
 
set(CPACK_PACKAGE_CONTACT "Hasan Yavuz Özderya <hy@ozderya.net>")
 
set(CPACK_PACKAGE_VERSION_MAJOR ${MAJOR_VERSION})
 
set(CPACK_PACKAGE_VERSION_MINOR ${MINOR_VERSION})
 
set(CPACK_PACKAGE_VERSION_PATCH ${PATCH_VERSION})
 
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
 
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
 
set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
 
set(CPACK_STRIP_FILES TRUE)
 
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5widgets5 (>= 5.2.1), libqt5svg5 (>= 5.2.1), libqt5serialport5 (>= 5.2.1), libc6 (>= 2.19)")
 
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Small and simple software for plotting data from serial port
 
 Supports binary data formats ([u]int8, [u]int16, [u]int32, float)
 
 and ASCII (as CSV). Captured waveforms can be exported in CSV format.
 
 Can also send simple user defined commands to serial port device.")
Dockerfile
Show inline comments
 
new file 100644
 
FROM ubuntu:trusty
 

	
 
WORKDIR /serialplot
 
ADD . /serialplot
 

	
 
# Install build dependencies
 
RUN apt-get update
 
RUN apt-get -y install software-properties-common
 
RUN add-apt-repository -y ppa:beineri/opt-qt562-trusty
 
RUN add-apt-repository -y ppa:george-edison55/cmake-3.x
 
RUN apt-get update
 
RUN apt-get -y install build-essential mesa-common-dev qt56base qt56serialport cmake mercurial subversion git wget libfuse2
 

	
 
# Define environment variable
 
ENV PATH /opt/qt56/bin/:$PATH
cmake/modules/BuildLinuxAppImage.cmake
Show inline comments
 
new file 100644
 
# Based on: https://github.com/mhoeher/opentodolist
 

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

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

	
 
add_custom_command(
 
    OUTPUT
 
        ${LINUXDEPLOYQT_TOOL}
 
    COMMAND
 
        wget ${LINUXDEPLOYQT_URL}
 
    COMMAND
 
        chmod a+x ${LINUXDEPLOYQT_TOOL})
 

	
 
add_custom_target(
 
    appimage
 

	
 
    DEPENDS ${LINUXDEPLOYQT_TOOL}
 

	
 
    COMMAND
 
        ${CMAKE_COMMAND} -E remove_directory ${APPIMAGE_DIR}
 
    COMMAND
 
        ${CMAKE_COMMAND} -E make_directory ${APPIMAGE_DIR}
 
    COMMAND
 
        ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PROGRAM_NAME}> ${APPIMAGE_DIR}
 
    COMMAND
 
        ${CMAKE_COMMAND} -E copy ${DESKTOP_FILE} ${APPIMAGE_DIR}
 
    COMMAND
 
        ${CMAKE_COMMAND} -E copy ${ICON_FILE} ${APPIMAGE_DIR}
 
    COMMAND
 
        ${CMAKE_COMMAND} -E env PATH=${QT_INSTALL_PREFIX}/bin:$ENV{PATH} ${LINUXDEPLOYQT_TOOL}
 
            ${APPIMAGE_DIR}/${PROGRAM_NAME} -appimage
 
            -always-overwrite -bundle-non-qt-libs -verbose=2
 
    WORKING_DIRECTORY
 
        ${CMAKE_CURRENT_BINARY_DIR})
cmake/modules/BuildQColorWidgets.cmake
Show inline comments
 
@@ -19,15 +19,18 @@
 

	
 
include(ExternalProject)
 

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

	
 
ExternalProject_Get_Property(QCW binary_dir source_dir)
 
set(QCW_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED")
 
set(QCW_LIBRARY ${binary_dir}/libColorWidgets-qt5.a)
 
set(QCW_INCLUDE_DIR ${source_dir}/include)
 
set(QTCOLORWIDGETS_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED")
 
set(QTCOLORWIDGETS_LIBRARY ${binary_dir}/libColorWidgets-qt5.a)
 
set(QTCOLORWIDGETS_INCLUDE_DIR ${source_dir}/include)
 

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

	
 
# Note: this script is intended for the debian package created for serialplot.
 

	
 
find_library(QTCOLORWIDGETS_LIBRARY "libColorWidgets-qt5.a")
 
find_path(QTCOLORWIDGETS_INCLUDE_DIR "color_preview.hpp" PATHS "/usr/include/qtcolorwidgets/" NO_DEFAULT_PATH)
 

	
 
mark_as_advanced(QTCOLORWIDGETS_LIBRARY QTCOLORWIDGETS_INCLUDE_DIR)
 

	
 
include(FindPackageHandleStandardArgs)
 
find_package_handle_standard_args(QtColorWidgets DEFAULT_MSG QTCOLORWIDGETS_LIBRARY QTCOLORWIDGETS_INCLUDE_DIR)
 

	
 
if (QTCOLORWIDGETS_FOUND)
 
  set(QTCOLORWIDGETS_FLAGS "-D QTCOLORWIDGETS_STATICALLY_LINKED")
 
  mark_as_advanced(QTCOLORWIDGETS_FLAGS)
 
  set(QTCOLORWIDGETS_LIBRARIES ${QTCOLORWIDGETS_LIBRARY})
 
  set(QTCOLORWIDGETS_INCLUDE_DIRS ${QTCOLORWIDGETS_INCLUDE_DIR})
 
endif (QTCOLORWIDGETS_FOUND)
cmake/modules/FindQwt.cmake
Show inline comments
 
#
 
# Copyright © 2015 Hasan Yavuz Özderya
 
# Copyright © 2017 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
@@ -63,13 +63,13 @@ if(qwt_roots)
 
	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"
 
  find_library(QWT_LIBRARY "qwt-qt5"
 
	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)
 
@@ -87,13 +87,13 @@ else (QWT_ROOT)
 
		  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" PATHS /usr/lib)
 
  find_library(QWT_LIBRARY "qwt-qt5" 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})
cmake/modules/GetVersion.cmake
Show inline comments
 
new file 100644
 
#
 
# Copyright © 2017 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
# try to get latest version from mercurial
 
find_program(HG hg)
 

	
 
if (HG)
 
  # get latest tag
 
  execute_process(COMMAND ${HG} parents --template {latesttag}
 
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
 
    RESULT_VARIABLE HG_RESULT
 
    OUTPUT_VARIABLE HG_LATEST_TAG
 
    OUTPUT_STRIP_TRAILING_WHITESPACE)
 
  if(HG_RESULT EQUAL 0)
 
    if (NOT HG_LATEST_TAG MATCHES "v[0-9.]+")
 
      unset(HG_LATEST_TAG)
 
    endif()
 
  else()
 
    unset(HG_LATEST_TAG)
 
  endif()
 

	
 
  # get revision
 
  execute_process(COMMAND ${HG} id -i
 
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
 
    RESULT_VARIABLE HG_RESULT
 
    OUTPUT_VARIABLE HG_REVISION
 
    OUTPUT_STRIP_TRAILING_WHITESPACE)
 
  if(NOT HG_RESULT EQUAL 0)
 
    unset(HG_REVISION)
 
  endif(NOT HG_RESULT EQUAL 0)
 
endif (HG)
 

	
 
# Try to get version from .hg_archival file
 
if (NOT HG_LATEST_TAG)
 
  set(HG_ARCHIVAL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/.hg_archival.txt)
 
  if (EXISTS ${HG_ARCHIVAL_FILE})
 
    # get latest tag
 
    file(STRINGS ${HG_ARCHIVAL_FILE} HG_ARCHIVAL_LATEST_TAG REGEX "^latesttag:.+")
 
    string(REGEX REPLACE "latesttag:[ \t]*(.+)" "\\1" HG_LATEST_TAG ${HG_ARCHIVAL_LATEST_TAG})
 

	
 
    # get latest revision
 
    file(STRINGS ${HG_ARCHIVAL_FILE} HG_ARCHIVAL_REV REGEX "^node:.+")
 
    string(REGEX REPLACE "node:[ \t]*([a-fA-F0-9]+)" "\\1" HG_ARCHIVAL_REV ${HG_ARCHIVAL_REV})
 
    string(SUBSTRING ${HG_ARCHIVAL_REV} 0 12 HG_REVISION)
 
  endif()
 
endif ()
 

	
 
# extract version information from tag (remove 'v' prefix)
 
if (HG_LATEST_TAG)
 
  string(REPLACE "v" "" HG_VERSION ${HG_LATEST_TAG})
 
  message("Version from mercurial: ${HG_VERSION} (${HG_REVISION})")
 

	
 
  # replace version string
 
  set(VERSION_STRING ${HG_VERSION})
 
  set(VERSION_REVISION ${HG_REVISION})
 
else ()
 
  message("Failed to find version information from mercurial.")
 
endif ()
 

	
 
# parse version numbers
 
string(REPLACE "." ";" VERSION_LIST ${VERSION_STRING})
 
list(GET VERSION_LIST 0 VERSION_MAJOR)
 
list(GET VERSION_LIST 1 VERSION_MINOR)
 
list(GET VERSION_LIST 2 VERSION_PATCH)
serialplot.pro
Show inline comments
 
@@ -65,13 +65,15 @@ SOURCES += \
 
    src/asciireadersettings.cpp \
 
    src/asciireader.cpp \
 
    src/demoreader.cpp \
 
    src/framedreader.cpp \
 
    src/plotmanager.cpp \
 
    src/numberformat.cpp \
 
    src/recordpanel.cpp
 
    src/recordpanel.cpp \
 
    src/updatechecker.cpp \
 
    src/updatecheckdialog.cpp
 

	
 
HEADERS += \
 
    src/mainwindow.h \
 
    src/utils.h \
 
    src/portcontrol.h \
 
    src/floatswap.h \
 
@@ -105,13 +107,15 @@ HEADERS += \
 
    src/asciireader.h \
 
    src/demoreader.h \
 
    src/framedreader.h \
 
    src/plotmanager.h \
 
    src/setting_defines.h \
 
    src/numberformat.h \
 
    src/recordpanel.h
 
    src/recordpanel.h \
 
    src/updatechecker.h \
 
    src/updatecheckdialog.h
 

	
 
FORMS += \
 
    src/mainwindow.ui \
 
    src/about_dialog.ui \
 
    src/portcontrol.ui \
 
    src/snapshotview.ui \
 
@@ -121,13 +125,14 @@ FORMS += \
 
    src/plotcontrolpanel.ui \
 
    src/numberformatbox.ui \
 
    src/endiannessbox.ui \
 
    src/framedreadersettings.ui \
 
    src/binarystreamreadersettings.ui \
 
    src/asciireadersettings.ui \
 
    src/recordpanel.ui
 
    src/recordpanel.ui \
 
    src/updatecheckdialog.ui
 

	
 
INCLUDEPATH += qmake/ src/
 

	
 
CONFIG += c++11
 

	
 
RESOURCES += misc/icons.qrc
src/asciireader.cpp
Show inline comments
 
@@ -30,24 +30,31 @@ AsciiReader::AsciiReader(QIODevice* devi
 
{
 
    paused = false;
 
    discardFirstLine = true;
 

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

	
 
    connect(&_settingsWidget, &AsciiReaderSettings::numOfChannelsChanged,
 
            [this](unsigned value)
 
            {
 
                _numOfChannels = value;
 
                autoNumOfChannels = (_numOfChannels == NUMOFCHANNELS_AUTO);
 
                if (!autoNumOfChannels)
 
                {
 
                    emit numOfChannelsChanged(value);
 
                }
 
            });
 

	
 
    connect(&_settingsWidget, &AsciiReaderSettings::delimiterChanged,
 
            [this](QChar d)
 
            {
 
                delimiter = d;
 
            });
 

	
 
    connect(device, &QIODevice::aboutToClose, [this](){discardFirstLine=true;});
 
}
 

	
 
QWidget* AsciiReader::settingsWidget()
 
{
 
    return &_settingsWidget;
 
@@ -87,13 +94,13 @@ void AsciiReader::pause(bool enabled)
 
}
 

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

	
 
        // discard only once when we just started reading
 
        if (discardFirstLine)
 
        {
 
            discardFirstLine = false;
 
            continue;
 
@@ -113,13 +120,13 @@ void AsciiReader::onDataReady()
 
        // empty lines in the input when the port is just opened.
 
        if (line.isEmpty())
 
        {
 
            continue;
 
        }
 

	
 
        auto separatedValues = line.split(',');
 
        auto separatedValues = line.split(delimiter, QString::SkipEmptyParts);
 

	
 
        unsigned numReadChannels; // effective number of channels to read
 
        unsigned numComingChannels = separatedValues.length();
 

	
 
        if (autoNumOfChannels)
 
        {
 
@@ -140,27 +147,32 @@ void AsciiReader::onDataReady()
 
            numReadChannels = separatedValues.length();
 
            qWarning() << "Incoming data is missing data for some channels!";
 
            qWarning() << "Read line: " << line;
 
        }
 

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

	
 
        // commit data
 
        addData(channelSamples, _numOfChannels);
 
        if (numReadChannels > numDataBroken)
 
        {
 
            // commit data
 
            addData(channelSamples, _numOfChannels);
 
        }
 

	
 
        delete[] channelSamples;
 
    }
 
}
 

	
 
void AsciiReader::saveSettings(QSettings* settings)
src/asciireader.h
Show inline comments
 
@@ -45,12 +45,13 @@ public slots:
 

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

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

	
src/asciireadersettings.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -14,26 +14,41 @@
 
  GNU General Public License for more details.
 

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

	
 
#include <QRegularExpressionValidator>
 
#include <QRegularExpression>
 

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

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

	
 
#include <QtDebug>
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	
 
    settings->endGroup();
 
}
 

	
 
void AsciiReaderSettings::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_ASCII);
 
@@ -80,8 +154,29 @@ void AsciiReaderSettings::loadSettings(Q
 
        if (ok)
 
        {
 
            ui->spNumOfChannels->setValue(nc);
 
        }
 
    }
 

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

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

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -19,12 +19,13 @@
 

	
 
#ifndef ASCIIREADERSETTINGS_H
 
#define ASCIIREADERSETTINGS_H
 

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

	
 
namespace Ui {
 
class AsciiReaderSettings;
 
}
 

	
 
class AsciiReaderSettings : public QWidget
 
@@ -32,20 +33,27 @@ class AsciiReaderSettings : public QWidg
 
    Q_OBJECT
 

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

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

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

	
 
private:
 
    Ui::AsciiReaderSettings *ui;
 

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

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

	
 
    // we create channel info but never remove channel info to
 
    // remember user entered info
 
    if ((int) number > infos.length())
 
    {
 
        for (unsigned ci = _numOfChannels; ci < number; ci++)
 
        for (unsigned ci = infos.length(); ci < number; ci++)
 
        {
 
            infos.append(ChannelInfo(ci));
 
        }
 
    }
 

	
 
    // make sure newly available channels are visible, we don't
src/commandedit.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -59,13 +59,13 @@ QValidator::State HexCommandValidator::v
 
}
 

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

	
 
CommandEdit::~CommandEdit()
 
{
src/commandwidget.cpp
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -100,13 +100,20 @@ bool CommandWidget::isASCIIMode()
 
{
 
    return ui->pbASCII->isChecked();
 
}
 

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

	
 
void CommandWidget::setName(QString name)
 
{
 
    ui->leName->setText(name);
 
}
src/datarecorder.cpp
Show inline comments
 
@@ -24,12 +24,13 @@
 
DataRecorder::DataRecorder(QObject *parent) :
 
    QObject(parent),
 
    fileStream(&file)
 
{
 
    lastNumChannels = 0;
 
    disableBuffering = false;
 
    windowsLE = false;
 
}
 

	
 
bool DataRecorder::startRecording(QString fileName, QString separator, QStringList channelNames)
 
{
 
    Q_ASSERT(!file.isOpen());
 
    _sep =  separator;
 
@@ -44,13 +45,13 @@ bool DataRecorder::startRecording(QStrin
 
    }
 

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

	
 
void DataRecorder::addData(double* data, unsigned length, unsigned numOfChannels)
 
@@ -71,19 +72,24 @@ void DataRecorder::addData(double* data,
 
    {
 
        for (unsigned ci = 0; ci < numOfChannels; ci++)
 
        {
 
            fileStream << data[ci * numOfSamples + i];
 
            if (ci != numOfChannels-1) fileStream << _sep;
 
        }
 
        fileStream << '\n';
 
        fileStream << le();
 
    }
 

	
 
    if (disableBuffering) fileStream.flush();
 
}
 

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

	
 
    file.close();
 
    lastNumChannels = 0;
 
}
 

	
 
const char* DataRecorder::le() const
 
{
 
    return windowsLE ? "\r\n" : "\n";
 
}
src/datarecorder.h
Show inline comments
 
@@ -31,12 +31,20 @@ public:
 
    explicit DataRecorder(QObject *parent = 0);
 

	
 
    /// Disables file buffering
 
    bool disableBuffering;
 

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

	
 
    /**
 
     * @brief Starts recording data to a file in CSV format.
 
     *
 
     * File is opened and header line (names of channels) is written.
 
     *
 
     * @param fileName name of the recording file
 
     * @param separator column separator
 
@@ -69,9 +77,12 @@ public:
 

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

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

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

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -14,27 +14,77 @@
 
  GNU General Public License for more details.
 

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

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

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

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

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

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

	
 
QRectF FrameBufferSeries::boundingRect() const
 
{
 
    return _buffer->boundingRect();
 
    if (xAsIndex)
 
    {
 
        return _buffer->boundingRect();
 
    }
 
    else
 
    {
 
        auto rect = _buffer->boundingRect();
 
        rect.setLeft(_xmin);
 
        rect.setRight(_xmax);
 
        return rect;
 
    }
 
}
 

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

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

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -34,16 +34,26 @@
 
 */
 
class FrameBufferSeries : public QwtSeriesData<QPointF>
 
{
 
public:
 
    FrameBufferSeries(FrameBuffer* buffer);
 

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

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

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

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

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

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -74,6 +74,11 @@ void HidableTabWidget::connectSignals()
 
    }
 
    else // shown
 
    {
 
        connect(this, SIGNAL(tabBarDoubleClicked(int)), this, SLOT(onTabBarDoubleClicked()));
 
    }
 
}
 

	
 
void HidableTabWidget::showTabs()
 
{
 
    hideAction.setChecked(false);
 
}
src/hidabletabwidget.h
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -27,12 +27,15 @@ class HidableTabWidget : public QTabWidg
 
{
 
    Q_OBJECT
 
public:
 
    explicit HidableTabWidget(QWidget *parent = 0);
 
    QAction hideAction;
 

	
 
public slots:
 
    void showTabs();
 

	
 
private slots:
 
    void onHideAction(bool checked);
 
    void onTabBarClicked();
 
    void onTabBarDoubleClicked();
 

	
 
    void connectSignals();
src/mainwindow.cpp
Show inline comments
 
@@ -60,13 +60,14 @@ MainWindow::MainWindow(QWidget *parent) 
 
    aboutDialog(this),
 
    portControl(&serialPort),
 
    channelMan(1, 1, this),
 
    snapshotMan(this, &channelMan),
 
    commandPanel(&serialPort),
 
    dataFormatPanel(&serialPort, &channelMan, &recorder),
 
    recordPanel(&recorder, &channelMan)
 
    recordPanel(&recorder, &channelMan),
 
    updateCheckDialog(this)
 
{
 
    ui->setupUi(this);
 

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

	
 
    ui->tabWidget->insertTab(0, &portControl, "Port");
 
@@ -83,12 +84,13 @@ MainWindow::MainWindow(QWidget *parent) 
 
    ui->menuBar->insertMenu(ui->menuHelp->menuAction(), snapshotMan.menu());
 
    ui->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();
 
@@ -108,12 +110,15 @@ MainWindow::MainWindow(QWidget *parent) 
 
    // init UI signals
 

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

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

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

	
 
    // File menu signals
 
    QObject::connect(ui->actionExportCsv, &QAction::triggered,
 
                     this, &MainWindow::onExportCsv);
 
@@ -130,20 +135,24 @@ MainWindow::MainWindow(QWidget *parent) 
 
                     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::onNumOfSamplesChanged);
 
            plotMan, &PlotManager::setNumOfSamples);
 

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

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

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

	
 
    QObject::connect(snapshotMan.takeSnapshotAction(), &QAction::triggered,
 
                     plotMan, &PlotManager::flashSnapshotOverlay);
 
@@ -208,15 +217,18 @@ MainWindow::MainWindow(QWidget *parent) 
 
    // init curve list
 
    for (unsigned int i = 0; i < numOfChannels; i++)
 
    {
 
        plotMan->addCurve(channelMan.channelName(i), channelMan.channelBuffer(i));
 
    }
 

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

	
 
    // Init sps (sample per second) counter
 
    spsLabel.setText("0sps");
 
    spsLabel.setToolTip("samples per second (per channel)");
 
    ui->statusBar->addPermanentWidget(&spsLabel);
 
    QObject::connect(&dataFormatPanel,
 
@@ -243,21 +255,18 @@ MainWindow::MainWindow(QWidget *parent) 
 
    // 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()
 
{
 
    // save settings
 
    QSettings settings("serialplot", "serialplot");
 
    saveAllSettings(&settings);
 

	
 
    if (serialPort.isOpen())
 
    {
 
        serialPort.close();
 
    }
 

	
 
    delete plotMan;
 
@@ -265,25 +274,60 @@ MainWindow::~MainWindow()
 
    delete ui;
 
    ui = NULL; // we check if ui is deleted in messageHandler
 
}
 

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

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

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

	
 
        if (button == QMessageBox::Cancel)
 
        {
 
            event->ignore();
 
            return;
 
        }
 
    }
 

	
 
    QMainWindow::closeEvent(event);
 
}
 

	
 
void MainWindow::setupAboutDialog()
 
{
 
    Ui_AboutDialog uiAboutDialog;
 
@@ -446,12 +490,17 @@ void MainWindow::onExportCsv()
 
        Snapshot* snapshot = snapshotMan.makeSnapshot();
 
        snapshot->save(fileName);
 
        delete snapshot;
 
    }
 
}
 

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

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

	
 
@@ -497,24 +546,26 @@ void MainWindow::saveAllSettings(QSettin
 
    dataFormatPanel.saveSettings(settings);
 
    channelMan.saveSettings(settings);
 
    plotControlPanel.saveSettings(settings);
 
    plotMan->saveSettings(settings);
 
    commandPanel.saveSettings(settings);
 
    recordPanel.saveSettings(settings);
 
    updateCheckDialog.saveSettings(settings);
 
}
 

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

	
 
void MainWindow::saveMWSettings(QSettings* settings)
 
{
 
    // save window geometry
 
    settings->beginGroup(SettingGroup_MainWindow);
src/mainwindow.h
Show inline comments
 
@@ -42,12 +42,13 @@
 
#include "ui_about_dialog.h"
 
#include "framebuffer.h"
 
#include "channelmanager.h"
 
#include "snapshotmanager.h"
 
#include "plotmanager.h"
 
#include "datarecorder.h"
 
#include "updatecheckdialog.h"
 

	
 
namespace Ui {
 
class MainWindow;
 
}
 

	
 
class MainWindow : public QMainWindow
 
@@ -55,12 +56,14 @@ 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);
 

	
 
private:
 
    Ui::MainWindow *ui;
 

	
 
@@ -80,12 +83,13 @@ private:
 

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

	
 
    bool isDemoRunning();
 
    /// Stores settings for all modules
 
    void saveAllSettings(QSettings* settings);
 
    /// Load settings for all modules
 
    void loadAllSettings(QSettings* settings);
src/mainwindow.ui
Show inline comments
 
@@ -87,21 +87,22 @@
 
  <widget class="QMenuBar" name="menuBar">
 
   <property name="geometry">
 
    <rect>
 
     <x>0</x>
 
     <y>0</y>
 
     <width>653</width>
 
     <height>27</height>
 
     <height>25</height>
 
    </rect>
 
   </property>
 
   <widget class="QMenu" name="menuHelp">
 
    <property name="title">
 
     <string>&amp;Help</string>
 
    </property>
 
    <addaction name="actionDemoMode"/>
 
    <addaction name="actionReportBug"/>
 
    <addaction name="actionCheckUpdate"/>
 
    <addaction name="actionHelpAbout"/>
 
   </widget>
 
   <widget class="QMenu" name="menuFile">
 
    <property name="title">
 
     <string>&amp;File</string>
 
    </property>
 
@@ -138,23 +139,33 @@
 
   <property name="checkable">
 
    <bool>true</bool>
 
   </property>
 
   <property name="checked">
 
    <bool>false</bool>
 
   </property>
 
   <property name="icon">
 
    <iconset theme="player_pause">
 
     <normaloff/>
 
    </iconset>
 
   </property>
 
   <property name="text">
 
    <string>Pause</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Pause Plotting</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>P</string>
 
   </property>
 
  </action>
 
  <action name="actionClear">
 
   <property name="icon">
 
    <iconset theme="editclear">
 
     <normaloff/>
 
    </iconset>
 
   </property>
 
   <property name="text">
 
    <string>Clear</string>
 
   </property>
 
   <property name="shortcut">
 
    <string>Ctrl+K</string>
 
   </property>
 
@@ -212,12 +223,17 @@
 
    <string>&amp;Load Settings</string>
 
   </property>
 
   <property name="toolTip">
 
    <string>Load Settings from a File</string>
 
   </property>
 
  </action>
 
  <action name="actionCheckUpdate">
 
   <property name="text">
 
    <string>&amp;Check Update</string>
 
   </property>
 
  </action>
 
 </widget>
 
 <layoutdefault spacing="6" margin="11"/>
 
 <customwidgets>
 
  <customwidget>
 
   <class>HidableTabWidget</class>
 
   <extends>QTabWidget</extends>
src/plot.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -35,12 +35,14 @@ Plot::Plot(QWidget* parent) :
 
    QwtPlot(parent),
 
    zoomer(this->canvas(), false),
 
    sZoomer(this, &zoomer)
 
{
 
    isAutoScaled = true;
 
    symbolSize = 0;
 
    numOfSamples = 1;
 
    showSymbols = Plot::ShowSymbolsAuto;
 

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

	
 
    zoomer.setZoomBase();
 
    grid.attach(this);
 
    legend.attach(this);
 
@@ -68,20 +70,30 @@ Plot::Plot(QWidget* parent) :
 
    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::setAxis(bool autoScaled, double yAxisMin, double yAxisMax)
 
void Plot::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax)
 
{
 
    this->isAutoScaled = autoScaled;
 

	
 
    if (!autoScaled)
 
    {
 
        yMin = yAxisMin;
 
@@ -89,29 +101,51 @@ void Plot::setAxis(bool autoScaled, doub
 
    }
 

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

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

	
 
    zoomer.zoom(0); // unzoom
 

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

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

	
 
    onXScaleChanged();
 
}
 

	
 
void Plot::resetAxes()
 
{
 
    // reset y axis
 
    if (isAutoScaled)
 
    {
 
        setAxisAutoScale(QwtPlot::yLeft);
 
    }
 
    else
 
    {
 
        setAxisScale(QwtPlot::yLeft, yMin, yMax);
 
    }
 

	
 
    zoomer.setZoomBase();
 

	
 
    replot();
 
}
 

	
 
void Plot::unzoomed()
 
{
 
    setAxisAutoScale(QwtPlot::xBottom);
 
    resetAxes();
 
    onXScaleChanged();
 
}
 

	
 
void Plot::showGrid(bool show)
 
{
 
@@ -136,12 +170,18 @@ void Plot::showLegend(bool show)
 
void Plot::showDemoIndicator(bool show)
 
{
 
    demoIndicator.setVisible(show);
 
    replot();
 
}
 

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

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

	
 
void Plot::darkBackground(bool enabled)
 
@@ -221,29 +261,60 @@ void Plot::flashSnapshotOverlay(bool lig
 
            {
 
                delete snapshotOverlay;
 
                snapshotOverlay = NULL;
 
            });
 
}
 

	
 
void Plot::setSymbols(ShowSymbols shown)
 
{
 
    showSymbols = shown;
 

	
 
    if (showSymbols == Plot::ShowSymbolsAuto)
 
    {
 
        calcSymbolSize();
 
    }
 
    else if (showSymbols == Plot::ShowSymbolsShow)
 
    {
 
        symbolSize = SYMBOL_SIZE_MAX;
 
    }
 
    else
 
    {
 
        symbolSize = 0;
 
    }
 

	
 
    updateSymbols();
 
    replot();
 
}
 

	
 
void Plot::onXScaleChanged()
 
{
 
    if (showSymbols == Plot::ShowSymbolsAuto)
 
    {
 
        calcSymbolSize();
 
        updateSymbols();
 
    }
 
}
 

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

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

	
 
    updateSymbols();
 
}
 

	
 
void Plot::updateSymbols()
 
{
 
    const QwtPlotItemList curves = itemList( QwtPlotItem::Rtti_PlotCurve );
 

	
 
@@ -268,13 +339,11 @@ void Plot::updateSymbols()
 
void Plot::resizeEvent(QResizeEvent * event)
 
{
 
    QwtPlot::resizeEvent(event);
 
    onXScaleChanged();
 
}
 

	
 
void Plot::onNumOfSamplesChanged(unsigned value)
 
void Plot::setNumOfSamples(unsigned value)
 
{
 
    auto currentBase = zoomer.zoomBase();
 
    currentBase.setWidth(value);
 
    zoomer.setZoomBase(currentBase);
 
    numOfSamples = value;
 
    onXScaleChanged();
 
}
src/plot.h
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -37,52 +37,67 @@ class Plot : public QwtPlot
 
{
 
    Q_OBJECT
 

	
 
    friend class PlotManager;
 

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

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

	
 
    static QColor makeColor(unsigned int channelIndex);
 

	
 
public slots:
 
    void showGrid(bool show = true);
 
    void showMinorGrid(bool show = true);
 
    void showLegend(bool show = true);
 
    void showDemoIndicator(bool show = true);
 
    void showNoChannel(bool show = true);
 
    void unzoom();
 
    void darkBackground(bool enabled = true);
 
    void setAxis(bool autoScaled, double yMin = 0, double yMax = 1);
 
    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 onNumOfSamplesChanged(unsigned value);
 
    void setNumOfSamples(unsigned value);
 

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

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

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

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

	
src/plotcontrolpanel.cpp
Show inline comments
 
@@ -58,12 +58,18 @@ PlotControlPanel::PlotControlPanel(QWidg
 
    ui->spYmin->setRange((-1) * std::numeric_limits<double>::max(),
 
                         std::numeric_limits<double>::max());
 

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

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

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

	
 
    // connect signals
 
    connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
 
            this, SLOT(onNumOfSamples(int)));
 

	
 
    connect(ui->cbAutoScale, &QCheckBox::toggled,
 
            this, &PlotControlPanel::onAutoScaleChecked);
 
@@ -71,12 +77,21 @@ PlotControlPanel::PlotControlPanel(QWidg
 
    connect(ui->spYmax, SIGNAL(valueChanged(double)),
 
            this, SLOT(onYScaleChanged()));
 

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

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

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

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

	
 
    // init scale range preset list
 
    for (int nbits = 8; nbits <= 24; nbits++) // signed binary formats
 
    {
 
        int rmax = pow(2, nbits-1)-1;
 
        int rmin = -rmax-1;
 
        Range r = {double(rmin),  double(rmax)};
 
@@ -178,53 +193,101 @@ void PlotControlPanel::onAutoScaleChecke
 
    {
 
        ui->lYmin->setEnabled(false);
 
        ui->lYmax->setEnabled(false);
 
        ui->spYmin->setEnabled(false);
 
        ui->spYmax->setEnabled(false);
 

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

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

	
 
void PlotControlPanel::onYScaleChanged()
 
{
 
    emit scaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
 
    if (!autoScale())
 
    {
 
        emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
 
    }
 
}
 

	
 
bool PlotControlPanel::autoScale()
 
bool PlotControlPanel::autoScale() const
 
{
 
    return ui->cbAutoScale->isChecked();
 
}
 

	
 
double PlotControlPanel::yMax()
 
double PlotControlPanel::yMax() const
 
{
 
    return ui->spYmax->value();
 
}
 

	
 
double PlotControlPanel::yMin()
 
double PlotControlPanel::yMin() const
 
{
 
    return ui->spYmin->value();
 
}
 

	
 
bool PlotControlPanel::xAxisAsIndex() const
 
{
 
    return ui->cbIndex->isChecked();
 
}
 

	
 
double PlotControlPanel::xMax() const
 
{
 
    return ui->spXmax->value();
 
}
 

	
 
double PlotControlPanel::xMin() const
 
{
 
    return ui->spXmin->value();
 
}
 

	
 
void PlotControlPanel::onRangeSelected()
 
{
 
    Range r = ui->cbRangePresets->currentData().value<Range>();
 
    ui->spYmin->setValue(r.rmin);
 
    ui->spYmax->setValue(r.rmax);
 
    ui->cbAutoScale->setChecked(false);
 
}
 

	
 
void PlotControlPanel::onIndexChecked(bool checked)
 
{
 
    if (checked)
 
    {
 
        ui->lXmin->setEnabled(false);
 
        ui->lXmax->setEnabled(false);
 
        ui->spXmin->setEnabled(false);
 
        ui->spXmax->setEnabled(false);
 

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

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

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

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

	
 
    // channel color selector
 
    connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::currentRowChanged,
 
@@ -296,23 +359,30 @@ void PlotControlPanel::setChannelInfoMod
 
}
 

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

	
 
void PlotControlPanel::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
    ui->spNumOfSamples->setValue(
 
        settings->value(SG_Plot_NumOfSamples, numOfSamples()).toInt());
 
    ui->cbIndex->setChecked(
 
        settings->value(SG_Plot_IndexAsX, xAxisAsIndex()).toBool());
 
    ui->spXmax->setValue(settings->value(SG_Plot_XMax, xMax()).toDouble());
 
    ui->spXmin->setValue(settings->value(SG_Plot_XMin, xMin()).toDouble());
 
    ui->cbAutoScale->setChecked(
 
        settings->value(SG_Plot_AutoScale, autoScale()).toBool());
 
    ui->spYmax->setValue(settings->value(SG_Plot_YMax, yMax()).toDouble());
 
    ui->spYmin->setValue(settings->value(SG_Plot_YMin, yMin()).toDouble());
 
    settings->endGroup();
 
}
src/plotcontrolpanel.h
Show inline comments
 
@@ -37,26 +37,30 @@ class PlotControlPanel : public QWidget
 

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

	
 
    unsigned numOfSamples();
 
    bool autoScale();
 
    double yMax();
 
    double yMin();
 
    bool   autoScale() const;
 
    double yMax() const;
 
    double yMin() const;
 
    bool   xAxisAsIndex() const;
 
    double xMax() const;
 
    double xMin() const;
 

	
 
    void setChannelInfoModel(ChannelInfoModel* model);
 

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

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

	
 
private:
 
    Ui::PlotControlPanel *ui;
 

	
 
    /// Holds 'number of samples' after the confirmation
 
    unsigned _numOfSamples;
 
@@ -71,9 +75,11 @@ private:
 

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

	
 
#endif // PLOTCONTROLPANEL_H
src/plotcontrolpanel.ui
Show inline comments
 
@@ -3,13 +3,13 @@
 
 <class>PlotControlPanel</class>
 
 <widget class="QWidget" name="PlotControlPanel">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>590</width>
 
    <width>706</width>
 
    <height>187</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
@@ -154,90 +154,181 @@
 
       <property name="value">
 
        <number>1000</number>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="1" column="0">
 
      <widget class="QCheckBox" name="cbIndex">
 
       <property name="text">
 
        <string>Index as X AXis</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="1" column="1">
 
      <layout class="QHBoxLayout" name="horizontalLayout_4">
 
       <item>
 
        <widget class="QLabel" name="lXmin">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="text">
 
          <string>Xmin</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QDoubleSpinBox" name="spXmin">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="minimumSize">
 
          <size>
 
           <width>75</width>
 
           <height>0</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>75</width>
 
           <height>16777215</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>lower limit of Y axis</string>
 
         </property>
 
         <property name="value">
 
          <double>0.000000000000000</double>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QLabel" name="lXmax">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="text">
 
          <string>Xmax</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QDoubleSpinBox" name="spXmax">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>75</width>
 
           <height>16777215</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>upper limit of Y axis</string>
 
         </property>
 
         <property name="maximum">
 
          <double>1000.000000000000000</double>
 
         </property>
 
         <property name="value">
 
          <double>1000.000000000000000</double>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item row="2" column="0">
 
      <widget class="QCheckBox" name="cbAutoScale">
 
       <property name="text">
 
        <string>Auto Scale Y Axis</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="2" column="0">
 
      <widget class="QLabel" name="lYmax">
 
       <property name="enabled">
 
        <bool>false</bool>
 
       </property>
 
       <property name="text">
 
        <string>Ymax</string>
 
       </property>
 
      </widget>
 
     <item row="2" column="1">
 
      <layout class="QHBoxLayout" name="horizontalLayout_3">
 
       <item>
 
        <widget class="QLabel" name="lYmin">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="text">
 
          <string>Ymin</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QDoubleSpinBox" name="spYmin">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="minimumSize">
 
          <size>
 
           <width>75</width>
 
           <height>0</height>
 
          </size>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>75</width>
 
           <height>16777215</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>lower limit of Y axis</string>
 
         </property>
 
         <property name="value">
 
          <double>0.000000000000000</double>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QLabel" name="lYmax">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="text">
 
          <string>Ymax</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item>
 
        <widget class="QDoubleSpinBox" name="spYmax">
 
         <property name="enabled">
 
          <bool>false</bool>
 
         </property>
 
         <property name="maximumSize">
 
          <size>
 
           <width>75</width>
 
           <height>16777215</height>
 
          </size>
 
         </property>
 
         <property name="toolTip">
 
          <string>upper limit of Y axis</string>
 
         </property>
 
         <property name="maximum">
 
          <double>1000.000000000000000</double>
 
         </property>
 
         <property name="value">
 
          <double>1000.000000000000000</double>
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item row="2" column="1">
 
      <widget class="QDoubleSpinBox" name="spYmax">
 
       <property name="enabled">
 
        <bool>false</bool>
 
       </property>
 
       <property name="maximumSize">
 
        <size>
 
         <width>75</width>
 
         <height>16777215</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>upper limit of Y axis</string>
 
       </property>
 
       <property name="maximum">
 
        <double>1000.000000000000000</double>
 
       </property>
 
       <property name="value">
 
        <double>1000.000000000000000</double>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="3" column="0">
 
      <widget class="QLabel" name="lYmin">
 
       <property name="enabled">
 
        <bool>false</bool>
 
       </property>
 
       <property name="text">
 
        <string>Ymin</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="3" column="1">
 
      <widget class="QDoubleSpinBox" name="spYmin">
 
       <property name="enabled">
 
        <bool>false</bool>
 
       </property>
 
       <property name="maximumSize">
 
        <size>
 
         <width>75</width>
 
         <height>16777215</height>
 
        </size>
 
       </property>
 
       <property name="toolTip">
 
        <string>lower limit of Y axis</string>
 
       </property>
 
       <property name="value">
 
        <double>0.000000000000000</double>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="4" column="0">
 
     <item row="5" column="0">
 
      <widget class="QLabel" name="label">
 
       <property name="text">
 
        <string>Select Range Preset:</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item row="4" column="1">
 
     <item row="5" column="1">
 
      <widget class="QComboBox" name="cbRangePresets"/>
 
     </item>
 
    </layout>
 
   </item>
 
   <item>
 
    <spacer name="horizontalSpacer_2">
src/plotmanager.cpp
Show inline comments
 
@@ -14,12 +14,15 @@
 
  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 <QActionGroup>
 
#include <QMetaEnum>
 
#include <QtDebug>
 
#include "qwt_symbol.h"
 

	
 
#include "plot.h"
 
#include "plotmanager.h"
 
#include "utils.h"
 
@@ -30,19 +33,24 @@ PlotManager::PlotManager(QWidget* plotAr
 
    _plotArea(plotArea),
 
    showGridAction("&Grid", this),
 
    showMinorGridAction("&Minor Grid", this),
 
    unzoomAction("&Unzoom", this),
 
    darkBackgroundAction("&Dark Background", this),
 
    showLegendAction("&Legend", this),
 
    showMultiAction("Multi &Plot", this)
 
    showMultiAction("Multi &Plot", this),
 
    setSymbolsAction("Symbols", this)
 
{
 
    _autoScaled = true;
 
    _yMin = 0;
 
    _yMax = 1;
 
    _xAxisAsIndex = true;
 
    isDemoShown = false;
 
    _infoModel = infoModel;
 
    _numOfSamples = 1;
 
    showSymbols = Plot::ShowSymbolsAuto;
 
    emptyPlot = NULL;
 

	
 
    // initalize layout and single widget
 
    isMulti = false;
 
    scrollArea = NULL;
 
    setupLayout(isMulti);
 
    addPlotWidget();
 
@@ -51,12 +59,13 @@ PlotManager::PlotManager(QWidget* plotAr
 
    showGridAction.setToolTip("Show Grid");
 
    showMinorGridAction.setToolTip("Show Minor Grid");
 
    unzoomAction.setToolTip("Unzoom the Plot");
 
    darkBackgroundAction.setToolTip("Enable Dark Plot Background");
 
    showLegendAction.setToolTip("Display the Legend on Plot");
 
    showMultiAction.setToolTip("Display All Channels Separately");
 
    setSymbolsAction.setToolTip("Show/Hide symbols");
 

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

	
 
    showGridAction.setCheckable(true);
 
    showMinorGridAction.setCheckable(true);
 
@@ -69,12 +78,43 @@ PlotManager::PlotManager(QWidget* plotAr
 
    darkBackgroundAction.setChecked(false);
 
    showLegendAction.setChecked(true);
 
    showMultiAction.setChecked(false);
 

	
 
    showMinorGridAction.setEnabled(false);
 

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

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

	
 
    connect(&showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::showGrid);
 
    connect(&showGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            &showMinorGridAction, &QAction::setEnabled);
 
    connect(&showMinorGridAction, SELECT<bool>::OVERLOAD_OF(&QAction::triggered),
 
            this, &PlotManager::showMinorGrid);
 
@@ -115,12 +155,13 @@ PlotManager::~PlotManager()
 
    while (plotWidgets.size())
 
    {
 
        delete plotWidgets.takeLast();
 
    }
 

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

	
 
void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft,
 
                                       const QModelIndex &bottomRight,
 
                                       const QVector<int> &roles)
 
{
 
@@ -148,21 +189,37 @@ void PlotManager::onChannelInfoChanged(c
 
            {
 
                plotWidgets[ci]->replot();
 
            }
 
        }
 
    }
 

	
 
    checkNoVisChannels();
 

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

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

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

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

	
 
    isMulti = enabled;
 

	
 
@@ -183,13 +240,15 @@ void PlotManager::setMulti(bool enabled)
 

	
 
    if (isMulti)
 
    {
 
        // add new widgets and attach
 
        for (auto curve : curves)
 
        {
 
            curve->attach(addPlotWidget());
 
            auto plot = addPlotWidget();
 
            plot->setVisible(curve->isVisible());
 
            curve->attach(plot);
 
        }
 
    }
 
    else
 
    {
 
        // add a single widget
 
        auto plot = addPlotWidget();
 
@@ -197,12 +256,14 @@ void PlotManager::setMulti(bool enabled)
 
        // attach all curves
 
        for (auto curve : curves)
 
        {
 
            curve->attach(plot);
 
        }
 
    }
 

	
 
    checkNoVisChannels();
 
}
 

	
 
void PlotManager::setupLayout(bool multiPlot)
 
{
 
    // delete previous layout if it exists
 
    if (_plotArea->layout() != 0)
 
@@ -248,21 +309,34 @@ Plot* PlotManager::addPlotWidget()
 

	
 
    plot->darkBackground(darkBackgroundAction.isChecked());
 
    plot->showGrid(showGridAction.isChecked());
 
    plot->showMinorGrid(showMinorGridAction.isChecked());
 
    plot->showLegend(showLegendAction.isChecked());
 
    plot->showDemoIndicator(isDemoShown);
 
    plot->setAxis(_autoScaled, _yMin, _yMax);
 
    plot->setYAxis(_autoScaled, _yMin, _yMax);
 
    plot->setNumOfSamples(_numOfSamples);
 
    plot->setSymbols(showSymbols);
 

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

	
 
    return plot;
 
}
 

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

	
 
void PlotManager::addCurve(QString title, QVector<QPointF> data)
 
{
 
    auto curve = new QwtPlotCurve(title);
 
@@ -349,12 +423,13 @@ QList<QAction*> PlotManager::menuActions
 
    actions << &showGridAction;
 
    actions << &showMinorGridAction;
 
    actions << &unzoomAction;
 
    actions << &darkBackgroundAction;
 
    actions << &showLegendAction;
 
    actions << &showMultiAction;
 
    actions << &setSymbolsAction;
 
    return actions;
 
}
 

	
 
void PlotManager::showGrid(bool show)
 
{
 
    for (auto plot : plotWidgets)
 
@@ -401,47 +476,140 @@ void PlotManager::darkBackground(bool en
 
    for (auto plot : plotWidgets)
 
    {
 
        plot->darkBackground(enabled);
 
    }
 
}
 

	
 
void PlotManager::setAxis(bool autoScaled, double yAxisMin, double yAxisMax)
 
void PlotManager::setSymbols(Plot::ShowSymbols shown)
 
{
 
    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->setAxis(autoScaled, yAxisMin, yAxisMax);
 
        plot->setYAxis(autoScaled, yAxisMin, yAxisMax);
 
    }
 
}
 

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

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

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

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

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

	
 
    setSymbols(settings.showSymbols);
 
    if (showSymbols == Plot::ShowSymbolsAuto)
 
    {
 
        setSymbolsAutoAct->setChecked(true);
 
    }
 
    else if (showSymbols == Plot::ShowSymbolsShow)
 
    {
 
        setSymbolsShowAct->setChecked(true);
 
    }
 
    else
 
    {
 
        setSymbolsHideAct->setChecked(true);
 
    }
 
}
 

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

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

	
 
    settings->endGroup();
 
}
 

	
 
void PlotManager::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Plot);
 
@@ -458,8 +626,30 @@ void PlotManager::loadSettings(QSettings
 
    showLegendAction.setChecked(
 
        settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool());
 
    showLegend(showLegendAction.isChecked());
 
    showMultiAction.setChecked(
 
        settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool());
 
    setMulti(showMultiAction.isChecked());
 

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

	
 
    settings->endGroup();
 
}
src/plotmanager.h
Show inline comments
 
@@ -23,18 +23,30 @@
 
#include <QObject>
 
#include <QWidget>
 
#include <QScrollArea>
 
#include <QVBoxLayout>
 
#include <QList>
 
#include <QSettings>
 
#include <QAction>
 
#include <QMenu>
 

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

	
 
struct PlotViewSettings
 
{
 
    bool showGrid;
 
    bool showMinorGrid;
 
    bool darkBackground;
 
    bool showLegend;
 
    bool showMulti;
 
    Plot::ShowSymbols showSymbols;
 
};
 

	
 
class PlotManager : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel = NULL, QObject *parent = 0);
 
@@ -49,12 +61,16 @@ public:
 
    /// Removes curves from the end
 
    void removeCurves(unsigned number);
 
    /// Returns current number of curves known by plot manager
 
    unsigned numOfCurves();
 
    /// Returns the list of actions to be inserted into the `View` menu
 
    QList<QAction*> menuActions();
 
    /// Returns current status of menu actions
 
    PlotViewSettings viewSettings() const;
 
    /// Set the current state of view
 
    void setViewSettings(const PlotViewSettings& settings);
 
    /// Stores plot settings into a `QSettings`.
 
    void saveSettings(QSettings* settings);
 
    /// Loads plot settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
@@ -62,46 +78,62 @@ public slots:
 
    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 setAxis(bool autoScaled, double yMin = 0, double yMax = 1);
 
    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 onNumOfSamplesChanged(unsigned value);
 
    void setNumOfSamples(unsigned value);
 

	
 
private:
 
    bool isMulti;
 
    QWidget* _plotArea;
 
    QVBoxLayout* layout; ///< layout of the `plotArea`
 
    QScrollArea* scrollArea;
 
    QList<QwtPlotCurve*> curves;
 
    QList<Plot*> plotWidgets;
 
    Plot* emptyPlot;  ///< for displaying when all channels are hidden
 
    ChannelInfoModel* _infoModel;
 
    bool isDemoShown;
 
    bool _autoScaled;
 
    double _yMin;
 
    double _yMax;
 
    bool _xAxisAsIndex;
 
    double _xMin;
 
    double _xMax;
 
    unsigned _numOfSamples;
 
    Plot::ShowSymbols showSymbols;
 

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

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

	
 
private slots:
 
    void showGrid(bool show = true);
 
    void showMinorGrid(bool show = true);
 
    void showLegend(bool show = true);
 
    void unzoom();
src/recordpanel.cpp
Show inline comments
 
@@ -57,12 +57,20 @@ RecordPanel::RecordPanel(DataRecorder* r
 

	
 
    connect(ui->cbDisableBuffering, &QCheckBox::toggled,
 
            [this](bool enabled)
 
            {
 
                _recorder->disableBuffering = enabled;
 
            });
 

	
 
    connect(ui->cbWindowsLE, &QCheckBox::toggled,
 
            [this](bool enabled)
 
            {
 
                _recorder->windowsLE = enabled;
 
            });
 

	
 
    connect(&recordAction, &QAction::toggled, ui->cbWindowsLE, &QWidget::setDisabled);
 
}
 

	
 
RecordPanel::~RecordPanel()
 
{
 
    delete ui;
 
}
src/recordpanel.ui
Show inline comments
 
@@ -3,13 +3,13 @@
 
 <class>RecordPanel</class>
 
 <widget class="QWidget" name="RecordPanel">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>532</width>
 
    <width>627</width>
 
    <height>261</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
@@ -41,59 +41,99 @@
 
         </property>
 
        </widget>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <widget class="QCheckBox" name="cbAutoIncrement">
 
       <property name="toolTip">
 
        <string>Increments file name automatically everytime a new recording starts</string>
 
       </property>
 
       <property name="text">
 
        <string>Auto increment file name</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QCheckBox" name="cbRecordPaused">
 
       <property name="toolTip">
 
        <string>Continue recording to file even when plotting is paused</string>
 
       </property>
 
       <property name="text">
 
        <string>Record while paused</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QCheckBox" name="cbStopOnClose">
 
       <property name="text">
 
        <string>Stop recording when port closed</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <widget class="QCheckBox" name="cbHeader">
 
       <property name="toolTip">
 
        <string>Channel names are written to the first line of record file</string>
 
       </property>
 
       <property name="text">
 
        <string>Write header line</string>
 
       </property>
 
       <property name="checked">
 
        <bool>true</bool>
 
       </property>
 
      </widget>
 
      <layout class="QGridLayout" name="gridLayout">
 
       <item row="1" column="1">
 
        <widget class="QCheckBox" name="cbHeader">
 
         <property name="toolTip">
 
          <string>Channel names are written to the first line of record file</string>
 
         </property>
 
         <property name="text">
 
          <string>Write header line</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="2" column="0">
 
        <widget class="QCheckBox" name="cbRecordPaused">
 
         <property name="toolTip">
 
          <string>Continue recording to file even when plotting is paused</string>
 
         </property>
 
         <property name="text">
 
          <string>Record while paused</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="0">
 
        <widget class="QCheckBox" name="cbDisableBuffering">
 
         <property name="toolTip">
 
          <string>Do not buffer when writing to file. Check this if you are using other software to open the file during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Disable buffering</string>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="2" column="1">
 
        <widget class="QCheckBox" name="cbStopOnClose">
 
         <property name="text">
 
          <string>Stop recording when port closed</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="0">
 
        <widget class="QCheckBox" name="cbAutoIncrement">
 
         <property name="toolTip">
 
          <string>Increments file name automatically everytime a new recording starts</string>
 
         </property>
 
         <property name="text">
 
          <string>Auto increment file name</string>
 
         </property>
 
         <property name="checked">
 
          <bool>true</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="3" column="1">
 
        <widget class="QCheckBox" name="cbWindowsLE">
 
         <property name="toolTip">
 
          <string>Use CR+LF as line endings. Some windows software may not show lines correctly otherwise. Can't be changed during recording.</string>
 
         </property>
 
         <property name="text">
 
          <string>Windows Style Line Endings</string>
 
         </property>
 
         <property name="checked">
 
          <bool>false</bool>
 
         </property>
 
        </widget>
 
       </item>
 
       <item row="1" column="2">
 
        <spacer name="horizontalSpacer_2">
 
         <property name="orientation">
 
          <enum>Qt::Horizontal</enum>
 
         </property>
 
         <property name="sizeHint" stdset="0">
 
          <size>
 
           <width>1</width>
 
           <height>20</height>
 
          </size>
 
         </property>
 
        </spacer>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <layout class="QHBoxLayout" name="horizontalLayout_3">
 
       <item>
 
        <widget class="QLabel" name="label">
 
         <property name="text">
 
@@ -130,22 +170,12 @@
 
         </property>
 
        </spacer>
 
       </item>
 
      </layout>
 
     </item>
 
     <item>
 
      <widget class="QCheckBox" name="cbDisableBuffering">
 
       <property name="toolTip">
 
        <string>Do not buffer when writing to file. Check this if you are using other software to open the file during recording.</string>
 
       </property>
 
       <property name="text">
 
        <string>Disable buffering</string>
 
       </property>
 
      </widget>
 
     </item>
 
     <item>
 
      <spacer name="verticalSpacer">
 
       <property name="orientation">
 
        <enum>Qt::Vertical</enum>
 
       </property>
 
       <property name="sizeHint" stdset="0">
 
        <size>
src/scalepicker.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -20,12 +20,13 @@
 
#include <QEvent>
 
#include <QMouseEvent>
 
#include <QPainter>
 
#include <qwt_scale_widget.h>
 
#include <qwt_scale_map.h>
 
#include <qwt_scale_div.h>
 
#include <qwt_text.h>
 
#include <math.h>
 

	
 
#include "scalepicker.h"
 

	
 
// minimum size for pick (in pixels)
 
#define MIN_PICK_SIZE (2)
 
@@ -104,13 +105,13 @@ bool ScalePicker::eventFilter(QObject* o
 

	
 
        // do snapping unless Shift is pressed
 
        if (! (mouseEvent->modifiers() & Qt::ShiftModifier))
 
        {
 
            for (auto sp : snapPoints)
 
            {
 
                if (fabs(posPx-sp) <= SNAP_DISTANCE)
 
                if (std::abs(posPx-sp) <= SNAP_DISTANCE)
 
                {
 
                    posPx = sp;
 
                    break;
 
                }
 
            }
 
        }
 
@@ -129,103 +130,272 @@ bool ScalePicker::eventFilter(QObject* o
 
        {
 
            // make sure pick size is big enough, so that just
 
            // clicking won't trigger pick
 
            if (!started && pressed && (fabs(posPx-firstPosPx) > MIN_PICK_SIZE))
 
            {
 
                started = true;
 
                emit pickStarted(pos);
 
            }
 
            else if (started)
 
            {
 
                pickerOverlay->updateOverlay();
 
                emit picking(firstPos, pos);
 
            }
 
            pickerOverlay->updateOverlay();
 
            scaleOverlay->updateOverlay();
 
        }
 
        else // event->type() == QEvent::MouseButtonRelease
 
        {
 
            pressed = false;
 
            if (started)
 
            {
 
                // finalize
 
                started = false;
 
                emit picked(firstPos, pos);
 
                if (firstPos != pos) // ignore 0 width zoom
 
                {
 
                    emit picked(firstPos, pos);
 
                }
 
            }
 
            pickerOverlay->updateOverlay();
 
            scaleOverlay->updateOverlay();
 
        }
 
        return true;
 
    }
 
    else if (event->type() == QEvent::Leave)
 
    {
 
        scaleOverlay->updateOverlay();
 
        pickerOverlay->updateOverlay();
 
        return true;
 
    }
 
    else
 
    {
 
        return QObject::eventFilter(object, event);
 
    }
 
}
 

	
 
const int TEXT_MARGIN = 4;
 

	
 
void ScalePicker::drawPlotOverlay(QPainter* painter)
 
{
 
    const double FILL_ALPHA = 0.2;
 

	
 
    painter->save();
 
    painter->setPen(_pen);
 

	
 
    if (started)
 
    {
 
        painter->save();
 
        painter->setPen(_pen);
 
        QColor color = _pen.color();
 
        color.setAlphaF(FILL_ALPHA);
 
        painter->setBrush(color);
 

	
 
        QRect rect;
 
        QwtText text = trackerText();
 
        auto tSize = text.textSize(painter->font());
 

	
 
        if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
 
            _scaleWidget->alignment() == QwtScaleDraw::TopScale)
 
        {
 
            int height = painter->device()->height();
 
            rect = QRect(posCanvasPx(firstPosPx), 0, currentPosPx-firstPosPx, height);
 
            int canvasHeight = painter->device()->height();
 
            int pickWidth = currentPosPx-firstPosPx;
 
            rect = QRect(posCanvasPx(firstPosPx), 0, pickWidth, canvasHeight);
 
        }
 
        else // vertical
 
        {
 
            int width = painter->device()->width();
 
            rect = QRect(0, posCanvasPx(firstPosPx), width, currentPosPx-firstPosPx);
 
            int canvasWidth = painter->device()->width();
 
            int pickHeight = currentPosPx-firstPosPx;
 
            rect = QRect(0, posCanvasPx(firstPosPx), canvasWidth, pickHeight);
 
        }
 
        painter->drawRect(rect);
 
        painter->restore();
 
        text.draw(painter, pickTrackerTextRect(painter, rect, tSize));
 
    }
 
    else if (_scaleWidget->underMouse())
 
    {
 
        // draw tracker text centered on cursor
 
        QwtText text = trackerText();
 
        auto tsize = text.textSize(painter->font());
 
        text.draw(painter, trackerTextRect(painter, currentPosPx, tsize));
 
    }
 
    painter->restore();
 
}
 

	
 
QwtText ScalePicker::trackerText() const
 
{
 
    double pos;
 
    // use stored value if snapped to restore precision
 
    if (snapPointMap.contains(currentPosPx))
 
    {
 
        pos = snapPointMap[currentPosPx];
 
    }
 
    else
 
    {
 
        pos = position(currentPosPx);
 
    }
 

	
 
    return QwtText(QString("%1").arg(pos));
 
}
 

	
 
QRectF ScalePicker::trackerTextRect(QPainter* painter, int posPx, QSizeF textSize) const
 
{
 
    int canvasPosPx = posCanvasPx(posPx);
 
    QPointF topLeft;
 

	
 
    if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
 
        _scaleWidget->alignment() == QwtScaleDraw::TopScale)
 
    {
 
        int left = canvasPosPx - textSize.width() / 2;
 
        int canvasWidth = painter->device()->width();
 
        left = std::max(TEXT_MARGIN, left);
 
        left = std::min(double(left), canvasWidth - textSize.width() - TEXT_MARGIN);
 
        int top = 0;
 
        if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale)
 
        {
 
            top = painter->device()->height() - textSize.height();
 
        }
 
        topLeft = QPointF(left, top);
 
    }
 
    else                        // left/right scales
 
    {
 
        int top = canvasPosPx-textSize.height() / 2;
 
        int canvasHeight = painter->device()->height();
 
        top = std::max(0, top);
 
        top = std::min(double(top), canvasHeight - textSize.height());
 
        int left = TEXT_MARGIN;
 
        if (_scaleWidget->alignment() == QwtScaleDraw::RightScale)
 
        {
 
            left = painter->device()->width() - textSize.width();
 
        }
 
        topLeft = QPointF(left, top);
 
    }
 
    return QRectF(topLeft, textSize);
 
}
 

	
 
QRectF ScalePicker::pickTrackerTextRect(QPainter* painter, QRect pickRect, QSizeF textSize) const
 
{
 
    qreal left = 0;
 
    int pickLength = currentPosPx - firstPosPx;
 
    QPointF topLeft;
 

	
 
    if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
 
        _scaleWidget->alignment() == QwtScaleDraw::TopScale)
 
    {
 
        int canvasWidth = painter->device()->width();
 

	
 
        if (pickLength > 0)
 
        {
 
            left = pickRect.right() + TEXT_MARGIN;
 
        }
 
        else
 
        {
 
            left = pickRect.right() - (textSize.width() + TEXT_MARGIN);
 
        }
 

	
 
        // make sure text is not off the canvas
 
        if (left < TEXT_MARGIN)
 
        {
 
            left = std::max(0, pickRect.right()) + TEXT_MARGIN;
 
        }
 
        else if (left + textSize.width() + TEXT_MARGIN > canvasWidth)
 
        {
 
            left = std::min(pickRect.right(), canvasWidth) - (textSize.width() + TEXT_MARGIN);
 
        }
 

	
 
        if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale)
 
        {
 
            int canvasHeight = painter->device()->height();
 
            topLeft = QPointF(left, canvasHeight - textSize.height());
 
        }
 
        else                // top scale
 
        {
 
            topLeft = QPointF(left, 0);
 
        }
 
    }
 
    else                        // left/right scale
 
    {
 
        int canvasHeight = painter->device()->height();
 

	
 
        int top = 0;
 
        if (pickLength > 0)
 
        {
 
            top = pickRect.bottom();
 
        }
 
        else
 
        {
 
            top = pickRect.bottom() - textSize.height();
 
        }
 

	
 
        // make sure text is not off the canvas
 
        if (top < 0)
 
        {
 
            top = std::max(0, pickRect.bottom());
 
        }
 
        else if (top + textSize.height() > canvasHeight)
 
        {
 
            top = std::min(canvasHeight, pickRect.bottom()) - textSize.height();
 
        }
 

	
 
        if (_scaleWidget->alignment() == QwtScaleDraw::LeftScale)
 
        {
 
            topLeft = QPointF(TEXT_MARGIN, top);
 
        }
 
        else                    // right scale
 
        {
 
            int canvasWidth = painter->device()->width();
 
            topLeft = QPointF(canvasWidth - textSize.width() - TEXT_MARGIN, top);
 
        }
 
    }
 
    return QRectF(topLeft, textSize);
 
}
 

	
 
void ScalePicker::drawScaleOverlay(QPainter* painter)
 
{
 
    painter->save();
 
    painter->setPen(_pen);
 

	
 
    if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
 
        _scaleWidget->alignment() == QwtScaleDraw::TopScale)
 
    {
 
        int height = painter->device()->height();
 
        if (started) painter->drawLine(firstPosPx, 0, firstPosPx, height);
 
        if (started || _scaleWidget->underMouse())
 
        {
 
            painter->drawLine(currentPosPx, 0, currentPosPx, height);
 
        }
 
    }
 
    else // vertical
 
    // rotate & adjust coordinate system for vertical drawing
 
    if (_scaleWidget->alignment() == QwtScaleDraw::LeftScale ||
 
        _scaleWidget->alignment() == QwtScaleDraw::RightScale) // vertical
 
    {
 
        int width = painter->device()->width();
 
        if (started) painter->drawLine(0, firstPosPx, width, firstPosPx);
 
        if (started || _scaleWidget->underMouse())
 
        painter->rotate(90);
 
        painter->translate(0, -width);
 
    }
 

	
 
    // draw the indicators
 
    if (started) drawTriangle(painter, firstPosPx);
 
    if (started || _scaleWidget->underMouse())
 
    {
 
        drawTriangle(painter, currentPosPx);
 
    }
 

	
 
    painter->restore();
 
}
 

	
 
void ScalePicker::drawTriangle(QPainter* painter, int position)
 
{
 
    const double tan60 = 1.732;
 
    const double trsize = 10;
 
    const int TRIANGLE_NUM_POINTS = 3;
 
    const int MARGIN = 2;
 
    const QPointF points[TRIANGLE_NUM_POINTS] =
 
        {
 
            painter->drawLine(0, currentPosPx, width, currentPosPx);
 
        }
 
    }
 
            {0, 0},
 
            {-trsize/tan60 , trsize},
 
            {trsize/tan60 , trsize}
 
        };
 

	
 
    painter->save();
 
    painter->setPen(Qt::NoPen);
 
    painter->setBrush(_scaleWidget->palette().windowText());
 
    painter->setRenderHint(QPainter::Antialiasing);
 

	
 
    painter->translate(position, MARGIN);
 
    painter->drawPolygon(points, TRIANGLE_NUM_POINTS);
 

	
 
    painter->restore();
 
}
 

	
 
void ScalePicker::setPen(QPen pen)
 
{
 
    _pen = pen;
 
}
 

	
 
// convert the position of the click to the plot coordinates
 
double ScalePicker::position(double posPx)
 
double ScalePicker::position(double posPx) const
 
{
 
    return _scaleWidget->scaleDraw()->scaleMap().invTransform(posPx);
 
}
 

	
 
int ScalePicker::positionPx(QMouseEvent* mouseEvent)
 
{
 
@@ -245,13 +415,13 @@ int ScalePicker::positionPx(QMouseEvent*
 
/*
 
 * Scale widget and canvas widget is not always aligned. Especially
 
 * when zooming scaleWidget moves around. This causes irregularities
 
 * when drawing the tracker lines. This function maps scale widgets
 
 * pixel coordinate to canvas' coordinate.
 
 */
 
double ScalePicker::posCanvasPx(double pos)
 
double ScalePicker::posCanvasPx(double pos) const
 
{
 
    // assumption: scale.width < canvas.width && scale.x > canvas.x
 
    if (_scaleWidget->alignment() == QwtScaleDraw::BottomScale ||
 
        _scaleWidget->alignment() == QwtScaleDraw::TopScale)
 
    {
 
        return pos + (_scaleWidget->x() - _canvas->x());
 
@@ -267,12 +437,15 @@ void ScalePicker::updateSnapPoints()
 
{
 
    auto allTicks = _scaleWidget->scaleDraw()->scaleDiv().ticks(QwtScaleDiv::MajorTick) +
 
        _scaleWidget->scaleDraw()->scaleDiv().ticks(QwtScaleDiv::MediumTick) +
 
        _scaleWidget->scaleDraw()->scaleDiv().ticks(QwtScaleDiv::MinorTick);
 

	
 
    snapPoints.clear();
 
    snapPointMap.clear();
 
    for(auto t : allTicks)
 
    {
 
        // `round` is used because `allTicks` is double but `snapPoints` is int
 
        snapPoints << round(_scaleWidget->scaleDraw()->scaleMap().transform(t));
 
        int p = round(_scaleWidget->scaleDraw()->scaleMap().transform(t));
 
        snapPoints << p;
 
        snapPointMap[p] = t;
 
    }
 
}
src/scalepicker.h
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -22,12 +22,13 @@
 

	
 
#include <QObject>
 
#include <QMouseEvent>
 
#include <QPen>
 
#include <QWidget>
 
#include <QList>
 
#include <QMap>
 
#include <qwt_scale_widget.h>
 
#include <qwt_widget_overlay.h>
 

	
 
class ScalePicker : public QObject
 
{
 
    Q_OBJECT
 
@@ -38,14 +39,12 @@ public:
 

	
 
    void drawPlotOverlay(QPainter*); // called from ScalePickerOverlay
 
    void drawScaleOverlay(QPainter*); // called from ScaleOverlay
 
    void setPen(QPen pen);
 

	
 
signals:
 
    void pickStarted(double pos);
 
    void picking(double firstPos, double lastPos);
 
    void picked(double firstPos, double lastPos);
 

	
 
private:
 
    QwtScaleWidget* _scaleWidget;
 
    QWidget* _canvas;
 
    QwtWidgetOverlay* pickerOverlay; // will be PlotOverlay
 
@@ -55,16 +54,24 @@ private:
 
    bool pressed;
 
    bool started;
 
    double firstPos; // converted to plot coordinates
 
    double firstPosPx; // pixel coordinates
 
    double currentPosPx; // current position in pixel coordinates
 
    QList<int> snapPoints;
 
    /// used to restore precision of snappoints that is lost due to rounding
 
    QMap<int, double> snapPointMap;
 

	
 
    double position(double); // returns the axis mouse position relative to plot coordinates
 
    double position(double) const; // returns the axis mouse position relative to plot coordinates
 
    int positionPx(QMouseEvent*); // returns the axis mouse position in pixels
 
    double posCanvasPx(double pos); // returns the given position in canvas coordinates
 
    double posCanvasPx(double pos) const; // returns the given position in canvas coordinates
 
    void drawTriangle(QPainter* painter, int position);
 
    QwtText trackerText() const;
 
     /// Returns tracker text position
 
    QRectF trackerTextRect(QPainter* painter, int posPx, QSizeF textSize) const;
 
    /// Returns the text position for tracker text shown during picking
 
    QRectF pickTrackerTextRect(QPainter* painter, QRect pickRect, QSizeF textSize) const;
 

	
 
private slots:
 
    void updateSnapPoints();
 
};
 

	
 
#endif // SCALEPICKER_H
src/setting_defines.h
Show inline comments
 
@@ -27,12 +27,13 @@ const char SettingGroup_Binary[] = "Data
 
const char SettingGroup_ASCII[] = "DataFormat_ASCII";
 
const char SettingGroup_CustomFrame[] = "DataFormat_CustomFrame";
 
const char SettingGroup_Channels[] = "Channels";
 
const char SettingGroup_Plot[] = "Plot";
 
const char SettingGroup_Commands[] = "Commands";
 
const char SettingGroup_Record[] = "Record";
 
const char SettingGroup_UpdateCheck[] = "UpdateCheck";
 

	
 
// mainwindow setting keys
 
const char SG_MainWindow_Size[] = "size";
 
const char SG_MainWindow_Pos[] = "pos";
 
const char SG_MainWindow_ActivePanel[] = "activePanel";
 
const char SG_MainWindow_HidePanels[] = "hidePanels";
 
@@ -54,12 +55,14 @@ const char SG_DataFormat_Format[] = "for
 
const char SG_Binary_NumOfChannels[] = "numOfChannels";
 
const char SG_Binary_NumberFormat[] = "numberFormat";
 
const char SG_Binary_Endianness[] = "endianness";
 

	
 
// ascii reader keys
 
const char SG_ASCII_NumOfChannels[] = "numOfChannels";
 
const char SG_ASCII_Delimiter[] = "delimiter";
 
const char SG_ASCII_CustomDelimiter[] = "customDelimiter";
 

	
 
// framed reader keys
 
const char SG_CustomFrame_NumOfChannels[] = "numOfChannels";
 
const char SG_CustomFrame_FrameStart[] = "frameStart";
 
const char SG_CustomFrame_FixedSize[] = "fixedSize";
 
const char SG_CustomFrame_FrameSize[] = "frameSize";
 
@@ -73,20 +76,24 @@ const char SG_Channels_Channel[] = "chan
 
const char SG_Channels_Name[] = "name";
 
const char SG_Channels_Color[] = "color";
 
const char SG_Channels_Visible[] = "visible";
 

	
 
// plot settings keys
 
const char SG_Plot_NumOfSamples[] = "numOfSamples";
 
const char SG_Plot_IndexAsX[] = "indexAsX";
 
const char SG_Plot_XMax[] = "xMax";
 
const char SG_Plot_XMin[] = "xMin";
 
const char SG_Plot_AutoScale[] = "autoScale";
 
const char SG_Plot_YMax[] = "yMax";
 
const char SG_Plot_YMin[] = "yMin";
 
const char SG_Plot_DarkBackground[] = "darkBackground";
 
const char SG_Plot_Grid[] = "grid";
 
const char SG_Plot_MinorGrid[] = "minorGrid";
 
const char SG_Plot_Legend[] = "legend";
 
const char SG_Plot_MultiPlot[] = "multiPlot";
 
const char SG_Plot_Symbols[] = "symbols";
 

	
 
// 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";
 
@@ -96,7 +103,11 @@ const char SG_Record_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";
 

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

	
 
#endif // SETTING_DEFINES_H
src/snapshot.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -18,23 +18,24 @@
 
*/
 

	
 
#include <stddef.h>
 
#include <QSaveFile>
 
#include <QTextStream>
 

	
 
#include "mainwindow.h"
 
#include "snapshot.h"
 
#include "snapshotview.h"
 

	
 
Snapshot::Snapshot(QMainWindow* parent, QString name, ChannelInfoModel infoModel) :
 
Snapshot::Snapshot(MainWindow* parent, QString name, ChannelInfoModel infoModel, bool saved) :
 
    QObject(parent),
 
    cInfoModel(infoModel),
 
    _showAction(this),
 
    _deleteAction("&Delete", this)
 
{
 
    _name = name;
 
    _saved = false;
 
    _saved = saved;
 

	
 
    view = NULL;
 
    mainWindow = parent;
 
    _showAction.setText(displayName());
 
    connect(&_showAction, &QAction::triggered, this, &Snapshot::show);
 

	
src/snapshot.h
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -18,28 +18,28 @@
 
*/
 

	
 
#ifndef SNAPSHOT_H
 
#define SNAPSHOT_H
 

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

	
 
#include "channelinfomodel.h"
 

	
 
class SnapshotView;
 
class MainWindow;
 

	
 
class Snapshot : public QObject
 
{
 
    Q_OBJECT
 

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

	
 
    QVector<QVector<QPointF>> data;
 
    QAction* showAction();
 
    QAction* deleteAction();
 

	
 
@@ -58,13 +58,13 @@ signals:
 

	
 
private:
 
    QString _name;
 
    ChannelInfoModel cInfoModel;
 
    QAction _showAction;
 
    QAction _deleteAction;
 
    QMainWindow* mainWindow;
 
    MainWindow* mainWindow;
 
    SnapshotView* view;
 
    bool _saved;
 

	
 
private slots:
 
    void show();
 
    void viewClosed();
src/snapshotmanager.cpp
Show inline comments
 
@@ -21,28 +21,31 @@
 
#include <QMenuBar>
 
#include <QKeySequence>
 
#include <QFileDialog>
 
#include <QFile>
 
#include <QVector>
 
#include <QPointF>
 
#include <QIcon>
 
#include <QtDebug>
 

	
 
#include "mainwindow.h"
 
#include "snapshotmanager.h"
 

	
 
SnapshotManager::SnapshotManager(QMainWindow* mainWindow,
 
SnapshotManager::SnapshotManager(MainWindow* mainWindow,
 
                                 ChannelManager* channelMan) :
 
    _menu("&Snapshots"),
 
    _takeSnapshotAction("&Take Snapshot", this),
 
    loadSnapshotAction("&Load Snapshots", this),
 
    clearAction("&Clear Snapshots", this)
 
{
 
    _mainWindow = mainWindow;
 
    _channelMan = channelMan;
 

	
 
    _takeSnapshotAction.setToolTip("Take a snapshot of current plot");
 
    _takeSnapshotAction.setShortcut(QKeySequence("F5"));
 
    _takeSnapshotAction.setIcon(QIcon::fromTheme("camera"));
 
    loadSnapshotAction.setToolTip("Load snapshots from CSV files");
 
    clearAction.setToolTip("Delete all snapshots");
 
    connect(&_takeSnapshotAction, SIGNAL(triggered(bool)),
 
            this, SLOT(takeSnapshot()));
 
    connect(&clearAction, SIGNAL(triggered(bool)),
 
            this, SLOT(clearSnapshots()));
 
@@ -188,13 +191,14 @@ void SnapshotManager::loadSnapshotFromFi
 
        lineNum++;
 
    }
 

	
 
    ChannelInfoModel channelInfo(channelNames);
 

	
 
    auto snapshot = new Snapshot(
 
        _mainWindow, QFileInfo(fileName).baseName(), ChannelInfoModel(channelNames));
 
        _mainWindow, QFileInfo(fileName).baseName(),
 
        ChannelInfoModel(channelNames), true);
 
    snapshot->data = data;
 

	
 
    addSnapshot(snapshot, false);
 
}
 

	
 
QMenu* SnapshotManager::menu()
src/snapshotmanager.h
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -25,31 +25,33 @@
 
#include <QMenu>
 

	
 
#include "framebuffer.h"
 
#include "channelmanager.h"
 
#include "snapshot.h"
 

	
 
class MainWindow;
 

	
 
class SnapshotManager : public QObject
 
{
 
    Q_OBJECT
 

	
 
public:
 
    SnapshotManager(QMainWindow* mainWindow, ChannelManager* channelMan);
 
    SnapshotManager(MainWindow* mainWindow, ChannelManager* channelMan);
 
    ~SnapshotManager();
 

	
 
    QMenu* menu();
 
    QAction* takeSnapshotAction();
 

	
 
    /// Creates a dynamically allocated snapshot object but doesn't record it in snapshots list.
 
    /// @note Caller is responsible for deletion of the returned `Snapshot` object.
 
    Snapshot* makeSnapshot();
 

	
 
    bool isAllSaved(); ///< returns `true` if all snapshots are saved to a file
 

	
 
private:
 
    QMainWindow* _mainWindow;
 
    MainWindow* _mainWindow;
 
    ChannelManager* _channelMan;
 

	
 
    QList<Snapshot*> snapshots;
 

	
 
    QMenu _menu;
 
    QAction _takeSnapshotAction;
src/snapshotview.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -17,32 +17,35 @@
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "snapshotview.h"
 
#include "ui_snapshotview.h"
 

	
 
SnapshotView::SnapshotView(QWidget *parent, Snapshot* snapshot) :
 
SnapshotView::SnapshotView(MainWindow* parent, Snapshot* snapshot) :
 
    QMainWindow(parent),
 
    ui(new Ui::SnapshotView),
 
    renameDialog(this)
 
{
 
    _snapshot = snapshot;
 

	
 
    ui->setupUi(this);
 

	
 
    plotMan = new PlotManager(ui->plotArea, snapshot->infoModel());
 
    plotMan = new PlotManager(ui->plotArea, snapshot->infoModel(), this);
 
    plotMan->setViewSettings(parent->viewSettings());
 

	
 
    ui->menuSnapshot->insertAction(ui->actionClose, snapshot->deleteAction());
 
    this->setWindowTitle(snapshot->displayName());
 

	
 
    // initialize curves
 
    unsigned numOfChannels = snapshot->data.size();
 
    unsigned numOfSamples = snapshot->data[0].size();
 
    for (unsigned ci = 0; ci < numOfChannels; ci++)
 
    {
 
        plotMan->addCurve(snapshot->channelName(ci), snapshot->data[ci]);
 
    }
 
    plotMan->setNumOfSamples(numOfSamples);
 

	
 
    renameDialog.setWindowTitle("Rename Snapshot");
 
    renameDialog.setLabelText("Enter new name:");
 
    connect(ui->actionRename, &QAction::triggered,
 
            this, &SnapshotView::showRenameDialog);
 

	
 
@@ -59,12 +62,13 @@ SnapshotView::SnapshotView(QWidget *pare
 
SnapshotView::~SnapshotView()
 
{
 
    for (auto curve : curves)
 
    {
 
        delete curve;
 
    }
 
    delete plotMan;
 
    delete ui;
 
}
 

	
 
void SnapshotView::closeEvent(QCloseEvent *event)
 
{
 
    QMainWindow::closeEvent(event);
src/snapshotview.h
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -26,25 +26,26 @@
 
#include <QVector>
 
#include <QPointF>
 
#include <QPen>
 
#include <QCloseEvent>
 
#include <qwt_plot_curve.h>
 

	
 
#include "mainwindow.h"
 
#include "plotmanager.h"
 
#include "snapshot.h"
 

	
 
namespace Ui {
 
class SnapshotView;
 
}
 

	
 
class SnapshotView : public QMainWindow
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit SnapshotView(QWidget *parent, Snapshot* snapshot);
 
    explicit SnapshotView(MainWindow* parent, Snapshot* snapshot);
 
    ~SnapshotView();
 

	
 
signals:
 
    void closed();
 

	
 
private:
src/updatecheckdialog.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include "setting_defines.h"
 
#include "updatecheckdialog.h"
 
#include "ui_updatecheckdialog.h"
 

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

	
 
    // by default start from yesterday, so that we check at first run
 
    lastCheck = QDate::currentDate().addDays(-1);
 

	
 
    connect(&updateChecker, &UpdateChecker::checkFailed,
 
            [this](QString errorMessage)
 
            {
 
                lastCheck = QDate::currentDate();
 
                ui->label->setText(QString("Update check failed.\n") + errorMessage);
 
            });
 

	
 
    connect(&updateChecker, &UpdateChecker::checkFinished,
 
            [this](bool found, QString newVersion, QString downloadUrl)
 
            {
 
                QString text;
 
                if (!found)
 
                {
 
                    text = "There is no update yet.";
 
                }
 
                else
 
                {
 
                    show();
 
#ifdef UPDATE_TYPE_PKGMAN
 
                    text = QString("There is a new version: %1. "
 
                                   "Use your package manager to update"
 
                                   " or click to <a href=\"%2\">download</a>.")\
 
                        .arg(newVersion).arg(downloadUrl);
 
#else
 
                    text = QString("Found update to version %1. Click to <a href=\"%2\">download</a>.")\
 
                        .arg(newVersion).arg(downloadUrl);
 
#endif
 
                }
 

	
 
                lastCheck = QDate::currentDate();
 
                ui->label->setText(text);
 
            });
 
}
 

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

	
 
void UpdateCheckDialog::showEvent(QShowEvent *event)
 
{
 
    updateChecker.checkUpdate();
 
    ui->label->setText("Checking update...");
 
}
 

	
 
void UpdateCheckDialog::closeEvent(QShowEvent *event)
 
{
 
    if (updateChecker.isChecking()) updateChecker.cancelCheck();
 
}
 

	
 
void UpdateCheckDialog::saveSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_UpdateCheck);
 
    settings->setValue(SG_UpdateCheck_Periodic, ui->cbPeriodic->isChecked());
 
    settings->setValue(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate));
 
    settings->endGroup();
 
}
 

	
 
void UpdateCheckDialog::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_UpdateCheck);
 
    ui->cbPeriodic->setChecked(settings->value(SG_UpdateCheck_Periodic,
 
                                               ui->cbPeriodic->isChecked()).toBool());
 
    auto lastCheckS = settings->value(SG_UpdateCheck_LastCheck, lastCheck.toString(Qt::ISODate)).toString();
 
    lastCheck = QDate::fromString(lastCheckS, Qt::ISODate);
 
    settings->endGroup();
 

	
 
    // start the periodic update if required
 
    if (ui->cbPeriodic->isChecked() && lastCheck < QDate::currentDate())
 
    {
 
        updateChecker.checkUpdate();
 
    }
 
}
src/updatecheckdialog.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef UPDATECHECKDIALOG_H
 
#define UPDATECHECKDIALOG_H
 

	
 
#include <QDialog>
 
#include <QDate>
 
#include <QSettings>
 
#include "updatechecker.h"
 

	
 
namespace Ui {
 
class UpdateCheckDialog;
 
}
 

	
 
class UpdateCheckDialog : public QDialog
 
{
 
    Q_OBJECT
 

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

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

	
 
private:
 
    Ui::UpdateCheckDialog *ui;
 
    UpdateChecker updateChecker;
 
    QDate lastCheck;
 

	
 
    void showEvent(QShowEvent *event);
 
    void closeEvent(QShowEvent *event);
 
};
 

	
 
#endif // UPDATECHECKDIALOG_H
src/updatecheckdialog.ui
Show inline comments
 
new file 100644
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>UpdateCheckDialog</class>
 
 <widget class="QDialog" name="UpdateCheckDialog">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>400</width>
 
    <height>148</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Check Update</string>
 
  </property>
 
  <layout class="QVBoxLayout" name="verticalLayout">
 
   <item>
 
    <widget class="QLabel" name="label">
 
     <property name="text">
 
      <string>Checking update...</string>
 
     </property>
 
     <property name="openExternalLinks">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QCheckBox" name="cbPeriodic">
 
     <property name="toolTip">
 
      <string>Updates will be checked only once a day at first start of the application</string>
 
     </property>
 
     <property name="text">
 
      <string>Check updates periodically</string>
 
     </property>
 
     <property name="checked">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QDialogButtonBox" name="buttonBox">
 
     <property name="orientation">
 
      <enum>Qt::Horizontal</enum>
 
     </property>
 
     <property name="standardButtons">
 
      <set>QDialogButtonBox::Close</set>
 
     </property>
 
    </widget>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections>
 
  <connection>
 
   <sender>buttonBox</sender>
 
   <signal>clicked(QAbstractButton*)</signal>
 
   <receiver>UpdateCheckDialog</receiver>
 
   <slot>close()</slot>
 
   <hints>
 
    <hint type="sourcelabel">
 
     <x>199</x>
 
     <y>125</y>
 
    </hint>
 
    <hint type="destinationlabel">
 
     <x>199</x>
 
     <y>73</y>
 
    </hint>
 
   </hints>
 
  </connection>
 
 </connections>
 
</ui>
src/updatechecker.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 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 <QJsonDocument>
 
#include <QJsonObject>
 
#include <QJsonArray>
 
#include <QJsonValue>
 
#include <QRegularExpression>
 
#include <algorithm>
 
#include <functional>
 

	
 
#include "updatechecker.h"
 

	
 
// This link returns the list of downloads in JSON format. Note that we only use
 
// the first page because results are sorted new to old.
 
const char BB_DOWNLOADS_URL[] = "https://api.bitbucket.org/2.0/repositories/hyozd/serialplot/downloads?fields=values.name,values.links.self.href";
 

	
 
UpdateChecker::UpdateChecker(QObject *parent) :
 
    QObject(parent), nam(this)
 
{
 
    activeReply = NULL;
 

	
 
    connect(&nam, &QNetworkAccessManager::finished,
 
            this, &UpdateChecker::onReqFinished);
 
}
 

	
 
bool UpdateChecker::isChecking() const
 
{
 
    return activeReply != NULL && !activeReply->isFinished();
 
}
 

	
 
void UpdateChecker::checkUpdate()
 
{
 
    if (isChecking()) return;
 

	
 
    auto req = QNetworkRequest(QUrl(BB_DOWNLOADS_URL));
 
    activeReply = nam.get(req);
 
}
 

	
 
void UpdateChecker::cancelCheck()
 
{
 
    if (activeReply != NULL) activeReply->abort();
 
}
 

	
 
void UpdateChecker::onReqFinished(QNetworkReply* reply)
 
{
 
    if (reply->error() != QNetworkReply::NoError)
 
    {
 
        emit checkFailed(QString("Network error: ") + reply->errorString());
 
    }
 
    else
 
    {
 
        QJsonParseError error;
 
        auto data = QJsonDocument::fromJson(reply->readAll(), &error);
 
        if (error.error != QJsonParseError::NoError)
 
        {
 
            emit checkFailed(QString("JSon parsing error: ") + error.errorString());
 
        }
 
        else
 
        {
 
            QList<FileInfo> files;
 
            if (!parseData(data, files))
 
            {
 
                // TODO: emit detailed data contents for logging
 
                emit checkFailed("Data parsing error.");
 
            }
 
            else
 
            {
 
                FileInfo updateFile;
 
                if (findUpdate(files, updateFile))
 
                {
 
                    emit checkFinished(
 
                        true, updateFile.version.toString(), updateFile.link);
 
                }
 
                else
 
                {
 
                    emit checkFinished(false, "", "");
 
                }
 
            }
 
        }
 
    }
 
    reply->deleteLater();
 
    activeReply = NULL;
 
}
 

	
 
bool UpdateChecker::parseData(const QJsonDocument& data, QList<FileInfo>& files) const
 
{
 
    /* Data is expected to be in this form:
 

	
 
    {
 
       "values": [
 
       {
 
         "name": "serialplot-0.9.1-x86_64.AppImage",
 
         "links": {
 
           "self": {
 
             "href": "https://api.bitbucket.org/2.0/repositories/hyOzd/serialplot/downloads/serialplot-0.9.1-x86_64.AppImage"
 
            }
 
          }
 
       }, ... ]
 
    }
 
    */
 

	
 
    if (!data.isObject()) return false;
 

	
 
    auto values = data.object()["values"];
 
    if (values == QJsonValue::Undefined || !values.isArray()) return false;
 

	
 
    for (auto value : values.toArray())
 
    {
 
        if (!value.isObject()) return false;
 

	
 
        auto name = value.toObject().value("name");
 
        if (name.isUndefined() || !name.isString())
 
             return false;
 

	
 
        auto links = value.toObject().value("links");
 
        if (links.isUndefined() || !links.isObject())
 
            return false;
 

	
 
        auto self = links.toObject().value("self");
 
        if (self.isUndefined() || !self.isObject())
 
            return false;
 

	
 
        auto href = self.toObject().value("href");
 
        if (href.isUndefined() || !href.isString())
 
            return false;
 

	
 
        FileInfo finfo;
 
        finfo.name = name.toString();
 
        finfo.link = href.toString();
 
        finfo.hasVersion = VersionNumber::extract(name.toString(), finfo.version);
 

	
 
        if (finfo.name.contains("amd64") ||
 
            finfo.name.contains("x86_64") ||
 
            finfo.name.contains("win64"))
 
        {
 
            finfo.arch = FileArch::amd64;
 
        }
 
        else if (finfo.name.contains("win32") ||
 
                 finfo.name.contains("i386"))
 
        {
 
            finfo.arch = FileArch::i386;
 
        }
 
        else
 
        {
 
            finfo.arch = FileArch::unknown;
 
        }
 

	
 
        files += finfo;
 
    }
 

	
 
    return true;
 
}
 

	
 
bool UpdateChecker::findUpdate(const QList<FileInfo>& files, FileInfo& foundFile) const
 
{
 
    QList<FileInfo> fflist;
 

	
 
    // filter the file list according to extension and version number
 
    for (int i = 0; i < files.length(); i++)
 
    {
 
        // file type to look
 
#if defined(Q_OS_WIN)
 
        const char ext[] = ".exe";
 
#else  // of course linux
 
        const char ext[] = ".appimage";
 
#endif
 

	
 
        // file architecture to look
 
#if defined(Q_PROCESSOR_X86_64)
 
        const FileArch arch = FileArch::amd64;
 
#elif defined(Q_PROCESSOR_X86_32)
 
        const FileArch arch = FileArch::i386;
 
#elif defined(Q_PROCESSOR_ARM)
 
        const FileArch arch = FileArch::arm;
 
#else
 
        #error Unknown architecture for update file detection.
 
#endif
 

	
 
        // filter the file list
 
        auto file = files[i];
 
        if (file.name.contains(ext, Qt::CaseInsensitive) &&
 
            file.arch == arch &&
 
            file.hasVersion && file.version > CurrentVersion)
 
        {
 
            fflist += file;
 
        }
 
    }
 

	
 
    // sort and find most up to date file
 
    if (!fflist.empty())
 
    {
 
        std::sort(fflist.begin(), fflist.end(),
 
                  [](const FileInfo& a, const FileInfo& b)
 
                  {
 
                      return a.version > b.version;
 
                  });
 

	
 
        foundFile = fflist[0];
 
        return true;
 
    }
 
    else
 
    {
 
        return false;
 
    }
 
}
src/updatechecker.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef UPDATECHECKER_H
 
#define UPDATECHECKER_H
 

	
 
#include <QObject>
 
#include <QNetworkAccessManager>
 
#include <QNetworkReply>
 
#include <QList>
 

	
 
#include "versionnumber.h"
 

	
 
class UpdateChecker : public QObject
 
{
 
    Q_OBJECT
 
public:
 
    explicit UpdateChecker(QObject *parent = 0);
 

	
 
    bool isChecking() const;
 

	
 
signals:
 
    void checkFinished(bool found, QString newVersion, QString downloadUrl);
 
    void checkFailed(QString errorMessage);
 

	
 
public slots:
 
    void checkUpdate();
 
    void cancelCheck();
 

	
 
private:
 
    enum class FileArch
 
    {
 
        unknown,
 
        i386,
 
        amd64,
 
        arm
 
    };
 

	
 
    struct FileInfo
 
    {
 
        QString name;
 
        QString link;
 
        bool hasVersion;
 
        VersionNumber version;
 
        FileArch arch;
 
    };
 

	
 
    QNetworkAccessManager nam;
 
    QNetworkReply* activeReply;
 

	
 
    /// Parses json and creates a list of files
 
    bool parseData(const QJsonDocument& data, QList<FileInfo>& files) const;
 
    /// Finds the update file in the file list. Returns `-1` if no new version
 
    /// is found.
 
    bool findUpdate(const QList<FileInfo>& files, FileInfo& foundFile) const;
 

	
 
private slots:
 
    void onReqFinished(QNetworkReply* reply);
 
};
 

	
 
#endif // UPDATECHECKER_H
src/version.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef VERSION_H
 
#define VERSION_H
 

	
 
#ifndef VERSION_STRING
 
#warning VERSION_STRING not defined!
 
#define VERSION_STRING   "0.0.0"
 
#endif
 

	
 
#ifndef VERSION_REVISION
 
#define VERSION_REVISION ""
 
#endif
 

	
 
#endif // VERSION_H
src/version.h.in
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -17,13 +17,10 @@
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef VERSION_H
 
#define VERSION_H
 

	
 
#define MAJOR_VERSION    @MAJOR_VERSION@
 
#define MINOR_VERSION    @MINOR_VERSION@
 
#define PATCH_VERSION    @PATCH_VERSION@
 
#define VERSION_STRING   "@VERSION_STRING@"
 
#define VERSION_REVISION "@VERSION_REVISION@"
 

	
 
#endif // VERSION_H
src/versionnumber.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#include <QRegularExpression>
 

	
 
#include "versionnumber.h"
 

	
 
VersionNumber::VersionNumber(unsigned mj, unsigned mn, unsigned pt)
 
{
 
    major = mj;
 
    minor = mn;
 
    patch = pt;
 
}
 

	
 
QString VersionNumber::toString() const
 
{
 
    return QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
 
}
 

	
 
bool VersionNumber::extract(const QString& str, VersionNumber& number)
 
{
 
    QRegularExpression regexp("(?:[-_vV \\t]|^)(?<major>\\d+)"
 
                              "(?:\\.(?<minor>\\d+))(?:\\.(?<patch>\\d+))?[-_ \\t]?");
 
    auto match = regexp.match(str, 0, QRegularExpression::PartialPreferCompleteMatch);
 

	
 
    if (!(match.hasMatch() || match.hasPartialMatch())) return false;
 

	
 
    number.major = match.captured("major").toUInt();
 

	
 
    auto zeroIfNull = [](QString str) -> unsigned
 
        {
 
            if (str.isNull()) return 0;
 
            return str.toUInt();
 
        };
 

	
 
    number.minor = zeroIfNull(match.captured("minor"));
 
    number.patch = zeroIfNull(match.captured("patch"));
 

	
 
    return true;
 
}
 

	
 
bool operator==(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    return lhs.major == rhs.major &&
 
           lhs.minor == rhs.minor &&
 
           lhs.patch == rhs.patch;
 
}
 

	
 
bool operator<(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    if (lhs.major < rhs.major)
 
    {
 
        return true;
 
    }
 
    else if (lhs.major == rhs.major)
 
    {
 
        if (lhs.minor < rhs.minor)
 
        {
 
             return true;
 
        }
 
        else if (lhs.minor == rhs.minor)
 
        {
 
            if (lhs.patch < rhs.patch) return true;
 
        }
 
    }
 
    return false;
 
}
 

	
 
bool operator>(const VersionNumber& lhs, const VersionNumber& rhs)
 
{
 
    if (lhs.major > rhs.major)
 
    {
 
        return true;
 
    }
 
    else if (lhs.major == rhs.major)
 
    {
 
        if (lhs.minor > rhs.minor)
 
        {
 
             return true;
 
        }
 
        else if (lhs.minor == rhs.minor)
 
        {
 
            if (lhs.patch > rhs.patch) return true;
 
        }
 
    }
 
    return false;
 
}
src/versionnumber.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

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

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

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

	
 
#ifndef VERSIONNUMBER_H
 
#define VERSIONNUMBER_H
 

	
 
#include <QString>
 

	
 
struct VersionNumber
 
{
 
    unsigned major = 0;
 
    unsigned minor = 0;
 
    unsigned patch = 0;
 

	
 
    VersionNumber(unsigned mj=0, unsigned mn=0, unsigned pt=0);
 

	
 
    /// Convert version number to string.
 
    QString toString() const;
 

	
 
    /// Extracts the version number from given string.
 
    static bool extract(const QString& str, VersionNumber& number);
 
};
 

	
 
bool operator==(const VersionNumber& lhs, const VersionNumber& rhs);
 
bool operator<(const VersionNumber& lhs, const VersionNumber& rhs);
 
bool operator>(const VersionNumber& lhs, const VersionNumber& rhs);
 

	
 
const VersionNumber CurrentVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
 

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

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -18,15 +18,19 @@
 
*/
 

	
 
#include "zoomer.h"
 
#include <qwt_plot.h>
 
#include <QtDebug>
 

	
 
#include <QMouseEvent>
 

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

	
 
    // 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);
 
@@ -49,6 +53,91 @@ void Zoomer::zoom( const QRectF & rect)
 
    {
 
        this->setZoomBase(false);
 
    }
 

	
 
    ScrollZoomer::zoom(rect);
 
}
 

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

	
 
    const QPolygon pa = selection();
 
    if (pa.count() < 2)
 
    {
 
        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::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
 
    {
 
        ScrollZoomer::widgetMouseMoveEvent(mouseEvent);
 
    }
 
}
 

	
 
void Zoomer::widgetMouseReleaseEvent(QMouseEvent* mouseEvent)
 
{
 
    if (is_panning)
 
    {
 
        is_panning = false;
 
        parentWidget()->setCursor(Qt::CrossCursor);
 
    }
 
    else
 
    {
 
        ScrollZoomer::widgetMouseReleaseEvent(mouseEvent);
 
    }
 
}
src/zoomer.h
Show inline comments
 
/*
 
  Copyright © 2016 Hasan Yavuz Özderya
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
@@ -30,9 +30,27 @@ public:
 
    Zoomer(QWidget *, bool doReplot=true);
 
    void zoom(int up);
 
    void zoom( const QRectF & );
 

	
 
signals:
 
    void unzoomed();
 

	
 
protected:
 
    /// Re-implemented to display selection size in the tracker text.
 
    QwtText trackerTextF(const QPointF &pos) const;
 
    /// Re-implemented for alpha background
 
    void drawRubberBand(QPainter* painter) const;
 
    /// 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;
 
};
 

	
 
#endif // ZOOMER_H
0 comments (0 inline, 0 general)